diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..bb92c879a --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,14 @@ +ARG INSTALL_NODE=false +ARG INSTALL_AZURE_CLI=false +FROM mcr.microsoft.com/vscode/devcontainers/dotnet:dev-5.0 + +ENV DOTNET_NOLOGO=true +ENV DOTNET_CLI_TELEMETRY_OPTOUT=true +ENV DEVCONTAINER=true + +# install redis-cli and ping +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends iputils-ping redis-tools mono-runtime + +# install SDK 3.1 +RUN curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 3.1 --install-dir /usr/share/dotnet/ diff --git a/.devcontainer/TestConfig.json b/.devcontainer/TestConfig.json new file mode 100644 index 000000000..2ec990b9f --- /dev/null +++ b/.devcontainer/TestConfig.json @@ -0,0 +1,11 @@ +{ + "MasterServer": "redis", + "ReplicaServer": "redis", + "SecureServer": "redis", + "FailoverMasterServer": "redis", + "FailoverReplicaServer": "redis", + "IPv4Server": "redis", + "RemoteServer": "redis", + "SentinelServer": "redis", + "ClusterServer": "redis" +} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..24c9b1186 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,17 @@ +{ + "name": "StackExchange.Redis", + "dockerComposeFile": [ + "docker-compose.yml" + ], + "service": "devcontainer", + "workspaceFolder": "/workspace", + "postCreateCommand": "dotnet restore Build.csproj", + + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + }, + "extensions": [ + "ms-dotnettools.csharp", + "ms-azuretools.vscode-docker" + ], +} \ No newline at end of file diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 000000000..a801d6f1e --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3.7' + +services: + devcontainer: + build: + context: . + dockerfile: Dockerfile + volumes: + - ..:/workspace:cached + # Mount the TestConfig.json file as readonly, so that tests talk to services in the internal docker network + # This leaves the original TestsConfig.json outside the devcontainer untouched + - ./TestConfig.json:/workspace/tests/StackExchange.Redis.Tests/TestConfig.json:ro + depends_on: + - redis + links: + - "redis:redis" + command: /bin/sh -c "while sleep 1000; do :; done" + redis: + build: + context: ../tests/RedisConfigs + dockerfile: Dockerfile + sysctls : + net.core.somaxconn: '511' \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..eb05866a0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,213 @@ +# editorconfig.org +root = true + +# Don't use tabs for indentation. +[*] +indent_style = space + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom +trim_trailing_whitespace = true + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# Xml config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +# Dotnet code style settings: +[*.{cs,vb}] +# Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = true:warning +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +# Modifiers +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +dotnet_style_readonly_field = true:warning + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_null_propagation = true:warning +dotnet_style_coalesce_expression = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_auto_properties = true:suggestion + +# Ignore silly if statements +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion + +# Don't warn on things that actually need suppressing +dotnet_remove_unnecessary_suppression_exclusions = CA1009,CA1063,CA1069,CA1416,CA1816,CA1822,CA2202,CS0618,IDE0060,IDE0062,RCS1047,RCS1085,RCS1090,RCS1194,RCS1231 + +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +# CSharp code style settings: +[*.cs] +# Prefer method-like constructs to have a expression-body +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_operators = true:warning + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_lambdas = true:warning +csharp_style_expression_bodied_local_functions = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning + +# Null-checking preferences +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:warning + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,volatile,async:suggestion + +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:silent +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:warning +csharp_prefer_simple_using_statement = true:silent +csharp_style_prefer_not_pattern = true:warning +csharp_style_prefer_switch_expression = true:warning + +# Disable range operator suggestions +csharp_style_prefer_range_operator = false:none +csharp_style_prefer_index_operator = false:none + +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true:warning +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false + +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true + +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852 +# Tags : Telemetry, EnabledRuleInAggressiveMode, CompilationEnd +dotnet_diagnostic.CA1852.severity = warning + +# IDE preferences +dotnet_diagnostic.IDE0090.severity = silent # IDE0090: Use 'new(...)' + +#Roslynator preferences +dotnet_diagnostic.RCS1037.severity = error # RCS1037: Remove trailing white-space. +dotnet_diagnostic.RCS1098.severity = none # RCS1098: Constant values should be placed on right side of comparisons. + +dotnet_diagnostic.RCS1194.severity = none # RCS1194: Implement exception constructors. +dotnet_diagnostic.RCS1229.severity = none # RCS1229: Use async/await when necessary. +dotnet_diagnostic.RCS1233.severity = none # RCS1233: Use short-circuiting operator. +dotnet_diagnostic.RCS1234.severity = none # RCS1234: Duplicate enum value. + +# StyleCop preferences +dotnet_diagnostic.SA0001.severity = none # SA0001: XML comment analysis is disabled + +dotnet_diagnostic.SA1101.severity = none # SA1101: Prefix local calls with this +dotnet_diagnostic.SA1108.severity = none # SA1108: Block statements should not contain embedded comments +dotnet_diagnostic.SA1122.severity = none # SA1122: Use string.Empty for empty strings +dotnet_diagnostic.SA1127.severity = none # SA1127: Generic type constraints should be on their own line +dotnet_diagnostic.SA1128.severity = none # SA1128: Put constructor initializers on their own line +dotnet_diagnostic.SA1132.severity = none # SA1132: Do not combine fields +dotnet_diagnostic.SA1133.severity = none # SA1133: Do not combine attributes + +dotnet_diagnostic.SA1200.severity = none # SA1200: Using directives should be placed correctly +dotnet_diagnostic.SA1201.severity = none # SA1201: Elements should appear in the correct order +dotnet_diagnostic.SA1202.severity = none # SA1202: Elements should be ordered by access +dotnet_diagnostic.SA1203.severity = none # SA1203: Constants should appear before fields + +dotnet_diagnostic.SA1306.severity = none # SA1306: Field names should begin with lower-case letter +dotnet_diagnostic.SA1309.severity = none # SA1309: Field names should not begin with underscore +dotnet_diagnostic.SA1310.severity = silent # SA1310: Field names should not contain underscore +dotnet_diagnostic.SA1311.severity = none # SA1311: Static readonly fields should begin with upper-case letter +dotnet_diagnostic.SA1312.severity = none # SA1312: Variable names should begin with lower-case letter + +dotnet_diagnostic.SA1401.severity = silent # SA1401: Fields should be private +dotnet_diagnostic.SA1402.severity = suggestion # SA1402: File may only contain a single type + +dotnet_diagnostic.SA1503.severity = silent # SA1503: Braces should not be omitted +dotnet_diagnostic.SA1516.severity = silent # SA1516: Elements should be separated by blank line + +dotnet_diagnostic.SA1600.severity = none # SA1600: Elements should be documented +dotnet_diagnostic.SA1601.severity = none # SA1601: Partial elements should be documented +dotnet_diagnostic.SA1602.severity = none # SA1602: Enumeration items should be documented +dotnet_diagnostic.SA1615.severity = none # SA1615: Element return value should be documented +dotnet_diagnostic.SA1623.severity = none # SA1623: Property summary documentation should match accessors +dotnet_diagnostic.SA1633.severity = none # SA1633: File should have header +dotnet_diagnostic.SA1642.severity = none # SA1642: Constructor summary documentation should begin with standard text +dotnet_diagnostic.SA1643.severity = none # SA1643: Destructor summary documentation should begin with standard text + + +# To Fix: +dotnet_diagnostic.SA1204.severity = none # SA1204: Static elements should appear before instance elements +dotnet_diagnostic.SA1214.severity = none # SA1214: Readonly fields should appear before non-readonly fields +dotnet_diagnostic.SA1304.severity = none # SA1304: Non-private readonly fields should begin with upper-case letter +dotnet_diagnostic.SA1307.severity = none # SA1307: Accessible fields should begin with upper-case letter +dotnet_diagnostic.SA1308.severity = suggestion # SA1308: Variable names should not be prefixed +dotnet_diagnostic.SA1131.severity = none # SA1131: Use readable conditions +dotnet_diagnostic.SA1405.severity = none # SA1405: Debug.Assert should provide message text +dotnet_diagnostic.SA1501.severity = none # SA1501: Statement should not be on a single line +dotnet_diagnostic.SA1502.severity = suggestion # SA1502: Element should not be on a single line +dotnet_diagnostic.SA1513.severity = none # SA1513: Closing brace should be followed by blank line +dotnet_diagnostic.SA1515.severity = none # SA1515: Single-line comment should be preceded by blank line +dotnet_diagnostic.SA1611.severity = suggestion # SA1611: Element parameters should be documented +dotnet_diagnostic.SA1649.severity = suggestion # SA1649: File name should match first type name + + + + + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..edc38fa76 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto encoding=UTF-8 +*.sh text eol=lf +*.cs diff=csharp text \ No newline at end of file diff --git a/.github/.github.csproj b/.github/.github.csproj new file mode 100644 index 000000000..008099327 --- /dev/null +++ b/.github/.github.csproj @@ -0,0 +1,5 @@ + + + net6.0 + + \ No newline at end of file diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 000000000..51f96b88d --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,151 @@ +name: CI + +on: + pull_request: + push: + branches: [ 'main' ] + paths: + - '**' + - '!/docs/*' # Don't run workflow when files are only in the /docs directory + +jobs: + main: + name: StackExchange.Redis (Ubuntu) + runs-on: ubuntu-latest + env: + DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: "1" # Enable color output, even though the console output is redirected in Actions + TERM: xterm # Enable color output in GitHub Actions + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch the full history + - name: Start Redis Services (docker-compose) + working-directory: ./tests/RedisConfigs + run: docker compose -f docker-compose.yml up -d --wait + - name: Install .NET SDK + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 6.0.x + 8.0.x + 9.0.x + - name: .NET Build + run: dotnet build Build.csproj -c Release /p:CI=true + - name: StackExchange.Redis.Tests + run: dotnet test tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj -c Release --logger trx --logger GitHubActions --results-directory ./test-results/ /p:CI=true + - uses: dorny/test-reporter@v1 + continue-on-error: true + if: success() || failure() + with: + name: Test Results - Ubuntu + path: 'test-results/*.trx' + reporter: dotnet-trx + - name: .NET Lib Pack + run: dotnet pack src/StackExchange.Redis/StackExchange.Redis.csproj --no-build -c Release /p:Packing=true /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true + + windows: + name: StackExchange.Redis (Windows Server 2022) + runs-on: windows-2022 + env: + NUGET_CERT_REVOCATION_MODE: offline # Disabling signing because of massive perf hit, see https://github.com/NuGet/Home/issues/11548 + DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: "1" # Note this doesn't work yet for Windows - see https://github.com/dotnet/runtime/issues/68340 + TERM: xterm + DOCKER_BUILDKIT: 1 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch the full history + - uses: Vampire/setup-wsl@v2 + with: + distribution: Ubuntu-22.04 + - name: Install Redis + shell: wsl-bash {0} + working-directory: ./tests/RedisConfigs + run: | + apt-get update + apt-get install curl gpg lsb-release libgomp1 jq -y + curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg + chmod 644 /usr/share/keyrings/redis-archive-keyring.gpg + echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/redis.list + apt-get update + apt-get install -y redis + mkdir redis + - name: Run redis-server + shell: wsl-bash {0} + working-directory: ./tests/RedisConfigs/redis + run: | + pwd + ls . + # Run each server instance in order + redis-server ../Basic/primary-6379.conf & + redis-server ../Basic/replica-6380.conf & + redis-server ../Basic/secure-6381.conf & + redis-server ../Failover/primary-6382.conf & + redis-server ../Failover/replica-6383.conf & + redis-server ../Cluster/cluster-7000.conf --dir ../Cluster & + redis-server ../Cluster/cluster-7001.conf --dir ../Cluster & + redis-server ../Cluster/cluster-7002.conf --dir ../Cluster & + redis-server ../Cluster/cluster-7003.conf --dir ../Cluster & + redis-server ../Cluster/cluster-7004.conf --dir ../Cluster & + redis-server ../Cluster/cluster-7005.conf --dir ../Cluster & + redis-server ../Sentinel/redis-7010.conf & + redis-server ../Sentinel/redis-7011.conf & + redis-server ../Sentinel/sentinel-26379.conf --sentinel & + redis-server ../Sentinel/sentinel-26380.conf --sentinel & + redis-server ../Sentinel/sentinel-26381.conf --sentinel & + # Wait for server instances to get ready + sleep 5 + echo "Checking redis-server version with port 6379" + redis-cli -p 6379 INFO SERVER | grep redis_version || echo "Failed to get version for port 6379" + echo "Checking redis-server version with port 6380" + redis-cli -p 6380 INFO SERVER | grep redis_version || echo "Failed to get version for port 6380" + echo "Checking redis-server version with port 6381" + redis-cli -p 6381 INFO SERVER | grep redis_version || echo "Failed to get version for port 6381" + echo "Checking redis-server version with port 6382" + redis-cli -p 6382 INFO SERVER | grep redis_version || echo "Failed to get version for port 6382" + echo "Checking redis-server version with port 6383" + redis-cli -p 6383 INFO SERVER | grep redis_version || echo "Failed to get version for port 6383" + echo "Checking redis-server version with port 7000" + redis-cli -p 7000 INFO SERVER | grep redis_version || echo "Failed to get version for port 7000" + echo "Checking redis-server version with port 7001" + redis-cli -p 7001 INFO SERVER | grep redis_version || echo "Failed to get version for port 7001" + echo "Checking redis-server version with port 7002" + redis-cli -p 7002 INFO SERVER | grep redis_version || echo "Failed to get version for port 7002" + echo "Checking redis-server version with port 7003" + redis-cli -p 7003 INFO SERVER | grep redis_version || echo "Failed to get version for port 7003" + echo "Checking redis-server version with port 7004" + redis-cli -p 7004 INFO SERVER | grep redis_version || echo "Failed to get version for port 7004" + echo "Checking redis-server version with port 7005" + redis-cli -p 7005 INFO SERVER | grep redis_version || echo "Failed to get version for port 7005" + echo "Checking redis-server version with port 7010" + redis-cli -p 7010 INFO SERVER | grep redis_version || echo "Failed to get version for port 7010" + echo "Checking redis-server version with port 7011" + redis-cli -p 7011 INFO SERVER | grep redis_version || echo "Failed to get version for port 7011" + echo "Checking redis-server version with port 26379" + redis-cli -p 26379 INFO SERVER | grep redis_version || echo "Failed to get version for port 26379" + echo "Checking redis-server version with port 26380" + redis-cli -p 26380 INFO SERVER | grep redis_version || echo "Failed to get version for port 26380" + echo "Checking redis-server version with port 26381" + redis-cli -p 26381 INFO SERVER | grep redis_version || echo "Failed to get version for port 26381" + continue-on-error: true + + - name: .NET Build + run: dotnet build Build.csproj -c Release /p:CI=true + - name: StackExchange.Redis.Tests + run: dotnet test tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj -c Release --logger trx --logger GitHubActions --results-directory ./test-results/ /p:CI=true + - uses: dorny/test-reporter@v1 + continue-on-error: true + if: success() || failure() + with: + name: Tests Results - Windows Server 2022 + path: 'test-results/*.trx' + reporter: dotnet-trx + # Package and upload to MyGet only on pushes to main, not on PRs + - name: .NET Pack + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=${env:GITHUB_WORKSPACE}\.nupkgs /p:CI=true + - name: Upload to MyGet + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + run: dotnet nuget push ${env:GITHUB_WORKSPACE}\.nupkgs\*.nupkg -s https://www.myget.org/F/stackoverflow/api/v2/package -k ${{ secrets.MYGET_API_KEY }} \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..53f5701f2 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,60 @@ +name: "CodeQL" + +on: + push: + branches: [ 'main' ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ 'main' ] + schedule: + - cron: '8 9 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - if: matrix.language == 'csharp' + name: .NET Build + run: dotnet build Build.csproj -c Release /p:CI=true + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 57de1906d..c0024fb1f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ obj *.user *.nupkg nupkgs/ -packages/NuGet.CommandLine.* *.sln.docstates _ReSharper.* Mono/ @@ -14,10 +13,19 @@ Mono/ *.rdb *.aof *.orig -redis-cli.exe -Redis Configs/*.dat +RedisConfigs/*.dat RedisQFork*.dat StackExchange.Redis.*.zip +.idea/ .vs/ +.vscode/ *.lock.json -packages/ \ No newline at end of file +test-results*.xml +packages/ +StackExchange.Redis.Tests/*Config.json +t8.shakespeare.txt +launchSettings.json +*.vsp +*.diagsession +TestResults/ +BenchmarkDotNet.Artifacts/ diff --git a/.hgignore b/.hgignore deleted file mode 100644 index deead95ff..000000000 --- a/.hgignore +++ /dev/null @@ -1,20 +0,0 @@ -syntax: glob -*/bin/ -.git -*/obj/ -Tests/bin/ -Tests/obj/ -*.suo -Async/bin/ -Async/obj/ -*.user -*.nupkg -packages/NuGet.CommandLine.* -*.sln.docstates -_ReSharper.* -Mono/ -*.sln.ide -*.rdb -*.orig -redis-cli.exe -Redis Configs/*.dat \ No newline at end of file diff --git a/.nuget/packages.config b/.nuget/packages.config deleted file mode 100644 index 565aa2a30..000000000 --- a/.nuget/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 46c8bcafa..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": ".NET Core Launch (console)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceRoot}\\BasicTest\\bin\\Debug\\netcoreapp1.0\\BasicTest.dll", - "args": [], - "cwd": "${workspaceRoot}", - "externalConsole": false, - "stopAtEntry": false, - "internalConsoleOptions": "openOnSessionStart" - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach", - "processId": "${command.pickProcess}" - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 863a30fd5..000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "version": "0.1.0", - "command": "dotnet", - "isShellCommand": true, - "args": [], - "tasks": [ - { - "taskName": "build", - "args": [ - "${workspaceRoot}\\BasicTest\\BasicTest.csproj" - ], - "isBuildCommand": true, - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file diff --git a/BasicTest/BasicTest.csproj b/BasicTest/BasicTest.csproj deleted file mode 100644 index 8e6c4fd98..000000000 --- a/BasicTest/BasicTest.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - StackExchange.Redis.BasicTest .NET Core - netcoreapp1.1 - $(TargetFramework) - BasicTest - Exe - BasicTest - win7-x64 - false - - - - - - - - $(DefineConstants);CORE_CLR - - - - - - - diff --git a/BasicTest/Program.cs b/BasicTest/Program.cs deleted file mode 100644 index 567bfc1b2..000000000 --- a/BasicTest/Program.cs +++ /dev/null @@ -1,38 +0,0 @@ -using StackExchange.Redis; -using System.Reflection; -using System.Runtime.CompilerServices; -using System; - -[assembly: AssemblyVersion("1.0.0")] - -namespace BasicTest -{ - static class Program - { - static void Main() - { - using (var conn = ConnectionMultiplexer.Connect("127.0.0.1:6379")) - { - var db = conn.GetDatabase(); - - RedisKey key = Me(); - db.KeyDelete(key); - db.StringSet(key, "abc"); - - string s = (string)db.ScriptEvaluate(@" - local val = redis.call('get', KEYS[1]) - redis.call('del', KEYS[1]) - return val", new RedisKey[] { key }, flags: CommandFlags.NoScriptCache); - - Console.WriteLine(s); - Console.WriteLine(db.KeyExists(key)); - - } - } - - internal static string Me([CallerMemberName] string caller = null) - { - return caller; - } - } -} diff --git a/Build.csproj b/Build.csproj new file mode 100644 index 000000000..3e16e801c --- /dev/null +++ b/Build.csproj @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ConnectionWatcher/App.config b/ConnectionWatcher/App.config deleted file mode 100644 index 1f6eeef12..000000000 --- a/ConnectionWatcher/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/ConnectionWatcher/ConnectionWatcher.csproj b/ConnectionWatcher/ConnectionWatcher.csproj deleted file mode 100644 index b9a01e91a..000000000 --- a/ConnectionWatcher/ConnectionWatcher.csproj +++ /dev/null @@ -1,129 +0,0 @@ - - - - - Debug - AnyCPU - {6756F911-BD09-4226-B597-67871DEB8ED5} - WinExe - Properties - ConnectionWatcher - ConnectionWatcher - v4.5 - 512 - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - AnyCPU - pdbonly - true - bin\Verbose\ - TRACE - prompt - 4 - false - - - true - bin\Verbose\ - TRACE;DEBUG;VERBOSE - full - AnyCPU - false - prompt - MinimumRecommendedRules.ruleset - true - - - true - bin\Log Output\ - TRACE;DEBUG;LOGOUTPUT - full - AnyCPU - false - prompt - MinimumRecommendedRules.ruleset - true - - - bin\Mono\ - TRACE - true - pdbonly - AnyCPU - false - prompt - MinimumRecommendedRules.ruleset - true - - - - - - - - - - - - - - - - Form - - - Form1.cs - - - - - Form1.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - - - {7cec07f2-8c03-4c42-b048-738b215824c1} - StackExchange.Redis_Net45 - - - - - \ No newline at end of file diff --git a/ConnectionWatcher/Form1.Designer.cs b/ConnectionWatcher/Form1.Designer.cs deleted file mode 100644 index 0ff89af19..000000000 --- a/ConnectionWatcher/Form1.Designer.cs +++ /dev/null @@ -1,579 +0,0 @@ -namespace ConnectionWatcher -{ - partial class Form1 - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.components = new System.ComponentModel.Container(); - System.Windows.Forms.Label label1; - System.Windows.Forms.Label label2; - System.Windows.Forms.Label label3; - System.Windows.Forms.Label label4; - System.Windows.Forms.Label label5; - this.connect = new System.Windows.Forms.Button(); - this.console = new System.Windows.Forms.TextBox(); - this.endpoints = new System.Windows.Forms.ListBox(); - this.breakSocket = new System.Windows.Forms.Button(); - this.demandMaster = new System.Windows.Forms.Label(); - this.preferMaster = new System.Windows.Forms.Label(); - this.preferSlave = new System.Windows.Forms.Label(); - this.demandSlave = new System.Windows.Forms.Label(); - this.ticker = new System.Windows.Forms.Timer(this.components); - this.label6 = new System.Windows.Forms.Label(); - this.redisKey = new System.Windows.Forms.Label(); - this.allowConnect = new System.Windows.Forms.CheckBox(); - this.connectionString = new System.Windows.Forms.ComboBox(); - this.clearLog = new System.Windows.Forms.Button(); - this.deslave = new System.Windows.Forms.Button(); - this.shutdown = new System.Windows.Forms.Button(); - this.deify = new System.Windows.Forms.Button(); - this.export = new System.Windows.Forms.Button(); - this.reconfigure = new System.Windows.Forms.Button(); - this.enableLog = new System.Windows.Forms.CheckBox(); - this.disconnect = new System.Windows.Forms.Button(); - this.bulkOps = new System.Windows.Forms.GroupBox(); - this.sameKey = new System.Windows.Forms.CheckBox(); - this.bulkPerThread = new System.Windows.Forms.NumericUpDown(); - this.bulkFF = new System.Windows.Forms.Button(); - this.bulkThreads = new System.Windows.Forms.NumericUpDown(); - this.bulkBatch = new System.Windows.Forms.Button(); - this.bulkSync = new System.Windows.Forms.Button(); - this.bulkAsync = new System.Windows.Forms.Button(); - this.flush = new System.Windows.Forms.Button(); - this.clearStormLog = new System.Windows.Forms.Button(); - label1 = new System.Windows.Forms.Label(); - label2 = new System.Windows.Forms.Label(); - label3 = new System.Windows.Forms.Label(); - label4 = new System.Windows.Forms.Label(); - label5 = new System.Windows.Forms.Label(); - this.bulkOps.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.bulkPerThread)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.bulkThreads)).BeginInit(); - this.SuspendLayout(); - // - // label1 - // - label1.AutoSize = true; - label1.Location = new System.Drawing.Point(12, 18); - label1.Name = "label1"; - label1.Size = new System.Drawing.Size(91, 13); - label1.TabIndex = 2; - label1.Text = "Connection String"; - // - // label2 - // - label2.AutoSize = true; - label2.Location = new System.Drawing.Point(333, 48); - label2.Name = "label2"; - label2.Size = new System.Drawing.Size(85, 13); - label2.TabIndex = 6; - label2.Text = "Demand Master:"; - // - // label3 - // - label3.AutoSize = true; - label3.Location = new System.Drawing.Point(333, 76); - label3.Name = "label3"; - label3.Size = new System.Drawing.Size(73, 13); - label3.TabIndex = 7; - label3.Text = "Prefer Master:"; - // - // label4 - // - label4.AutoSize = true; - label4.Location = new System.Drawing.Point(333, 104); - label4.Name = "label4"; - label4.Size = new System.Drawing.Size(68, 13); - label4.TabIndex = 8; - label4.Text = "Prefer Slave:"; - // - // label5 - // - label5.AutoSize = true; - label5.Location = new System.Drawing.Point(333, 131); - label5.Name = "label5"; - label5.Size = new System.Drawing.Size(80, 13); - label5.TabIndex = 9; - label5.Text = "Demand Slave:"; - // - // connect - // - this.connect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.connect.Location = new System.Drawing.Point(808, 13); - this.connect.Name = "connect"; - this.connect.Size = new System.Drawing.Size(75, 23); - this.connect.TabIndex = 1; - this.connect.Text = "Connect"; - this.connect.UseVisualStyleBackColor = true; - this.connect.Click += new System.EventHandler(this.connect_Clicked); - // - // console - // - this.console.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.console.Location = new System.Drawing.Point(12, 261); - this.console.Multiline = true; - this.console.Name = "console"; - this.console.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.console.Size = new System.Drawing.Size(872, 312); - this.console.TabIndex = 3; - // - // endpoints - // - this.endpoints.Enabled = false; - this.endpoints.FormattingEnabled = true; - this.endpoints.Location = new System.Drawing.Point(15, 39); - this.endpoints.Name = "endpoints"; - this.endpoints.SelectionMode = System.Windows.Forms.SelectionMode.MultiExtended; - this.endpoints.Size = new System.Drawing.Size(312, 160); - this.endpoints.TabIndex = 4; - // - // breakSocket - // - this.breakSocket.Enabled = false; - this.breakSocket.Location = new System.Drawing.Point(15, 205); - this.breakSocket.Name = "breakSocket"; - this.breakSocket.Size = new System.Drawing.Size(120, 23); - this.breakSocket.TabIndex = 5; - this.breakSocket.Text = "Break Socket"; - this.breakSocket.UseVisualStyleBackColor = true; - this.breakSocket.Click += new System.EventHandler(this.breakSocket_Click); - // - // demandMaster - // - this.demandMaster.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.demandMaster.Location = new System.Drawing.Point(424, 48); - this.demandMaster.Name = "demandMaster"; - this.demandMaster.Size = new System.Drawing.Size(348, 23); - this.demandMaster.TabIndex = 10; - this.demandMaster.Text = "(timings etc)"; - // - // preferMaster - // - this.preferMaster.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.preferMaster.Location = new System.Drawing.Point(424, 76); - this.preferMaster.Name = "preferMaster"; - this.preferMaster.Size = new System.Drawing.Size(348, 23); - this.preferMaster.TabIndex = 11; - this.preferMaster.Text = "(timings etc)"; - // - // preferSlave - // - this.preferSlave.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.preferSlave.Location = new System.Drawing.Point(424, 104); - this.preferSlave.Name = "preferSlave"; - this.preferSlave.Size = new System.Drawing.Size(348, 23); - this.preferSlave.TabIndex = 12; - this.preferSlave.Text = "(timings etc)"; - // - // demandSlave - // - this.demandSlave.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.demandSlave.Location = new System.Drawing.Point(424, 131); - this.demandSlave.Name = "demandSlave"; - this.demandSlave.Size = new System.Drawing.Size(348, 23); - this.demandSlave.TabIndex = 13; - this.demandSlave.Text = "(timings etc)"; - // - // ticker - // - this.ticker.Interval = 1000; - this.ticker.Tick += new System.EventHandler(this.ticker_Tick); - // - // label6 - // - this.label6.AutoSize = true; - this.label6.Location = new System.Drawing.Point(333, 158); - this.label6.Name = "label6"; - this.label6.Size = new System.Drawing.Size(58, 13); - this.label6.TabIndex = 14; - this.label6.Text = "Redis Key:"; - // - // redisKey - // - this.redisKey.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.redisKey.Location = new System.Drawing.Point(424, 158); - this.redisKey.Name = "redisKey"; - this.redisKey.Size = new System.Drawing.Size(348, 23); - this.redisKey.TabIndex = 15; - this.redisKey.Text = "(timings etc)"; - // - // allowConnect - // - this.allowConnect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.allowConnect.AutoSize = true; - this.allowConnect.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; - this.allowConnect.Checked = true; - this.allowConnect.CheckState = System.Windows.Forms.CheckState.Checked; - this.allowConnect.Enabled = false; - this.allowConnect.Location = new System.Drawing.Point(525, 209); - this.allowConnect.Name = "allowConnect"; - this.allowConnect.Size = new System.Drawing.Size(107, 17); - this.allowConnect.TabIndex = 16; - this.allowConnect.Text = "Allow Reconnect"; - this.allowConnect.UseVisualStyleBackColor = true; - this.allowConnect.CheckedChanged += new System.EventHandler(this.allowConnect_CheckedChanged); - // - // connectionString - // - this.connectionString.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.connectionString.FormattingEnabled = true; - this.connectionString.Items.AddRange(new object[] { - "cluster:7000,cluster:7001,cluster:7002,cluster:7003,cluster:7004,cluster:7005,res" + - "olveDns=true", - ".,.:6380,resolveDns=true", - "sslredis:6379,syncTimeout=10000", - "sslredis:6380,sslHost=anyone,syncTimeout=10000"}); - this.connectionString.Location = new System.Drawing.Point(109, 15); - this.connectionString.Name = "connectionString"; - this.connectionString.Size = new System.Drawing.Size(612, 21); - this.connectionString.TabIndex = 17; - this.connectionString.Text = "localhost,localhost:6380"; - // - // clearLog - // - this.clearLog.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.clearLog.Location = new System.Drawing.Point(764, 205); - this.clearLog.Name = "clearLog"; - this.clearLog.Size = new System.Drawing.Size(120, 23); - this.clearLog.TabIndex = 18; - this.clearLog.Text = "Clear Log"; - this.clearLog.UseVisualStyleBackColor = true; - this.clearLog.Click += new System.EventHandler(this.clearLog_Click); - // - // deslave - // - this.deslave.Enabled = false; - this.deslave.Location = new System.Drawing.Point(141, 205); - this.deslave.Name = "deslave"; - this.deslave.Size = new System.Drawing.Size(120, 23); - this.deslave.TabIndex = 19; - this.deslave.Text = "Deslave"; - this.deslave.UseVisualStyleBackColor = true; - this.deslave.Click += new System.EventHandler(this.deslave_Click); - // - // shutdown - // - this.shutdown.Enabled = false; - this.shutdown.Location = new System.Drawing.Point(15, 234); - this.shutdown.Name = "shutdown"; - this.shutdown.Size = new System.Drawing.Size(120, 23); - this.shutdown.TabIndex = 20; - this.shutdown.Text = "Shutdown"; - this.shutdown.UseVisualStyleBackColor = true; - this.shutdown.Click += new System.EventHandler(this.shutdown_Click); - // - // deify - // - this.deify.Enabled = false; - this.deify.Location = new System.Drawing.Point(141, 234); - this.deify.Name = "deify"; - this.deify.Size = new System.Drawing.Size(120, 23); - this.deify.TabIndex = 21; - this.deify.Text = "DEIFY!"; - this.deify.UseVisualStyleBackColor = true; - this.deify.Click += new System.EventHandler(this.deify_Click); - // - // export - // - this.export.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.export.Enabled = false; - this.export.Location = new System.Drawing.Point(638, 205); - this.export.Name = "export"; - this.export.Size = new System.Drawing.Size(120, 23); - this.export.TabIndex = 22; - this.export.Text = "Export"; - this.export.UseVisualStyleBackColor = true; - this.export.Click += new System.EventHandler(this.export_Click); - // - // reconfigure - // - this.reconfigure.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.reconfigure.Enabled = false; - this.reconfigure.Location = new System.Drawing.Point(638, 234); - this.reconfigure.Name = "reconfigure"; - this.reconfigure.Size = new System.Drawing.Size(120, 23); - this.reconfigure.TabIndex = 23; - this.reconfigure.Text = "Reconfigure"; - this.reconfigure.UseVisualStyleBackColor = true; - this.reconfigure.Click += new System.EventHandler(this.reconfigure_Click); - // - // enableLog - // - this.enableLog.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.enableLog.AutoSize = true; - this.enableLog.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; - this.enableLog.Checked = true; - this.enableLog.CheckState = System.Windows.Forms.CheckState.Checked; - this.enableLog.Location = new System.Drawing.Point(552, 238); - this.enableLog.Name = "enableLog"; - this.enableLog.Size = new System.Drawing.Size(80, 17); - this.enableLog.TabIndex = 24; - this.enableLog.Text = "Enable Log"; - this.enableLog.UseVisualStyleBackColor = true; - // - // disconnect - // - this.disconnect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.disconnect.Enabled = false; - this.disconnect.Location = new System.Drawing.Point(727, 13); - this.disconnect.Name = "disconnect"; - this.disconnect.Size = new System.Drawing.Size(75, 23); - this.disconnect.TabIndex = 25; - this.disconnect.Text = "Disconnect"; - this.disconnect.UseVisualStyleBackColor = true; - this.disconnect.Click += new System.EventHandler(this.disconnect_Click); - // - // bulkOps - // - this.bulkOps.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.bulkOps.Controls.Add(this.sameKey); - this.bulkOps.Controls.Add(this.bulkPerThread); - this.bulkOps.Controls.Add(this.bulkFF); - this.bulkOps.Controls.Add(this.bulkThreads); - this.bulkOps.Controls.Add(this.bulkBatch); - this.bulkOps.Controls.Add(this.bulkSync); - this.bulkOps.Controls.Add(this.bulkAsync); - this.bulkOps.Enabled = false; - this.bulkOps.Location = new System.Drawing.Point(778, 48); - this.bulkOps.Name = "bulkOps"; - this.bulkOps.Size = new System.Drawing.Size(105, 151); - this.bulkOps.TabIndex = 31; - this.bulkOps.TabStop = false; - this.bulkOps.Text = "Bulk Ops"; - // - // sameKey - // - this.sameKey.AutoSize = true; - this.sameKey.Checked = true; - this.sameKey.CheckState = System.Windows.Forms.CheckState.Checked; - this.sameKey.Location = new System.Drawing.Point(6, 109); - this.sameKey.Name = "sameKey"; - this.sameKey.Size = new System.Drawing.Size(74, 17); - this.sameKey.TabIndex = 35; - this.sameKey.Text = "Same Key"; - this.sameKey.UseVisualStyleBackColor = true; - // - // bulkPerThread - // - this.bulkPerThread.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.bulkPerThread.Location = new System.Drawing.Point(52, 26); - this.bulkPerThread.Maximum = new decimal(new int[] { - 9999, - 0, - 0, - 0}); - this.bulkPerThread.Minimum = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.bulkPerThread.Name = "bulkPerThread"; - this.bulkPerThread.Size = new System.Drawing.Size(47, 20); - this.bulkPerThread.TabIndex = 34; - this.bulkPerThread.Value = new decimal(new int[] { - 1000, - 0, - 0, - 0}); - // - // bulkFF - // - this.bulkFF.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.bulkFF.Location = new System.Drawing.Point(6, 81); - this.bulkFF.Name = "bulkFF"; - this.bulkFF.Size = new System.Drawing.Size(47, 23); - this.bulkFF.TabIndex = 33; - this.bulkFF.Text = "F+F"; - this.bulkFF.UseVisualStyleBackColor = true; - this.bulkFF.Click += new System.EventHandler(this.bulkFF_Click); - // - // bulkThreads - // - this.bulkThreads.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.bulkThreads.Location = new System.Drawing.Point(6, 26); - this.bulkThreads.Maximum = new decimal(new int[] { - 50, - 0, - 0, - 0}); - this.bulkThreads.Minimum = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.bulkThreads.Name = "bulkThreads"; - this.bulkThreads.Size = new System.Drawing.Size(47, 20); - this.bulkThreads.TabIndex = 32; - this.bulkThreads.Value = new decimal(new int[] { - 10, - 0, - 0, - 0}); - // - // bulkBatch - // - this.bulkBatch.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.bulkBatch.Location = new System.Drawing.Point(52, 81); - this.bulkBatch.Name = "bulkBatch"; - this.bulkBatch.Size = new System.Drawing.Size(47, 23); - this.bulkBatch.TabIndex = 31; - this.bulkBatch.Text = "Batch"; - this.bulkBatch.UseVisualStyleBackColor = true; - this.bulkBatch.Click += new System.EventHandler(this.bulkBatch_Click); - // - // bulkSync - // - this.bulkSync.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.bulkSync.Location = new System.Drawing.Point(52, 52); - this.bulkSync.Name = "bulkSync"; - this.bulkSync.Size = new System.Drawing.Size(47, 23); - this.bulkSync.TabIndex = 30; - this.bulkSync.Text = "Sync"; - this.bulkSync.UseVisualStyleBackColor = true; - this.bulkSync.Click += new System.EventHandler(this.bulkSync_Click); - // - // bulkAsync - // - this.bulkAsync.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.bulkAsync.Location = new System.Drawing.Point(6, 52); - this.bulkAsync.Name = "bulkAsync"; - this.bulkAsync.Size = new System.Drawing.Size(47, 23); - this.bulkAsync.TabIndex = 29; - this.bulkAsync.Text = "Async"; - this.bulkAsync.UseVisualStyleBackColor = true; - this.bulkAsync.Click += new System.EventHandler(this.bulkAsync_Click); - // - // flush - // - this.flush.Enabled = false; - this.flush.Location = new System.Drawing.Point(267, 205); - this.flush.Name = "flush"; - this.flush.Size = new System.Drawing.Size(120, 23); - this.flush.TabIndex = 32; - this.flush.Text = "Flush"; - this.flush.UseVisualStyleBackColor = true; - this.flush.Click += new System.EventHandler(this.flush_Click); - // - // clearStormLog - // - this.clearStormLog.Enabled = false; - this.clearStormLog.Location = new System.Drawing.Point(267, 234); - this.clearStormLog.Name = "clearStormLog"; - this.clearStormLog.Size = new System.Drawing.Size(120, 23); - this.clearStormLog.TabIndex = 33; - this.clearStormLog.Text = "Clear Storm Log"; - this.clearStormLog.UseVisualStyleBackColor = true; - this.clearStormLog.Click += new System.EventHandler(this.clearStormLog_Click); - // - // Form1 - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(896, 585); - this.Controls.Add(this.clearStormLog); - this.Controls.Add(this.flush); - this.Controls.Add(this.bulkOps); - this.Controls.Add(this.disconnect); - this.Controls.Add(this.enableLog); - this.Controls.Add(this.reconfigure); - this.Controls.Add(this.export); - this.Controls.Add(this.deify); - this.Controls.Add(this.shutdown); - this.Controls.Add(this.deslave); - this.Controls.Add(this.clearLog); - this.Controls.Add(this.connectionString); - this.Controls.Add(this.allowConnect); - this.Controls.Add(this.redisKey); - this.Controls.Add(this.label6); - this.Controls.Add(this.demandSlave); - this.Controls.Add(this.preferSlave); - this.Controls.Add(this.preferMaster); - this.Controls.Add(this.demandMaster); - this.Controls.Add(label5); - this.Controls.Add(label4); - this.Controls.Add(label3); - this.Controls.Add(label2); - this.Controls.Add(this.breakSocket); - this.Controls.Add(this.endpoints); - this.Controls.Add(this.console); - this.Controls.Add(label1); - this.Controls.Add(this.connect); - this.MinimumSize = new System.Drawing.Size(540, 430); - this.Name = "Form1"; - this.Text = "Connection Watcher"; - this.bulkOps.ResumeLayout(false); - this.bulkOps.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.bulkPerThread)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.bulkThreads)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - private System.Windows.Forms.Button connect; - private System.Windows.Forms.TextBox console; - private System.Windows.Forms.ListBox endpoints; - private System.Windows.Forms.Button breakSocket; - private System.Windows.Forms.Label demandMaster; - private System.Windows.Forms.Label preferMaster; - private System.Windows.Forms.Label preferSlave; - private System.Windows.Forms.Label demandSlave; - private System.Windows.Forms.Timer ticker; - private System.Windows.Forms.Label label6; - private System.Windows.Forms.Label redisKey; - private System.Windows.Forms.CheckBox allowConnect; - private System.Windows.Forms.ComboBox connectionString; - private System.Windows.Forms.Button clearLog; - private System.Windows.Forms.Button deslave; - private System.Windows.Forms.Button shutdown; - private System.Windows.Forms.Button deify; - private System.Windows.Forms.Button export; - private System.Windows.Forms.Button reconfigure; - private System.Windows.Forms.CheckBox enableLog; - private System.Windows.Forms.Button disconnect; - private System.Windows.Forms.NumericUpDown bulkThreads; - private System.Windows.Forms.Button bulkBatch; - private System.Windows.Forms.Button bulkSync; - private System.Windows.Forms.Button bulkAsync; - private System.Windows.Forms.GroupBox bulkOps; - private System.Windows.Forms.Button bulkFF; - private System.Windows.Forms.NumericUpDown bulkPerThread; - private System.Windows.Forms.CheckBox sameKey; - private System.Windows.Forms.Button flush; - private System.Windows.Forms.Button clearStormLog; - } -} - diff --git a/ConnectionWatcher/Form1.cs b/ConnectionWatcher/Form1.cs deleted file mode 100644 index 33ad0e848..000000000 --- a/ConnectionWatcher/Form1.cs +++ /dev/null @@ -1,621 +0,0 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; -using StackExchange.Redis; - -namespace ConnectionWatcher -{ - public partial class Form1 : Form - { - public Form1() - { - foreach(var file in Directory.GetFiles(Environment.CurrentDirectory, "Interactive_*.txt")) - { - File.Delete(file); - } - foreach (var file in Directory.GetFiles(Environment.CurrentDirectory, "Subscriber_*.txt")) - { - File.Delete(file); - } - InitializeComponent(); - - demandMaster.Text = preferMaster.Text = demandSlave.Text = preferSlave.Text = redisKey.Text = ""; - -#if !DEBUG - breakSocket.Text = allowConnect.Text = "#if DEBUG"; -#endif -#if LOGOUTPUT - ConnectionMultiplexer.EchoPath = Environment.CurrentDirectory; -#endif - } - - protected override void OnClosing(CancelEventArgs e) - { - SetEnabled(false); - } - private void disconnect_Click(object sender, EventArgs e) - { - SetEnabled(false); - } - private void SetEnabled(bool running) - { - connect.Enabled = connectionString.Enabled = !running; - shutdown.Enabled = endpoints.Enabled = deslave.Enabled = deify.Enabled = export.Enabled = reconfigure.Enabled = - disconnect.Enabled = bulkOps.Enabled = flush.Enabled = clearStormLog.Enabled = running; - -#if DEBUG - breakSocket.Enabled = allowConnect.Enabled = running; -#endif - if (running) ticker.Start(); - else ticker.Stop(); - - if (muxer != null) - { - muxer.Dispose(); - muxer = null; - } - if(running) - { - console.Text = ""; - var options = ConfigurationOptions.Parse(connectionString.Text); - options.AllowAdmin = true; - options.AbortOnConnectFail = false; - options.CertificateValidation += (sender, cert, chain, errors) => - { - Log("cert issued to: " + cert.Subject); - return true; // fingers in ears, pretend we don't know this is wrong - }; - - using (var logger = new StringWriter()) - { - muxer = ConnectionMultiplexer.Connect(options, logger); - Log(logger.ToString()); - } - endpoints.Items.Clear(); - endpoints.Items.AddRange(Array.ConvertAll( - muxer.GetEndPoints(), ep => new EndPointPair(muxer.GetServer(ep)))); - - muxer.ConnectionFailed += Muxer_ConnectionFailed; - muxer.ConnectionRestored += Muxer_ConnectionRestored; - muxer.ErrorMessage += Muxer_ErrorMessage; - muxer.ConfigurationChanged += Muxer_ConfigurationChanged; - } - } - private void connect_Clicked(object sender, EventArgs e) - { - SetEnabled(true); - } - - private void Muxer_ConfigurationChanged(object sender, EndPointEventArgs e) - { - Log("Configuration changed: " + e.EndPoint); - } - - private void Muxer_ErrorMessage(object sender, RedisErrorEventArgs e) - { - Log(e.EndPoint + ": " + e.Message); - } - - private void Muxer_ConnectionRestored(object sender, ConnectionFailedEventArgs e) - { - Log("Endpoint restored: " + e.EndPoint); - } - private void Log(string message) - { - if(InvokeRequired) - { - BeginInvoke((MethodInvoker)delegate - { - if (enableLog.Checked) - console.Text = message + Environment.NewLine + console.Text; - }); - } else - { - if(enableLog.Checked) - console.Text = message + Environment.NewLine + console.Text; - } - } - - class EndPointPair - { - public EndPointPair(IServer server) - { - this.server = server; - } - private readonly IServer server; - public EndPoint EndPoint { get { return server == null ? null : server.EndPoint; } } - private string state; - - public override string ToString() - { - try - { - string spacer; - switch (((uint)Thread.VolatileRead(ref loop)) % 4) - { - case 0: spacer = @" - "; break; - case 1: spacer = @" \ "; break; - case 2: spacer = @" | "; break; - case 3: spacer = @" / "; break; - default: spacer = " ! "; break; - } - return (server.IsSlave ? "S " : "M ") + EndPointCollection.ToString(EndPoint) + spacer + OpCount + ": " + state; - } - catch (Exception ex) - { - return ex.Message; - } - - } - public long OpCount { get;set; } - int loop; - internal void SetState(string msg) - { - state = msg; - Interlocked.Increment(ref loop); - Interlocked.Exchange(ref concern, 0); - } - int concern; - internal bool Worried() - { - return Interlocked.Increment(ref concern) >= 5; - } - } - private void Muxer_ConnectionFailed(object sender, ConnectionFailedEventArgs e) - { - Log("Endpoint failed: " + e.EndPoint + ", " + e.FailureType + (e.Exception == null ? "" : (", " + e.Exception.Message))); - } - - private void ticker_Tick(object sender, EventArgs e) - { - RefreshListBox(); - if (muxer == null) return; - if(endpoints.SelectedIndices.Count == 1) - { - var ep = ((EndPointPair)endpoints.SelectedItem).EndPoint; - var server = muxer.GetServer(ep); - Text = server.ServerType + " " + server.Version + " " + (server.IsSlave ? "slave" : "master") + "; " + server.GetCounters().ToString(); - } - foreach (var pair in endpoints.Items.OfType()) - { - - if(pair.Worried()) - { - Log("No response from " + pair.EndPoint); - } - var server = muxer.GetServer(pair.EndPoint, pair); - - var q = server.GetCounters(); - pair.OpCount = q.Interactive.OperationCount; - if (q.TotalOutstanding > 5) - { - Log(q.ToString()); -//#if DEBUG -// if(q.Interactive.PendingUnsentItems != 0 || q.Subscription.PendingUnsentItems != 0) -// { -// Log(((IRedisServerDebug)server).ListPending(100)); -// } -//#endif - } - var ping = server.PingAsync().ContinueWith(UpdateEndPoint); - } - - - string s = Guid.NewGuid().ToString(); - redisKey.Text = s; - RedisKey key = s; - var db = muxer.GetDatabase(asyncState: Stopwatch.StartNew()); - db.IdentifyEndpointAsync(key, CommandFlags.DemandMaster).ContinueWith(DemandMaster); - db.IdentifyEndpointAsync(key, CommandFlags.PreferMaster).ContinueWith(PreferMaster); - db.IdentifyEndpointAsync(key, CommandFlags.PreferSlave).ContinueWith(PreferSlave); - db.IdentifyEndpointAsync(key, CommandFlags.DemandSlave).ContinueWith(DemandSlave); - - // ThreadPool.QueueUserWorkItem(AccessSync); - - } - private void AccessSync(object state) - { - var ep = muxer.GetEndPoints(); - for (int i = 0; i < ep.Length; i++) - { - try - { muxer.GetServer(ep[i]).Ping(); } - catch (Exception ex) - { - Log(ep[i] + ":" + ex.Message); - } - } - } - - private void UpdateEndPoint(Task task) - { - - string msg = ExtractMessage(task); - var pair = (EndPointPair)task.AsyncState; - if (pair != null) - { - BeginInvoke((MethodInvoker)delegate - { - pair.SetState(msg); - RefreshListBox(); - }); - } - - } - - void RefreshListBox() - { - if (InvokeRequired) - { - BeginInvoke((MethodInvoker)RefreshListBox); - } - else - { - // hacky "redraw your damned text", via http://stackoverflow.com/a/4631419/23354 - // unfortunately, while this works, it breaks the selected items - bool[] selected = new bool[endpoints.Items.Count]; - for (int i = 0; i < selected.Length; i++) - { - selected[i] = endpoints.GetSelected(i); - } - typeof(ListBox).InvokeMember("RefreshItems", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null, endpoints, new object[] { }); - for (int i = 0; i < selected.Length; i++) - { - endpoints.SetSelected(i, selected[i]); - } - } - } - - static string ExtractMessage(Task task) - { - if (task == null) return ""; - try - { - var status = task.Status; - switch (status) - { - case TaskStatus.RanToCompletion: - object result = task.Result; - if(result is TimeSpan) - { - return ((TimeSpan)result).TotalMilliseconds.ToString("###,##0.00ms"); - } - if(result is EndPoint) - { - return EndPointCollection.ToString((EndPoint)result); - } - return Convert.ToString(result); - case TaskStatus.Faulted: - return string.Join(", ", task.Exception.InnerExceptions.Select(x => x.Message)); - default: - task.ContinueWith(x => - { - try - { // mark observed - GC.KeepAlive(x.Exception); - } - catch - { } - }, continuationOptions: TaskContinuationOptions.OnlyOnFaulted); - return status.ToString(); - } - } - catch (Exception ex) - { - return ex.Message; - } - - } - - private void DemandMaster(Task task) - { - Write(demandMaster, task); - } - private void DemandSlave(Task task) - { - Write(demandSlave, task); - } - private void PreferMaster(Task task) - { - Write(preferMaster, task); - } - private void PreferSlave(Task task) - { - Write(preferSlave, task); - } - - private void Write(Label output, Task task) - { - var watch = (Stopwatch)task.AsyncState; - var elapsed = watch.Elapsed; - output.BeginInvoke((MethodInvoker)delegate - { - string msg = ExtractMessage(task); - output.Text = elapsed.TotalMilliseconds.ToString("###,##0.00ms") + ": " + msg; - }); - } - - ConnectionMultiplexer muxer; - - private void breakSocket_Click(object sender, EventArgs e) - { -#if DEBUG - foreach (var pair in endpoints.SelectedItems.OfType()) - { - try - { - muxer.GetServer(pair.EndPoint).SimulateConnectionFailure(); - } catch(Exception ex) - { - Log(ex.Message); - } - } -#endif - } - - private void allowConnect_CheckedChanged(object sender, EventArgs e) - { -#if DEBUG - muxer.AllowConnect = allowConnect.Checked; -#endif - } - - private void clearLog_Click(object sender, EventArgs e) - { - console.Text = ""; - } - - - - private void deslave_Click(object sender, EventArgs e) - { - foreach (var pair in endpoints.SelectedItems.OfType()) - { - var sw = new StringWriter(); - try - { - muxer.GetServer(pair.EndPoint).MakeMaster(ReplicationChangeOptions.None, sw); - } catch(Exception ex) - { - Log(ex.Message); - } - Log(sw.ToString()); - } - } - - private void shutdown_Click(object sender, EventArgs e) - { - foreach (var pair in endpoints.SelectedItems.OfType()) - { - try - { - muxer.GetServer(pair.EndPoint).Shutdown(); - } catch(Exception ex) - { - Log(ex.Message); - } - } - } - private void flush_Click(object sender, EventArgs e) - { - foreach (var pair in endpoints.SelectedItems.OfType()) - { - try - { - muxer.GetServer(pair.EndPoint).FlushDatabase(); - } - catch (Exception ex) - { - Log(ex.Message); - } - } - } - private void clearStormLog_Click(object sender, EventArgs e) - { - try - { - muxer.ResetStormLog(); - } - catch (Exception ex) - { - Log(ex.Message); - } - } - - private void deify_Click(object sender, EventArgs e) - { - var items = endpoints.SelectedItems.OfType().ToList(); - if (items.Count != 1) return; - - var sw = new StringWriter(); - try - { - muxer.GetServer(items[0].EndPoint).MakeMaster(ReplicationChangeOptions.SetTiebreaker | ReplicationChangeOptions.Broadcast | ReplicationChangeOptions.EnslaveSubordinates, sw); - } catch(Exception ex) - { - Log(ex.Message); - } - Log(sw.ToString()); - } - - private void export_Click(object sender, EventArgs e) - { - try - { - using (var dlg = new SaveFileDialog()) - { - dlg.Filter = "Zip Files | *.zip"; - dlg.DefaultExt = "zip"; - if (dlg.ShowDialog(this) == DialogResult.OK) - { - string path = dlg.FileName; - if(!string.IsNullOrEmpty(path)) - { - using(var file = File.Create(path)) - { - muxer.ExportConfiguration(file); - } - } - } - } - } - catch (Exception ex) - { - Log(ex.Message); - } - - } - - private void reconfigure_Click(object sender, EventArgs e) - { - using(var writer = new StringWriter()) - { - try - { - muxer.Configure(writer); - } catch(Exception ex) - { - Log(ex.Message); - } - Log(writer.ToString()); - } - } - - private void bulkSync_Click(object sender, EventArgs e) - { - RunConcurrent(0.1M, (db, count, key) => { - while(count-- > 0) db.StringIncrement(key()); - return null; - }, "Bulk sync"); - } - - private void bulkFF_Click(object sender, EventArgs e) - { - RunConcurrent(1, (db, count, key) => { - while (count-- > 0) db.StringIncrement(key(), flags: CommandFlags.FireAndForget); - return null; - }, "Bulk F+F"); - } - private void bulkAsync_Click(object sender, EventArgs e) - { - RunConcurrent(1, (db, count, key) => { - while (count-- > 1) db.StringIncrementAsync(key(), flags: CommandFlags.FireAndForget); - return db.StringIncrementAsync(key()); - }, "Bulk async"); - } - - private void bulkBatch_Click(object sender, EventArgs e) - { - RunConcurrent(1, (db, count, key) => - { - var batch = db.CreateBatch(); - while (count-- > 1) batch.StringIncrementAsync(key(), flags: CommandFlags.FireAndForget); - Task last = db.StringIncrementAsync(key()); - batch.Execute(); - return last; - }, "Bulk batch"); - } - - private void RunConcurrent(decimal factor, Func, Task> work, [CallerMemberName] string caller = null, Action> whenDone = null, int timeout = 10000) - { - int threads = (int)bulkThreads.Value; - int perThread = (int)(bulkPerThread.Value * factor); - ThreadPool.QueueUserWorkItem(delegate - { - Func keyFunc; - if(sameKey.Checked) - { - RedisKey key = Guid.NewGuid().ToString(); - keyFunc = () => key; - } else - { - keyFunc = () => Guid.NewGuid().ToString(); - } - - Task last = null; - var db = muxer.GetDatabase(); - db.KeyDelete(keyFunc(), CommandFlags.FireAndForget); - try - { - if (work == null) return; - if (threads < 1) return; - Stopwatch watch = null; - ManualResetEvent allDone = new ManualResetEvent(false); - object token = new object(); - int active = 0; - ThreadStart callback = delegate - { - lock (token) - { - int nowActive = Interlocked.Increment(ref active); - if (nowActive == threads) - { - watch = Stopwatch.StartNew(); - Monitor.PulseAll(token); - } - else - { - Monitor.Wait(token); - } - } - var result = work(db, perThread, keyFunc); - if (result != null) Interlocked.Exchange(ref last, result); - if (Interlocked.Decrement(ref active) == 0) - { - allDone.Set(); - } - }; - - Thread[] threadArr = new Thread[threads]; - for (int i = 0; i < threads; i++) - { - var thd = new Thread(callback); - thd.Name = caller; - threadArr[i] = thd; - thd.Start(); - } - if (allDone.WaitOne(timeout)) - { - if (whenDone != null) whenDone(keyFunc); - var result = db.StringGet(keyFunc()); - watch.Stop(); - var finalTask = Interlocked.Exchange(ref last, null); - if (finalTask != null) Log("Last task is: " + finalTask.Status.ToString()); - Log(string.Format("{0}, {1} per-thread on {2} threads: {3:###,###,##0.##}ms, {4:###,###,##0}ops/s (result: {5})", - caller, perThread, threads, - watch.Elapsed.TotalMilliseconds, - (int)((perThread * threads) / watch.Elapsed.TotalSeconds), - result)); - } - else - { - for (int i = 0; i < threads; i++) - { - var thd = threadArr[i]; - if (thd.IsAlive) thd.Abort(); - } - Log(string.Format("{0} timed out", caller)); - } - } - catch (Exception ex) - { - Log(string.Format("{0} failed: {1}", caller, ex.Message)); - } - finally - { - db.KeyDelete(keyFunc(), CommandFlags.FireAndForget); - } - }); - } - - - } -} diff --git a/ConnectionWatcher/Form1.resx b/ConnectionWatcher/Form1.resx deleted file mode 100644 index 70a64f64c..000000000 --- a/ConnectionWatcher/Form1.resx +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - False - - - False - - - False - - - False - - - False - - - 17, 17 - - \ No newline at end of file diff --git a/ConnectionWatcher/Program.cs b/ConnectionWatcher/Program.cs deleted file mode 100644 index 28a135500..000000000 --- a/ConnectionWatcher/Program.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Windows.Forms; - -namespace ConnectionWatcher -{ - static class Program - { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new Form1()); - } - } -} diff --git a/ConnectionWatcher/Properties/AssemblyInfo.cs b/ConnectionWatcher/Properties/AssemblyInfo.cs deleted file mode 100644 index 6f83a467e..000000000 --- a/ConnectionWatcher/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ConnectionWatcher")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ConnectionWatcher")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f494c35a-e665-4f46-9906-dbb873e00b51")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ConnectionWatcher/Properties/Resources.Designer.cs b/ConnectionWatcher/Properties/Resources.Designer.cs deleted file mode 100644 index 0981f11b5..000000000 --- a/ConnectionWatcher/Properties/Resources.Designer.cs +++ /dev/null @@ -1,71 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.34011 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace ConnectionWatcher.Properties -{ - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ConnectionWatcher.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - } -} diff --git a/ConnectionWatcher/Properties/Resources.resx b/ConnectionWatcher/Properties/Resources.resx deleted file mode 100644 index af7dbebba..000000000 --- a/ConnectionWatcher/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/ConnectionWatcher/Properties/Settings.Designer.cs b/ConnectionWatcher/Properties/Settings.Designer.cs deleted file mode 100644 index 9c34a242b..000000000 --- a/ConnectionWatcher/Properties/Settings.Designer.cs +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.34011 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace ConnectionWatcher.Properties -{ - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { - return defaultInstance; - } - } - } -} diff --git a/ConnectionWatcher/Properties/Settings.settings b/ConnectionWatcher/Properties/Settings.settings deleted file mode 100644 index 39645652a..000000000 --- a/ConnectionWatcher/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..42de5875c --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,45 @@ + + + 2.0.0 + 2014 - $([System.DateTime]::Now.Year) Stack Exchange, Inc. + true + $(MSBuildThisFileDirectory)StackExchange.Redis.snk + $(AssemblyName) + strict + Stack Exchange, Inc.; Marc Gravell; Nick Craver + true + $(MSBuildThisFileDirectory)Shared.ruleset + NETSDK1069 + $(NoWarn);NU5105;NU1507;SER001 + https://stackexchange.github.io/StackExchange.Redis/ReleaseNotes + https://stackexchange.github.io/StackExchange.Redis/ + MIT + + 13 + git + https://github.com/StackExchange/StackExchange.Redis/ + + true + embedded + en-US + false + true + false + true + 00240000048000009400000006020000002400005253413100040000010001007791a689e9d8950b44a9a8886baad2ea180e7a8a854f158c9b98345ca5009cdd2362c84f368f1c3658c132b3c0f74e44ff16aeb2e5b353b6e0fe02f923a050470caeac2bde47a2238a9c7125ed7dab14f486a5a64558df96640933b9f2b6db188fc4a820f96dce963b662fa8864adbff38e5b4542343f162ecdc6dad16912fff + + + true + true + true + + + + + + + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 000000000..687e19684 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 000000000..df8c078a3 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Directory.build.props b/Directory.build.props deleted file mode 100644 index 2742ab670..000000000 --- a/Directory.build.props +++ /dev/null @@ -1,44 +0,0 @@ - - - 1.2.6 - - 2017 Stack Exchange, Inc. - true - true - ../StackExchange.Redis.snk - $(AssemblyName) - Stack Exchange, Inc.; marc.gravell - true - - https://stackexchange.github.io/StackExchange.Redis/ReleaseNotes - https://github.com/StackExchange/StackExchange.Redis/ - https://raw.github.com/StackExchange/StackExchange.Redis/master/LICENSE - - git - https://github.com/StackExchange/StackExchange.Redis/ - - true - embedded - en-US - false - - net45;net46;netstandard1.5 - 4.3.0 - - - - - true - false - - - - - - - - - - - - \ No newline at end of file diff --git a/LICENSE b/LICENSE index e32afb858..db4620c99 100644 --- a/LICENSE +++ b/LICENSE @@ -24,11 +24,11 @@ SOFTWARE. Third Party Licenses: -The Redis project (http://redis.io/) is independent of this client library, and +The Redis project (https://redis.io/) is independent of this client library, and is licensed separately under the three clause BSD license. The full license -information can be viewed here: http://redis.io/topics/license +information can be viewed here: https://redis.io/topics/license -This tool makes use of the "redis-doc" library from http://redis.io/documentation +This tool makes use of the "redis-doc" library from https://redis.io/documentation in the intellisense comments, which is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license; full details are available here: @@ -43,5 +43,5 @@ This tool is not used in the release binaries. The development solution uses the BookSleeve package from nuget (https://code.google.com/p/booksleeve/) by Marc Gravell. This is licensed under the Apache 2.0 license; full details are available here: -http://www.apache.org/licenses/LICENSE-2.0 +https://www.apache.org/licenses/LICENSE-2.0 This tool is not used in the release binaries. \ No newline at end of file diff --git a/MigratedBookSleeveTestSuite/App.config b/MigratedBookSleeveTestSuite/App.config deleted file mode 100644 index 8e1564635..000000000 --- a/MigratedBookSleeveTestSuite/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/MigratedBookSleeveTestSuite/Batches.cs b/MigratedBookSleeveTestSuite/Batches.cs deleted file mode 100644 index 93a81043a..000000000 --- a/MigratedBookSleeveTestSuite/Batches.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace Tests -{ - [TestFixture] - public class Batches - { - [Test] - public void TestBatchNotSent() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(0); - conn.KeyDeleteAsync("batch"); - conn.StringSetAsync("batch", "batch-not-sent"); - var tasks = new List(); - var batch = conn.CreateBatch(); - - tasks.Add(batch.KeyDeleteAsync("batch")); - tasks.Add(batch.SetAddAsync("batch", "a")); - tasks.Add(batch.SetAddAsync("batch", "b")); - tasks.Add(batch.SetAddAsync("batch", "c")); - - Assert.AreEqual("batch-not-sent", (string)conn.StringGet("batch")); - } - } - - [Test] - public void TestBatchSent() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(0); - conn.KeyDeleteAsync("batch"); - conn.StringSetAsync("batch", "batch-sent"); - var tasks = new List(); - var batch = conn.CreateBatch(); - tasks.Add(batch.KeyDeleteAsync("batch")); - tasks.Add(batch.SetAddAsync("batch", "a")); - tasks.Add(batch.SetAddAsync("batch", "b")); - tasks.Add(batch.SetAddAsync("batch", "c")); - batch.Execute(); - - var result = conn.SetMembersAsync("batch"); - tasks.Add(result); - Task.WhenAll(tasks.ToArray()); - - var arr = result.Result; - Array.Sort(arr, (x, y) => string.Compare(x, y)); - Assert.AreEqual(3, arr.Length); - Assert.AreEqual("a", (string)arr[0]); - Assert.AreEqual("b", (string)arr[1]); - Assert.AreEqual("c", (string)arr[2]); - } - } - } -} diff --git a/MigratedBookSleeveTestSuite/Config.cs b/MigratedBookSleeveTestSuite/Config.cs deleted file mode 100644 index ca0cdeeb1..000000000 --- a/MigratedBookSleeveTestSuite/Config.cs +++ /dev/null @@ -1,289 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Security.Authentication; -using System.Threading.Tasks; -using NUnit.Framework; -using StackExchange.Redis; - -namespace Tests -{ - [TestFixture(Description = "Validates that the test environment is configured and responding")] - public class Config - { - public static string CreateUniqueName() - { - return Guid.NewGuid().ToString("N"); - } - - internal static IServer GetServer(ConnectionMultiplexer conn) - { - return conn.GetServer(conn.GetEndPoints()[0]); - } - - static readonly SocketManager socketManager = new SocketManager(); - static Config() - { - TaskScheduler.UnobservedTaskException += (sender, args) => - { - Trace.WriteLine(args.Exception, "UnobservedTaskException"); - args.SetObserved(); - }; - } - - public const string LocalHost = "127.0.0.1"; //"192.168.0.10"; //"127.0.0.1"; - public const string RemoteHost = "ubuntu"; - - const int unsecuredPort = 6379, securedPort = 6381, - clusterPort0 = 7000, clusterPort1 = 7001, clusterPort2 = 7002; - - -#if CLUSTER - internal static RedisCluster GetCluster(TextWriter log = null) - { - string clusterConfiguration = - RemoteHost + ":" + clusterPort0 + "," + - RemoteHost + ":" + clusterPort1 + "," + - RemoteHost + ":" + clusterPort2; - return RedisCluster.Connect(clusterConfiguration, log); - } -#endif - - //const int unsecuredPort = 6380, securedPort = 6381; - - internal static ConnectionMultiplexer GetRemoteConnection(bool open = true, bool allowAdmin = false, bool waitForOpen = false, int syncTimeout = 5000, int ioTimeout = 5000) - { - return GetConnection(RemoteHost, unsecuredPort, open, allowAdmin, waitForOpen, syncTimeout, ioTimeout); - } - private static ConnectionMultiplexer GetConnection(string host, int port, bool open = true, bool allowAdmin = false, bool waitForOpen = false, int syncTimeout = 5000, int ioTimeout = 5000) - { - var options = new ConfigurationOptions - { - EndPoints = { { host, port } }, - AllowAdmin = allowAdmin, - SyncTimeout = syncTimeout, - SocketManager = socketManager - }; - var conn = ConnectionMultiplexer.Connect(options); - conn.InternalError += (s, args) => - { - Trace.WriteLine(args.Exception.Message, args.Origin); - }; - if (open) - { - if (waitForOpen) conn.GetDatabase().Ping(); - } - return conn; - } - internal static ConnectionMultiplexer GetUnsecuredConnection(bool open = true, bool allowAdmin = false, bool waitForOpen = false, int syncTimeout = 5000, int ioTimeout = 5000) - { - return GetConnection(LocalHost, unsecuredPort, open, allowAdmin, waitForOpen, syncTimeout, ioTimeout); - } - - internal static ConnectionMultiplexer GetSecuredConnection(bool open = true) - { - var options = new ConfigurationOptions - { - EndPoints = { { LocalHost, securedPort } }, - Password = "changeme", - SyncTimeout = 6000, - SocketManager = socketManager - }; - var conn = ConnectionMultiplexer.Connect(options); - conn.InternalError += (s, args) => - { - Trace.WriteLine(args.Exception.Message, args.Origin); - }; - return conn; - } - - internal static RedisFeatures GetFeatures(ConnectionMultiplexer muxer) - { - return GetServer(muxer).Features; - } - - [Test] - public void CanOpenUnsecuredConnection() - { - using (var conn = GetUnsecuredConnection(false)) - { - var server = GetServer(conn); - server.Ping(); - } - } - - [Test] - public void CanOpenSecuredConnection() - { - using (var conn = GetSecuredConnection(false)) - { - var server = GetServer(conn); - server.Ping(); - } - } - - [Test] - public void CanNotOpenNonsenseConnection_IP() - { - Assert.Throws(() => - { - var log = new StringWriter(); - try { - using (var conn = ConnectionMultiplexer.Connect(Config.LocalHost + ":6500")) { } - } - finally { - Console.WriteLine(log); - } - }); - } - - [Test] - public void CanNotOpenNonsenseConnection_DNS() - { - Assert.Throws(() => - { - var log = new StringWriter(); - try - { - using (var conn = ConnectionMultiplexer.Connect("doesnot.exist.ds.aasd981230d.com:6500", log)) { } - } - finally - { - Console.WriteLine(log); - } - }); - } - - [Test] - public void CreateDisconnectedNonsenseConnection_IP() - { - var log = new StringWriter(); - try - { - using (var conn = ConnectionMultiplexer.Connect(Config.LocalHost + ":6500,abortConnect=false")) { - Assert.IsFalse(conn.GetServer(conn.GetEndPoints().Single()).IsConnected); - Assert.IsFalse(conn.GetDatabase().IsConnected(default(RedisKey))); - } - } - finally - { - Console.WriteLine(log); - } - } - [Test] - public void CreateDisconnectedNonsenseConnection_DNS() - { - var log = new StringWriter(); - try - { - using (var conn = ConnectionMultiplexer.Connect("doesnot.exist.ds.aasd981230d.com:6500,abortConnect=false", log)) { - Assert.IsFalse(conn.GetServer(conn.GetEndPoints().Single()).IsConnected); - Assert.IsFalse(conn.GetDatabase().IsConnected(default(RedisKey))); - } - } - finally - { - Console.WriteLine(log); - } - } - - [Test] - public void SslProtocols_SingleValue() - { - var log = new StringWriter(); - var options = ConfigurationOptions.Parse("myhost,sslProtocols=Tls11"); - Assert.AreEqual(SslProtocols.Tls11, options.SslProtocols.Value); - } - - [Test] - public void SslProtocols_MultipleValues() - { - var log = new StringWriter(); - var options = ConfigurationOptions.Parse("myhost,sslProtocols=Tls11|Tls12"); - Assert.AreEqual(SslProtocols.Tls11|SslProtocols.Tls12, options.SslProtocols.Value); - } - - [Test] - public void SslProtocols_UsingIntegerValue() - { - var log = new StringWriter(); - - // The below scenario is for cases where the *targeted* - // .NET framework version (e.g. .NET 4.0) doesn't define an enum value (e.g. Tls11) - // but the OS has been patched with support - int integerValue = (int)(SslProtocols.Tls11 | SslProtocols.Tls12); - var options = ConfigurationOptions.Parse("myhost,sslProtocols=" + integerValue); - Assert.AreEqual(SslProtocols.Tls11 | SslProtocols.Tls12, options.SslProtocols.Value); - } - - [Test] - public void SslProtocols_InvalidValue() - { - var log = new StringWriter(); - Assert.Throws(() => ConfigurationOptions.Parse("myhost,sslProtocols=InvalidSslProtocol")); - } - - [Test] - public void ConfigurationOptionsDefaultForAzure() - { - var options = ConfigurationOptions.Parse("contoso.redis.cache.windows.net"); - Assert.IsTrue(options.DefaultVersion.Equals(new Version(3, 0, 0))); - Assert.IsFalse(options.AbortOnConnectFail); - } - - [Test] - public void ConfigurationOptionsForAzureWhenSpecified() - { - var options = ConfigurationOptions.Parse("contoso.redis.cache.windows.net,abortConnect=true, version=2.1.1"); - Assert.IsTrue(options.DefaultVersion.Equals(new Version(2, 1, 1))); - Assert.IsTrue(options.AbortOnConnectFail); - } - - [Test] - public void ConfigurationOptionsDefaultForAzureChina() - { - // added a few upper case chars to validate comparison - var options = ConfigurationOptions.Parse("contoso.REDIS.CACHE.chinacloudapi.cn"); - Assert.IsTrue(options.DefaultVersion.Equals(new Version(3, 0, 0))); - Assert.IsFalse(options.AbortOnConnectFail); - } - - [Test] - public void ConfigurationOptionsDefaultForAzureGermany() - { - var options = ConfigurationOptions.Parse("contoso.redis.cache.cloudapi.de"); - Assert.IsTrue(options.DefaultVersion.Equals(new Version(3, 0, 0))); - Assert.IsFalse(options.AbortOnConnectFail); - } - - [Test] - public void ConfigurationOptionsDefaultForAzureUSGov() - { - var options = ConfigurationOptions.Parse("contoso.redis.cache.usgovcloudapi.net"); - Assert.IsTrue(options.DefaultVersion.Equals(new Version(3, 0, 0))); - Assert.IsFalse(options.AbortOnConnectFail); - } - - [Test] - public void ConfigurationOptionsDefaultForNonAzure() - { - var options = ConfigurationOptions.Parse("redis.contoso.com"); - Assert.IsTrue(options.DefaultVersion.Equals(new Version(2, 0, 0))); - Assert.IsTrue(options.AbortOnConnectFail); - } - - [Test] - public void ConfigurationOptionsDefaultWhenNoEndpointsSpecifiedYet() - { - var options = new ConfigurationOptions(); - Assert.IsTrue(options.DefaultVersion.Equals(new Version(2, 0, 0))); - Assert.IsTrue(options.AbortOnConnectFail); - } - - internal static void AssertNearlyEqual(double x, double y) - { - if (Math.Abs(x - y) > 0.00001) Assert.AreEqual(x, y); - } - } -} \ No newline at end of file diff --git a/MigratedBookSleeveTestSuite/Connection.cs b/MigratedBookSleeveTestSuite/Connection.cs deleted file mode 100644 index 1d86d6666..000000000 --- a/MigratedBookSleeveTestSuite/Connection.cs +++ /dev/null @@ -1,312 +0,0 @@ -//using BookSleeve; -//using NUnit.Framework; -//using System.Linq; -//using System; -//using System.Diagnostics; -//using System.IO; -//using System.Threading; -//using System.Collections.Generic; -//using System.Threading.Tasks; - -//namespace Tests -//{ -// [TestFixture] -// public class Connections // http://redis.io/commands#connection -// { -// [Test] -// public void TestConnectWithDownedNodeMustBeFast_multipletimes() -// { -// for (int i = 0; i < 5; i++) TestConnectWithDownedNodeMustBeFast(); -// } -// [Test] -// public void TestConnectWithDownedNodeMustBeFast() -// { -// using (var good = ConnectionUtils.Connect(Config.LocalHost + ":6379")) -// using (var bad = ConnectionUtils.Connect(Config.LocalHost + ":6666")) -// { -// Assert.IsNotNull(good, "6379 should exist for this test"); -// Assert.IsNull(bad, "6666 should not exist for this test"); -// } - -// StringWriter log = new StringWriter(); -// var watch = Stopwatch.StartNew(); -// using (var selected = ConnectionUtils.Connect(Config.LocalHost +":6379," + Config.LocalHost + ":6666,name=Core(Q&A)", log)) -// {} -// watch.Stop(); -// Console.WriteLine(log); -// Assert.Less(1, 2, "I always get this wrong!"); -// Assert.Less(watch.ElapsedMilliseconds, 1200, "I always get this wrong!"); - -// } - -// [Test] -// public void TestConnectViaSentinel() -// { -// string[] endpoints; -// StringWriter sw = new StringWriter(); -// var selected = ConnectionUtils.SelectConfiguration(Config.RemoteHost+":26379,serviceName=mymaster", out endpoints, sw); -// string log = sw.ToString(); -// Console.WriteLine(log); -// Assert.IsNotNull(selected, NO_SERVER); -// Assert.AreEqual(Config.RemoteHost + ":6379", selected); -// } -// [Test] -// public void TestConnectViaSentinelInvalidServiceName() -// { -// string[] endpoints; -// StringWriter sw = new StringWriter(); -// var selected = ConnectionUtils.SelectConfiguration(Config.RemoteHost + ":26379,serviceName=garbage", out endpoints, sw); -// string log = sw.ToString(); -// Console.WriteLine(log); -// Assert.IsNull(selected); -// } - -// const string NO_SERVER = "No server available"; -// [Test] -// public void TestDirectConnect() -// { -// string[] endpoints; -// StringWriter sw = new StringWriter(); -// var selected = ConnectionUtils.SelectConfiguration(Config.RemoteHost + ":6379", out endpoints, sw); -// string log = sw.ToString(); -// Console.WriteLine(log); -// Assert.IsNotNull(selected, NO_SERVER); -// Assert.AreEqual(Config.RemoteHost + ":6379", selected); - -// } - - -// [Test] -// public void TestName() -// { -// using (var conn = Config.GetUnsecuredConnection(open: false, allowAdmin: true)) -// { -// string name = Config.CreateUniqueName(); -// conn.Name = name; -// conn.Wait(conn.Open()); -// if (!conn.Features.ClientName) Assert.Inconclusive(); -// var client = conn.Wait(conn.Server.ListClients()).SingleOrDefault(c => c.Name == name); -// Assert.IsNotNull(client, "found client"); -// } -// } - -// [Test] -// public void CheckInProgressCountersGoToZero() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.CompletionMode = ResultCompletionMode.Concurrent; -// Task counters = null; -// Task[] allTasks = new Task[5000]; -// for (int i = 0; i < 5000; i++) -// { -// var tmp = conn.Strings.Get(5, "foo" + i); -// if (i == 2500) -// { -// counters = tmp.ContinueWith(x => -// { -// return conn.GetCounters(false); -// },TaskContinuationOptions.ExecuteSynchronously); -// } -// allTasks[i] = tmp; -// } - -// var c = conn.Wait(counters); -// Console.WriteLine("in progress during: {0}", c.AsyncCallbacksInProgress); -// Assert.AreNotEqual(0, c.AsyncCallbacksInProgress, "async during"); - -// conn.WaitAll(allTasks); -// PubSub.AllowReasonableTimeToPublishAndProcess(); -// Assert.AreEqual(0, conn.GetCounters(false).AsyncCallbacksInProgress, "async @ end"); -// Assert.AreEqual(0, c.SyncCallbacksInProgress, "sync @ end"); -// } -// } - -// [Test] -// public void TestSubscriberName() -// { -// using (var conn = Config.GetUnsecuredConnection(open: false, allowAdmin: true)) -// { -// string name = Config.CreateUniqueName(); -// conn.Name = name; -// conn.Wait(conn.Open()); -// if (!conn.Features.ClientName) Assert.Inconclusive(); -// using (var subscriber = conn.GetOpenSubscriberChannel()) -// { -// var evt = new ManualResetEvent(false); -// var tmp = subscriber.Subscribe("test-subscriber-name", delegate -// { -// evt.Set(); -// }); -// subscriber.Wait(tmp); -// conn.Publish("test-subscriber-name", "foo"); -// Assert.IsTrue(evt.WaitOne(1000), "event was set"); -// var clients = conn.Wait(conn.Server.ListClients()).Where(c => c.Name == name).ToList(); -// Assert.AreEqual(2, clients.Count, "number of clients"); -// } - -// } -// } - -// [Test] -// public void TestSubscriberNameOnRemote_WithName() -// { -// TestSubscriberNameOnRemote(true); -// } -// [Test] -// public void TestSubscriberNameOnRemote_WithoutName() -// { -// TestSubscriberNameOnRemote(false); -// } -// private void TestSubscriberNameOnRemote(bool setName) -// { -// string id = Config.CreateUniqueName(); - -// using (var pub = new RedisConnection(Config.RemoteHost, allowAdmin: true)) -// using (var sub = new RedisSubscriberConnection(Config.RemoteHost)) -// { -// List errors = new List(); -// EventHandler errorHandler = (sender, args) => -// { -// lock (errors) errors.Add(args.Exception.Message); -// }; -// pub.Error += errorHandler; -// sub.Error += errorHandler; - -// if (setName) -// { -// pub.Name = "pub_" + id; -// sub.Name = "sub_" + id; -// } -// int count = 0; -// var subscribe = sub.Subscribe("foo"+id, (key,payload) => Interlocked.Increment(ref count)); - -// Task pOpen = pub.Open(), sOpen = sub.Open(); -// pub.WaitAll(pOpen, sOpen, subscribe); - -// Assert.AreEqual(0, Interlocked.CompareExchange(ref count, 0, 0), "init message count"); -// pub.Wait(pub.Publish("foo" + id, "hello")); - -// PubSub.AllowReasonableTimeToPublishAndProcess(); -// var clients = setName ? pub.Wait(pub.Server.ListClients()) : null; -// Assert.AreEqual(1, Interlocked.CompareExchange(ref count, 0, 0), "got message"); -// lock (errors) -// { -// foreach (var error in errors) -// { -// Console.WriteLine(error); -// } -// Assert.AreEqual(0, errors.Count, "zero errors"); -// } -// if (setName) -// { -// Assert.AreEqual(1, clients.Count(x => x.Name == pub.Name), "pub has name"); -// Assert.AreEqual(1, clients.Count(x => x.Name == sub.Name), "sub has name"); -// } -// } - -// } - -// [Test] -// public void TestForcedSubscriberName() -// { -// using (var conn = Config.GetUnsecuredConnection(allowAdmin: true, open: true, waitForOpen: true)) -// using (var sub = new RedisSubscriberConnection(conn.Host, conn.Port)) -// { -// var task = sub.Subscribe("foo", delegate { }); -// string name = Config.CreateUniqueName(); -// sub.Name = name; -// sub.SetServerVersion(new Version("2.6.9"), ServerType.Master); -// sub.Wait(sub.Open()); -// sub.Wait(task); -// Assert.AreEqual(1, sub.SubscriptionCount); - -// if (!conn.Features.ClientName) Assert.Inconclusive(); -// var clients = conn.Wait(conn.Server.ListClients()).Where(c => c.Name == name).ToList(); -// Assert.AreEqual(1, clients.Count, "number of clients"); -// } -// } - -// [Test] -// public void TestNameViaConnect() -// { -// string name = Config.CreateUniqueName(); -// using (var conn = ConnectionUtils.Connect(Config.RemoteHost+",allowAdmin=true,name=" + name)) -// { -// Assert.IsNotNull(conn, NO_SERVER, "connection"); -// Assert.AreEqual(name, conn.Name, "connection name"); -// if (!conn.Features.ClientName) Assert.Inconclusive(); -// var client = conn.Wait(conn.Server.ListClients()).SingleOrDefault(c => c.Name == name); -// Assert.IsNotNull(client, "find client"); -// } -// } - -// // AUTH is already tested by secured connection - -// // QUIT is implicit in dispose - -// // ECHO has little utility in an application - -// [Test] -// public void TestGetSetOnDifferentDbHasDifferentValues() -// { -// // note: we don't expose SELECT directly, but we can verify that we have different DBs in play: - -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Strings.Set(1, "select", "abc"); -// conn.Strings.Set(2, "select", "def"); -// var x = conn.Strings.GetString(1, "select"); -// var y = conn.Strings.GetString(2, "select"); -// conn.WaitAll(x, y); -// Assert.AreEqual("abc", x.Result); -// Assert.AreEqual("def", y.Result); -// } -// } -// [Test, ExpectedException(typeof(ArgumentOutOfRangeException))] -// public void TestGetOnInvalidDbThrows() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Strings.GetString(-1, "select"); -// } -// } - - -// [Test] -// public void Ping() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// var ms = conn.Wait(conn.Server.Ping()); -// Assert.GreaterOrEqual(ms, 0); -// } -// } - -// [Test] -// public void CheckCounters() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen:true)) -// { -// conn.Wait(conn.Strings.GetString(0, "CheckCounters")); -// var first = conn.GetCounters(); - -// conn.Wait(conn.Strings.GetString(0, "CheckCounters")); -// var second = conn.GetCounters(); -// // +2 = ping + one select -// Assert.AreEqual(first.MessagesSent + 2, second.MessagesSent, "MessagesSent"); -// Assert.AreEqual(first.MessagesReceived + 2, second.MessagesReceived, "MessagesReceived"); -// Assert.AreEqual(0, second.ErrorMessages, "ErrorMessages"); -// Assert.AreEqual(0, second.MessagesCancelled, "MessagesCancelled"); -// Assert.AreEqual(0, second.SentQueue, "SentQueue"); -// Assert.AreEqual(0, second.UnsentQueue, "UnsentQueue"); -// Assert.AreEqual(0, second.QueueJumpers, "QueueJumpers"); -// Assert.AreEqual(0, second.Timeouts, "Timeouts"); -// Assert.IsTrue(second.Ping >= 0, "Ping"); -// Assert.IsTrue(second.ToString().Length > 0, "ToString"); -// } -// } - - -// } -//} diff --git a/MigratedBookSleeveTestSuite/Constraints.cs b/MigratedBookSleeveTestSuite/Constraints.cs deleted file mode 100644 index 629b06fed..000000000 --- a/MigratedBookSleeveTestSuite/Constraints.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Threading.Tasks; -using NUnit.Framework; -using StackExchange.Redis; - -namespace Tests -{ - [TestFixture] - public class Constraints - { - [Test] - public void ValueEquals() - { - RedisValue x = 1, y = "1"; - Assert.IsTrue(x.Equals(y), "equals"); - Assert.IsTrue(x == y, "operator"); - - } - - [Test] - public void TestManualIncr() - { - using (var muxer = Config.GetUnsecuredConnection(syncTimeout: 120000)) // big timeout while debugging - { - var conn = muxer.GetDatabase(0); - for (int i = 0; i < 200; i++) - { - conn.KeyDelete("foo"); - Assert.AreEqual(1, conn.Wait(ManualIncr(conn, "foo"))); - Assert.AreEqual(2, conn.Wait(ManualIncr(conn, "foo"))); - Assert.AreEqual(2, (long)conn.StringGet("foo")); - } - } - - } - - public async Task ManualIncr(IDatabase connection, string key) - { - var oldVal = (long?)await connection.StringGetAsync(key).ConfigureAwait(false); - var newVal = (oldVal ?? 0) + 1; - var tran = connection.CreateTransaction(); - { // check hasn't changed - -#pragma warning disable 4014 - tran.AddCondition(Condition.StringEqual(key, oldVal)); - tran.StringSetAsync(key, newVal); -#pragma warning restore 4014 - if (!await tran.ExecuteAsync().ConfigureAwait(false)) return null; // aborted - return newVal; - } - } - } -} diff --git a/MigratedBookSleeveTestSuite/Hashes.cs b/MigratedBookSleeveTestSuite/Hashes.cs deleted file mode 100644 index 6fd98d654..000000000 --- a/MigratedBookSleeveTestSuite/Hashes.cs +++ /dev/null @@ -1,507 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Linq; -using NUnit.Framework; -using StackExchange.Redis; - -namespace Tests -{ - [TestFixture] - public class Hashes // http://redis.io/commands#hash - { - [Test] - public void TestIncrBy() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(5); - conn.KeyDeleteAsync("hash-test"); - for (int i = 1; i < 1000; i++) - { - Assert.AreEqual(i, conn.HashIncrementAsync("hash-test", "a", 1).Result); - Assert.AreEqual(-i, conn.HashIncrementAsync("hash-test", "b", -1).Result); - //Assert.AreEqual(i, conn.Wait(conn.Hashes.Increment(5, "hash-test", "a", 1))); - //Assert.AreEqual(-i, conn.Wait(conn.Hashes.Increment(5, "hash-test", "b", -1))); - } - } - } - - [Test] - public void Scan() - { - using (var muxer = Config.GetUnsecuredConnection(waitForOpen: true)) - { - if (!Config.GetFeatures(muxer).Scan) Assert.Inconclusive(); - const int db = 3; - var conn = muxer.GetDatabase(db); - - const string key = "hash-scan"; - conn.KeyDeleteAsync(key); - conn.HashSetAsync(key, "abc", "def"); - conn.HashSetAsync(key, "ghi", "jkl"); - conn.HashSetAsync(key, "mno", "pqr"); - - var t1 = conn.HashScan(key); - var t2 = conn.HashScan(key, "*h*"); - var t3 = conn.HashScan(key); - var t4 = conn.HashScan(key, "*h*"); - - var v1 = t1.ToArray(); - var v2 = t2.ToArray(); - var v3 = t3.ToArray(); - var v4 = t4.ToArray(); - - Assert.AreEqual(3, v1.Length); - Assert.AreEqual(1, v2.Length); - Assert.AreEqual(3, v3.Length); - Assert.AreEqual(1, v4.Length); - Array.Sort(v1, (x, y) => string.Compare(x.Name, y.Name)); - Array.Sort(v2, (x, y) => string.Compare(x.Name, y.Name)); - Array.Sort(v3, (x, y) => string.Compare(x.Name, y.Name)); - Array.Sort(v4, (x, y) => string.Compare(x.Name, y.Name)); - - Assert.AreEqual("abc=def,ghi=jkl,mno=pqr", string.Join(",", v1.Select(pair => pair.Name + "=" + (string)pair.Value))); - Assert.AreEqual("ghi=jkl", string.Join(",", v2.Select(pair => pair.Name + "=" + (string)pair.Value))); - Assert.AreEqual("abc=def,ghi=jkl,mno=pqr", string.Join(",", v3.Select(pair => pair.Name + "=" + pair.Value))); - Assert.AreEqual("ghi=jkl", string.Join(",", v4.Select(pair => pair.Name + "=" + pair.Value))); - } - } - [Test] - public void TestIncrementOnHashThatDoesntExist() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(0); - conn.KeyDeleteAsync("keynotexist"); - var result1 = conn.Wait(conn.HashIncrementAsync("keynotexist", "fieldnotexist", 1)); - var result2 = conn.Wait(conn.HashIncrementAsync("keynotexist", "anotherfieldnotexist", 1)); - Assert.AreEqual(1, result1); - Assert.AreEqual(1, result2); - } - } - [Test] - public void TestIncrByFloat() - { - using (var muxer = Config.GetUnsecuredConnection(waitForOpen: true)) - { - var conn = muxer.GetDatabase(5); - if (!Config.GetFeatures(muxer).IncrementFloat) Assert.Inconclusive(); - { - conn.KeyDeleteAsync("hash-test"); - for (int i = 1; i < 1000; i++) - { - Assert.AreEqual((double)i, conn.HashIncrementAsync("hash-test", "a", 1.0).Result); - Assert.AreEqual((double)(-i), conn.HashIncrementAsync("hash-test", "b", -1.0).Result); - } - } - } - } - - - [Test] - public void TestGetAll() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(6); - const string key = "hash test"; - conn.KeyDeleteAsync(key); - var shouldMatch = new Dictionary(); - var random = new Random(); - - for (int i = 1; i < 1000; i++) - { - var guid = Guid.NewGuid(); - var value = random.Next(Int32.MaxValue); - - shouldMatch[guid] = value; - - var x = conn.HashIncrementAsync(key, guid.ToString(), value).Result; // Kill Async - } -#pragma warning disable 618 - var inRedis = conn.HashGetAllAsync(key).Result.ToDictionary( - x => Guid.Parse(x.Name), x => int.Parse(x.Value)); -#pragma warning restore 618 - - Assert.AreEqual(shouldMatch.Count, inRedis.Count); - - foreach (var k in shouldMatch.Keys) - { - Assert.AreEqual(shouldMatch[k], inRedis[k]); - } - } - } - - [Test] - public void TestGet() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var key = "hash test"; - var conn = muxer.GetDatabase(6); - var shouldMatch = new Dictionary(); - var random = new Random(); - - for (int i = 1; i < 1000; i++) - { - var guid = Guid.NewGuid(); - var value = random.Next(Int32.MaxValue); - - shouldMatch[guid] = value; - - var x = conn.HashIncrementAsync(key, guid.ToString(), value).Result; // Kill Async - } - - foreach (var k in shouldMatch.Keys) - { - var inRedis = conn.HashGetAsync(key, k.ToString()).Result; - var num = int.Parse((string)inRedis); - - Assert.AreEqual(shouldMatch[k], num); - } - } - } - - [Test] - public void TestSet() // http://redis.io/commands/hset - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(9); - conn.KeyDeleteAsync("hashkey"); - - var val0 = conn.HashGetAsync("hashkey", "field"); - var set0 = conn.HashSetAsync("hashkey", "field", "value1"); - var val1 = conn.HashGetAsync("hashkey", "field"); - var set1 = conn.HashSetAsync("hashkey", "field", "value2"); - var val2 = conn.HashGetAsync("hashkey", "field"); - - var set2 = conn.HashSetAsync("hashkey", "field-blob", Encoding.UTF8.GetBytes("value3")); - var val3 = conn.HashGetAsync("hashkey", "field-blob"); - - var set3 = conn.HashSetAsync("hashkey", "empty_type1", ""); - var val4 = conn.HashGetAsync("hashkey", "empty_type1"); - var set4 = conn.HashSetAsync("hashkey", "empty_type2", RedisValue.EmptyString); - var val5 = conn.HashGetAsync("hashkey", "empty_type2"); - - Assert.AreEqual(null, (string)val0.Result); - Assert.AreEqual(true, set0.Result); - Assert.AreEqual("value1", (string)val1.Result); - Assert.AreEqual(false, set1.Result); - Assert.AreEqual("value2", (string)val2.Result); - - Assert.AreEqual(true, set2.Result); - Assert.AreEqual("value3", (string)val3.Result); - - Assert.AreEqual(true, set3.Result); - Assert.AreEqual("", (string)val4.Result); - Assert.AreEqual(true, set4.Result); - Assert.AreEqual("", (string)val5.Result); - } - } - [Test] - public void TestSetNotExists() // http://redis.io/commands/hsetnx - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(9); - conn.KeyDeleteAsync("hashkey"); - - var val0 = conn.HashGetAsync("hashkey", "field"); - var set0 = conn.HashSetAsync("hashkey", "field", "value1", When.NotExists); - var val1 = conn.HashGetAsync("hashkey", "field"); - var set1 = conn.HashSetAsync("hashkey", "field", "value2", When.NotExists); - var val2 = conn.HashGetAsync("hashkey", "field"); - - var set2 = conn.HashSetAsync("hashkey", "field-blob", Encoding.UTF8.GetBytes("value3"), When.NotExists); - var val3 = conn.HashGetAsync("hashkey", "field-blob"); - var set3 = conn.HashSetAsync("hashkey", "field-blob", Encoding.UTF8.GetBytes("value3"), When.NotExists); - - Assert.AreEqual(null, (string)val0.Result); - Assert.AreEqual(true, set0.Result); - Assert.AreEqual("value1", (string)val1.Result); - Assert.AreEqual(false, set1.Result); - Assert.AreEqual("value1", (string)val2.Result); - - Assert.AreEqual(true, set2.Result); - Assert.AreEqual("value3", (string)val3.Result); - Assert.AreEqual(false, set3.Result); - - } - } - [Test] - public void TestDelSingle() // http://redis.io/commands/hdel - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(9); - conn.KeyDeleteAsync("hashkey"); - var del0 = conn.HashDeleteAsync("hashkey", "field"); - - conn.HashSetAsync("hashkey", "field", "value"); - - var del1 = conn.HashDeleteAsync("hashkey", "field"); - var del2 = conn.HashDeleteAsync("hashkey", "field"); - - Assert.AreEqual(false, del0.Result); - Assert.AreEqual(true, del1.Result); - Assert.AreEqual(false, del2.Result); - - } - } - [Test] - public void TestDelMulti() // http://redis.io/commands/hdel - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(3); - conn.HashSetAsync("TestDelMulti", "key1", "val1"); - conn.HashSetAsync("TestDelMulti", "key2", "val2"); - conn.HashSetAsync("TestDelMulti", "key3", "val3"); - - var s1 = conn.HashExistsAsync("TestDelMulti", "key1"); - var s2 = conn.HashExistsAsync("TestDelMulti", "key2"); - var s3 = conn.HashExistsAsync("TestDelMulti", "key3"); - - var removed = conn.HashDeleteAsync("TestDelMulti", new RedisValue[] { "key1", "key3" }); - - var d1 = conn.HashExistsAsync("TestDelMulti", "key1"); - var d2 = conn.HashExistsAsync("TestDelMulti", "key2"); - var d3 = conn.HashExistsAsync("TestDelMulti", "key3"); - - Assert.IsTrue(conn.Wait(s1)); - Assert.IsTrue(conn.Wait(s2)); - Assert.IsTrue(conn.Wait(s3)); - - Assert.AreEqual(2, conn.Wait(removed)); - - Assert.IsFalse(conn.Wait(d1)); - Assert.IsTrue(conn.Wait(d2)); - Assert.IsFalse(conn.Wait(d3)); - - var removeFinal = conn.HashDeleteAsync("TestDelMulti", new RedisValue[] { "key2" }); - - Assert.AreEqual(0, conn.Wait(conn.HashLengthAsync("TestDelMulti"))); - Assert.AreEqual(1, conn.Wait(removeFinal)); - } - } - - [Test] - public void TestDelMultiInsideTransaction() // http://redis.io/commands/hdel - { - using (var outer = Config.GetUnsecuredConnection()) - { - - var conn = outer.GetDatabase(3).CreateTransaction(); - { - conn.HashSetAsync("TestDelMulti", "key1", "val1"); - conn.HashSetAsync("TestDelMulti", "key2", "val2"); - conn.HashSetAsync("TestDelMulti", "key3", "val3"); - - var s1 = conn.HashExistsAsync("TestDelMulti", "key1"); - var s2 = conn.HashExistsAsync("TestDelMulti", "key2"); - var s3 = conn.HashExistsAsync("TestDelMulti", "key3"); - - var removed = conn.HashDeleteAsync("TestDelMulti", new RedisValue[] { "key1", "key3" }); - - var d1 = conn.HashExistsAsync("TestDelMulti", "key1"); - var d2 = conn.HashExistsAsync("TestDelMulti", "key2"); - var d3 = conn.HashExistsAsync("TestDelMulti", "key3"); - - conn.Execute(); - - Assert.IsTrue(conn.Wait(s1)); - Assert.IsTrue(conn.Wait(s2)); - Assert.IsTrue(conn.Wait(s3)); - - Assert.AreEqual(2, conn.Wait(removed)); - - Assert.IsFalse(conn.Wait(d1)); - Assert.IsTrue(conn.Wait(d2)); - Assert.IsFalse(conn.Wait(d3)); - } - - } - } - [Test] - public void TestExists() // http://redis.io/commands/hexists - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(9); - conn.KeyDeleteAsync("hashkey"); - var ex0 = conn.HashExistsAsync("hashkey", "field"); - conn.HashSetAsync("hashkey", "field", "value"); - var ex1 = conn.HashExistsAsync("hashkey", "field"); - conn.HashDeleteAsync("hashkey", "field"); - var ex2 = conn.HashExistsAsync("hashkey", "field"); - - Assert.AreEqual(false, ex0.Result); - Assert.AreEqual(true, ex1.Result); - Assert.AreEqual(false, ex0.Result); - - } - } - - [Test] - public void TestHashKeys() // http://redis.io/commands/hkeys - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(9); - conn.KeyDeleteAsync("hashkey"); - - var keys0 = conn.HashKeysAsync("hashkey"); - - conn.HashSetAsync("hashkey", "foo", "abc"); - conn.HashSetAsync("hashkey", "bar", "def"); - - var keys1 = conn.HashKeysAsync("hashkey"); - - Assert.AreEqual(0, keys0.Result.Length); - - var arr = keys1.Result; - Assert.AreEqual(2, arr.Length); - Assert.AreEqual("foo", (string)arr[0]); - Assert.AreEqual("bar", (string)arr[1]); - - } - } - - [Test] - public void TestHashValues() // http://redis.io/commands/hvals - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(9); - conn.KeyDeleteAsync("hashkey"); - - var keys0 = conn.HashValuesAsync("hashkey"); - - conn.HashSetAsync("hashkey", "foo", "abc"); - conn.HashSetAsync("hashkey", "bar", "def"); - - var keys1 = conn.HashValuesAsync("hashkey"); - - Assert.AreEqual(0, keys0.Result.Length); - - var arr = keys1.Result; - Assert.AreEqual(2, arr.Length); - Assert.AreEqual("abc", Encoding.UTF8.GetString(arr[0])); - Assert.AreEqual("def", Encoding.UTF8.GetString(arr[1])); - - } - } - - [Test] - public void TestHashLength() // http://redis.io/commands/hlen - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(9); - conn.KeyDeleteAsync("hashkey"); - - var len0 = conn.HashLengthAsync("hashkey"); - - conn.HashSetAsync("hashkey", "foo", "abc"); - conn.HashSetAsync("hashkey", "bar", "def"); - - var len1 = conn.HashLengthAsync("hashkey"); - - Assert.AreEqual(0, len0.Result); - Assert.AreEqual(2, len1.Result); - - } - } - - [Test] - public void TestGetMulti() // http://redis.io/commands/hmget - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(9); - conn.KeyDeleteAsync("hashkey"); - - RedisValue[] fields = { "foo", "bar", "blop" }; - var result0 = conn.HashGetAsync("hashkey", fields); - - conn.HashSetAsync("hashkey", "foo", "abc"); - conn.HashSetAsync("hashkey", "bar", "def"); - - var result1 = conn.HashGetAsync("hashkey", fields); - - var result2 = conn.HashGetAsync("hashkey", fields); - - var arr0 = result0.Result; - var arr1 = result1.Result; - var arr2 = result2.Result; - - Assert.AreEqual(3, arr0.Length); - Assert.IsNull((string)arr0[0]); - Assert.IsNull((string)arr0[1]); - Assert.IsNull((string)arr0[2]); - - Assert.AreEqual(3, arr1.Length); - Assert.AreEqual("abc", (string)arr1[0]); - Assert.AreEqual("def", (string)arr1[1]); - Assert.IsNull((string)arr1[2]); - - Assert.AreEqual(3, arr2.Length); - Assert.AreEqual("abc", (string)arr2[0]); - Assert.AreEqual("def", (string)arr2[1]); - Assert.IsNull((string)arr2[2]); - } - } - - [Test] - public void TestGetPairs() // http://redis.io/commands/hgetall - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(9); - conn.KeyDeleteAsync("hashkey"); - - var result0 = conn.HashGetAllAsync("hashkey"); - - conn.HashSetAsync("hashkey", "foo", "abc"); - conn.HashSetAsync("hashkey", "bar", "def"); - - var result1 = conn.HashGetAllAsync("hashkey"); - - Assert.AreEqual(0, result0.Result.Length); - var result = result1.Result.ToStringDictionary(); - Assert.AreEqual(2, result.Count); - Assert.AreEqual("abc", result["foo"]); - Assert.AreEqual("def", result["bar"]); - } - } - - [Test] - public void TestSetPairs() // http://redis.io/commands/hmset - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(9); - conn.KeyDeleteAsync("hashkey"); - - var result0 = conn.HashGetAllAsync("hashkey"); - - var data = new HashEntry[] { - new HashEntry("foo", Encoding.UTF8.GetBytes("abc")), - new HashEntry("bar", Encoding.UTF8.GetBytes("def")) - }; - conn.HashSetAsync("hashkey", data); - - var result1 = conn.HashGetAllAsync("hashkey"); - - Assert.AreEqual(0, result0.Result.Length); - var result = result1.Result.ToStringDictionary(); - Assert.AreEqual(2, result.Count); - Assert.AreEqual("abc", result["foo"]); - Assert.AreEqual("def", result["bar"]); - } - } - - } -} diff --git a/MigratedBookSleeveTestSuite/Issues/Issue10.cs b/MigratedBookSleeveTestSuite/Issues/Issue10.cs deleted file mode 100644 index 01afcd913..000000000 --- a/MigratedBookSleeveTestSuite/Issues/Issue10.cs +++ /dev/null @@ -1,30 +0,0 @@ -using NUnit.Framework; - -namespace Tests.Issues -{ - [TestFixture] - public class Issue10 - { - [Test] - public void Execute() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - const int DB = 5; - const string Key = "issue-10-list"; - var conn = muxer.GetDatabase(DB); - conn.KeyDeleteAsync(Key); // contents: nil - conn.ListLeftPushAsync(Key, "abc"); // "abc" - conn.ListLeftPushAsync(Key, "def"); // "def", "abc" - conn.ListLeftPushAsync(Key, "ghi"); // "ghi", "def", "abc", - conn.ListSetByIndexAsync(Key, 1, "jkl"); // "ghi", "jkl", "abc" - - var contents = conn.Wait(conn.ListRangeAsync(Key, 0, -1)); - Assert.AreEqual(3, contents.Length); - Assert.AreEqual("ghi", (string)contents[0]); - Assert.AreEqual("jkl", (string)contents[1]); - Assert.AreEqual("abc", (string)contents[2]); - } - } - } -} diff --git a/MigratedBookSleeveTestSuite/Issues/Massive Delete.cs b/MigratedBookSleeveTestSuite/Issues/Massive Delete.cs deleted file mode 100644 index de8f9d6fa..000000000 --- a/MigratedBookSleeveTestSuite/Issues/Massive Delete.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace Tests.Issues -{ - [TestFixture] - public class Massive_Delete - { - [OneTimeSetUpAttribute] - public void Init() - { - using (var muxer = Config.GetUnsecuredConnection(allowAdmin: true)) - { - Config.GetServer(muxer).FlushDatabase(db); - Task last = null; - var conn = muxer.GetDatabase(db); - for (int i = 0; i < 100000; i++) - { - string key = "key" + i; - conn.StringSetAsync(key, key); - last = conn.SetAddAsync(todoKey, key); - } - conn.Wait(last); - } - } - - const int db = 4; - const string todoKey = "todo"; - - [Test] - public void ExecuteMassiveDelete() - { - var watch = Stopwatch.StartNew(); - using (var muxer = Config.GetUnsecuredConnection()) - using (var throttle = new SemaphoreSlim(1)) - { - var conn = muxer.GetDatabase(db); - var originallyTask = conn.SetLengthAsync(todoKey); - int keepChecking = 1; - Task last = null; - while (Volatile.Read(ref keepChecking) == 1) - { - throttle.Wait(); // acquire - conn.SetPopAsync(todoKey).ContinueWith(task => - { - throttle.Release(); - if (task.IsCompleted) - { - if ((string)task.Result == null) - { - Volatile.Write(ref keepChecking, 0); - } - else - { - last = conn.KeyDeleteAsync((string)task.Result); - } - } - }); - } - if (last != null) - { - conn.Wait(last); - } - watch.Stop(); - long originally = conn.Wait(originallyTask), - remaining = conn.SetLength(todoKey); - Console.WriteLine("From {0} to {1}; {2}ms", originally, remaining, - watch.ElapsedMilliseconds); - - var counters = Config.GetServer(muxer).GetCounters(); - Console.WriteLine("Completions: {0} sync, {1} async", counters.Interactive.CompletedSynchronously, counters.Interactive.CompletedAsynchronously); - } - } - } -} diff --git a/MigratedBookSleeveTestSuite/Issues/SO10504853.cs b/MigratedBookSleeveTestSuite/Issues/SO10504853.cs deleted file mode 100644 index 54fa8fcb9..000000000 --- a/MigratedBookSleeveTestSuite/Issues/SO10504853.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Diagnostics; -using NUnit.Framework; -using StackExchange.Redis; - -namespace Tests.Issues -{ - [TestFixture] - public class SO10504853 - { - [Test] - public void LoopLotsOfTrivialStuff() - { - Trace.WriteLine("### init"); - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(0); - conn.KeyDelete("lots-trivial"); - } - const int COUNT = 2; - for (int i = 0; i < COUNT; i++) - { - Trace.WriteLine("### incr:" + i); - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(0); - Assert.AreEqual(i + 1, conn.StringIncrement("lots-trivial")); - } - } - Trace.WriteLine("### close"); - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(0); - Assert.AreEqual(COUNT, (long)conn.StringGet("lots-trivial")); - } - } - [Test] - public void ExecuteWithEmptyStartingPoint() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(0); - var task = new { priority = 3 }; - conn.KeyDeleteAsync("item:1"); - conn.HashSetAsync("item:1", "something else", "abc"); - conn.HashSetAsync("item:1", "priority", task.priority.ToString()); - - var taskResult = conn.HashGetAsync("item:1", "priority"); - - conn.Wait(taskResult); - - var priority = Int32.Parse(taskResult.Result); - - Assert.AreEqual(3, priority); - } - } - - [Test] - public void ExecuteWithNonHashStartingPoint() - { - Assert.Throws(() => - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(0); - var task = new { priority = 3 }; - conn.KeyDeleteAsync("item:1"); - conn.StringSetAsync("item:1", "not a hash"); - conn.HashSetAsync("item:1", "priority", task.priority.ToString()); - - var taskResult = conn.HashGetAsync("item:1", "priority"); - - try - { - conn.Wait(taskResult); - Assert.Fail(); - } - catch (AggregateException ex) - { - throw ex.InnerExceptions[0]; - } - } - }, - message: "WRONGTYPE Operation against a key holding the wrong kind of value"); - } - } -} diff --git a/MigratedBookSleeveTestSuite/Issues/SO10825542.cs b/MigratedBookSleeveTestSuite/Issues/SO10825542.cs deleted file mode 100644 index 11636652e..000000000 --- a/MigratedBookSleeveTestSuite/Issues/SO10825542.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Text; -using NUnit.Framework; -using StackExchange.Redis; - -namespace Tests.Issues -{ - [TestFixture] - public class SO10825542 - { - [Test] - public void Execute() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var key = "somekey1"; - - var con = muxer.GetDatabase(1); - // set the field value and expiration - con.HashSetAsync(key, "field1", Encoding.UTF8.GetBytes("hello world")); - con.KeyExpireAsync(key, TimeSpan.FromSeconds(7200)); - con.HashSetAsync(key, "field2", "fooobar"); - var task = con.HashGetAllAsync(key); - con.Wait(task); - - Assert.AreEqual(2, task.Result.Length); - var dict = task.Result.ToStringDictionary(); - Assert.AreEqual("hello world", dict["field1"]); - Assert.AreEqual("fooobar", dict["field2"]); - } - } - } -} diff --git a/MigratedBookSleeveTestSuite/Issues/SO11766033.cs b/MigratedBookSleeveTestSuite/Issues/SO11766033.cs deleted file mode 100644 index f3fa1cc9e..000000000 --- a/MigratedBookSleeveTestSuite/Issues/SO11766033.cs +++ /dev/null @@ -1,41 +0,0 @@ -using NUnit.Framework; - -namespace Tests.Issues -{ - [TestFixture] - public class SO11766033 - { - [Test] - public void TestNullString() - { - const int db = 3; - using (var muxer = Config.GetUnsecuredConnection(true)) - { - var redis = muxer.GetDatabase(db); - string expectedTestValue = null; - var uid = Config.CreateUniqueName(); - redis.StringSetAsync(uid, "abc"); - redis.StringSetAsync(uid, expectedTestValue); - string testValue = redis.StringGet(uid); - Assert.IsNull(testValue); - } - } - - [Test] - public void TestEmptyString() - { - const int db = 3; - using (var muxer = Config.GetUnsecuredConnection(true)) - { - var redis = muxer.GetDatabase(db); - string expectedTestValue = ""; - var uid = Config.CreateUniqueName(); - - redis.StringSetAsync(uid, expectedTestValue); - string testValue = redis.StringGet(uid); - - Assert.AreEqual(expectedTestValue, testValue); - } - } - } -} diff --git a/MigratedBookSleeveTestSuite/Keys.cs b/MigratedBookSleeveTestSuite/Keys.cs deleted file mode 100644 index aa678b4d3..000000000 --- a/MigratedBookSleeveTestSuite/Keys.cs +++ /dev/null @@ -1,511 +0,0 @@ -//using System.Threading; -//using BookSleeve; -//using NUnit.Framework; -//using System.Text.RegularExpressions; -//using System.Linq; -//using System; -//namespace Tests -//{ -// [TestFixture] -// public class Keys // http://redis.io/commands#generic -// { -// // note we don't expose EXPIREAT as it raises all sorts of problems with -// // time synchronisation, UTC vs local, DST, etc; easier for the caller -// // to use EXPIRE - -// [Test] -// public void TestDeleteValidKey() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Strings.Set(0, "del", "abcdef"); -// var x = conn.Strings.GetString(0, "del"); -// var del = conn.Keys.Remove(0, "del"); -// var y = conn.Strings.GetString(0, "del"); -// conn.WaitAll(x, del, y); -// Assert.AreEqual("abcdef", x.Result); -// Assert.IsTrue(del.Result); -// Assert.AreEqual(null, y.Result); -// } -// } - -// [Test] -// public void TestLargeIntegers() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// const long expected = 20L * int.MaxValue; -// conn.Strings.Set(0, "large-int", expected); -// var result = conn.Strings.GetInt64(0, "large-int"); -// Assert.AreEqual(expected, conn.Wait(result)); -// } -// } - -// [Test] -// public void TestDeleteInvalidKey() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Strings.Set(0, "exists", "abcdef"); -// var x = conn.Keys.Remove(0, "exists"); -// var y = conn.Keys.Remove(0, "exists"); -// conn.WaitAll(x, y); -// Assert.IsTrue(x.Result); -// Assert.IsFalse(y.Result); -// } -// } - -// [Test] -// public void TestDeleteMultiple() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Strings.Set(0, "del", "abcdef"); -// var x = conn.Keys.Remove(0, "del"); -// var y = conn.Keys.Remove(0, "del"); -// conn.WaitAll(x, y); -// Assert.IsTrue(x.Result); -// Assert.IsFalse(y.Result); -// } -// } - -// [Test] -// public void Scan() -// { -// using (var conn = Config.GetUnsecuredConnection(allowAdmin: true, waitForOpen: true)) -// { -// if (!conn.Features.Scan) Assert.Inconclusive(); - -// const int DB = 3; -// conn.Wait(conn.Server.FlushDb(DB)); -// conn.Strings.Set(DB, "foo", "foo"); -// conn.Strings.Set(DB, "bar", "bar"); -// conn.Strings.Set(DB, "blap", "blap"); - -// var keys = conn.Keys.Scan(DB).ToArray(); -// Array.Sort(keys); -// Assert.AreEqual(3, keys.Length); -// Assert.AreEqual("bar", keys[0]); -// Assert.AreEqual("blap", keys[1]); -// Assert.AreEqual("foo", keys[2]); - -// keys = conn.Keys.Scan(DB, "b*").ToArray(); -// Array.Sort(keys); -// Assert.AreEqual(2, keys.Length); -// Assert.AreEqual("bar", keys[0]); -// Assert.AreEqual("blap", keys[1]); - - -// } -// } - -// [Test] -// public void TestExpireAgainstInvalidKey() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Strings.Set(0, "delA", "abcdef"); -// conn.Keys.Remove(0, "delB"); -// conn.Strings.Set(0, "delC", "abcdef"); - -// var del = conn.Keys.Remove(0, new[] {"delA", "delB", "delC"}); -// Assert.AreEqual(2, conn.Wait(del)); -// } -// } - -// [Test] -// public void TestExists() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Strings.Set(0, "exists", "abcdef"); -// var x = conn.Keys.Exists(0, "exists"); -// conn.Keys.Remove(0, "exists"); -// var y = conn.Keys.Exists(0, "exists"); -// conn.WaitAll(x, y); -// Assert.IsTrue(x.Result); -// Assert.IsFalse (y.Result); -// } -// } - -// [Test] -// public void TestExpireAgainstValidKey() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Strings.Set(0, "expire", "abcdef"); -// var x = conn.Keys.TimeToLive(0, "expire"); -// var exp1 = conn.Keys.Expire(0, "expire", 100); -// var y = conn.Keys.TimeToLive(0, "expire"); -// var exp2 = conn.Keys.Expire(0, "expire", 150); -// var z = conn.Keys.TimeToLive(0, "expire"); - -// conn.WaitAll(x, exp1, y, exp2, z); - -// Assert.AreEqual(-1, x.Result); -// Assert.IsTrue(exp1.Result); -// Assert.GreaterOrEqual(y.Result, 90); -// Assert.LessOrEqual(y.Result, 100); - -// if (conn.Features.ExpireOverwrite) -// { -// Assert.IsTrue(exp2.Result); -// Assert.GreaterOrEqual(z.Result, 140); -// Assert.LessOrEqual(z.Result, 150); -// } -// else -// { -// Assert.IsFalse(exp2.Result); -// Assert.GreaterOrEqual(z.Result, 90); -// Assert.LessOrEqual(z.Result, 100); -// } -// } -// } - -// [Test] -// public void TestSuccessfulMove() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Strings.Set(1, "move", "move-value"); -// conn.Keys.Remove(2, "move"); - -// var succ = conn.Keys.Move(1, "move", 2); -// var in1 = conn.Strings.GetString(1, "move"); -// var in2 = conn.Strings.GetString(2, "move"); - -// Assert.IsTrue(conn.Wait(succ)); -// Assert.IsNull(conn.Wait(in1)); -// Assert.AreEqual("move-value", conn.Wait(in2)); -// } -// } - -// [Test] -// public void TestFailedMoveWhenNotExistsInSource() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "move"); -// conn.Strings.Set(2, "move", "move-value"); - -// var succ = conn.Keys.Move(1, "move", 2); -// var in1 = conn.Strings.GetString(1, "move"); -// var in2 = conn.Strings.GetString(2, "move"); - -// Assert.IsFalse(conn.Wait(succ)); -// Assert.IsNull(conn.Wait(in1)); -// Assert.AreEqual("move-value", conn.Wait(in2)); -// } -// } - -// [Test] -// public void TestFailedMoveWhenNotExistsInTarget() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Strings.Set(1, "move", "move-valueA"); -// conn.Strings.Set(2, "move", "move-valueB"); - -// var succ = conn.Keys.Move(1, "move", 2); -// var in1 = conn.Strings.GetString(1, "move"); -// var in2 = conn.Strings.GetString(2, "move"); - -// Assert.IsFalse(conn.Wait(succ)); -// Assert.AreEqual("move-valueA", conn.Wait(in1)); -// Assert.AreEqual("move-valueB", conn.Wait(in2)); -// } -// } - -// [Test] -// public void RemoveExpiry() -// { -// int errors = 0, expectedErrors; -// using (var conn = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// conn.Error += delegate -// { -// Interlocked.Increment(ref errors); -// }; -// conn.Keys.Remove(1, "persist"); -// conn.Strings.Set(1, "persist", "persist"); -// var persist1 = conn.Keys.Persist(1, "persist"); -// conn.Keys.Expire(1, "persist", 100); -// var before = conn.Keys.TimeToLive(1, "persist"); -// var persist2 = conn.Keys.Persist(1, "persist"); -// var after = conn.Keys.TimeToLive(1, "persist"); - -// Assert.GreaterOrEqual(conn.Wait(before), 90); -// if (conn.Features.Persist) -// { -// Assert.IsFalse(conn.Wait(persist1)); -// Assert.IsTrue(conn.Wait(persist2)); -// Assert.AreEqual(-1, conn.Wait(after)); -// expectedErrors = 0; -// } -// else -// { -// try{ -// conn.Wait(persist1); -// Assert.Fail(); -// } -// catch (RedisException){} -// try -// { -// conn.Wait(persist2); -// Assert.Fail(); -// } -// catch (RedisException) { } -// Assert.GreaterOrEqual(conn.Wait(after), 90); -// expectedErrors = 2; -// } -// } - -// Assert.AreEqual(expectedErrors, Interlocked.CompareExchange(ref errors,0,0)); -// } - - -// [Test] -// public void RandomKeys() -// { -// using (var conn = Config.GetUnsecuredConnection(allowAdmin: true, waitForOpen: true)) -// { -// conn.Server.FlushDb(6); -// var key1 = conn.Keys.Random(6); -// conn.Strings.Set(6, "random1", "random1"); -// var key2 = conn.Keys.Random(6); -// for (int i = 2; i < 100; i++) -// { -// string key = "random" + i; -// conn.Strings.Set(6, key, key); -// } -// var key3 = conn.Keys.Random(6); - -// Assert.IsNull(conn.Wait(key1)); -// Assert.AreEqual("random1", conn.Wait(key2)); -// string s = conn.Wait(key3); - -// Assert.IsTrue(s.StartsWith("random")); -// s = s.Substring(6); -// int result = int.Parse(s); -// Assert.GreaterOrEqual(result, 1); -// Assert.Less(result, 100); -// } - -// } - -// [Test] -// public void Sort() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "sort"); -// conn.Lists.AddLast(1, "sort", "10"); -// conn.Lists.AddLast(1, "sort", "3"); -// conn.Lists.AddLast(1, "sort", "1.1"); -// conn.Lists.AddLast(1, "sort", "2"); -// var a = conn.Keys.SortString(1, "sort"); -// var b = conn.Keys.SortString(1, "sort", ascending: false, offset: 1, count: 2); -// var c = conn.Keys.SortString(1, "sort", alpha: true); -// var d = conn.Keys.SortAndStore(1, "sort-store", "sort"); -// var e = conn.Lists.RangeString(1, "sort-store", 0, -1); -// var f = conn.Lists.RangeString(1, "sort", 0, -1); - -// Assert.AreEqual("1.1;2;3;10",string.Join(";", conn.Wait(a))); -// Assert.AreEqual("3;2",string.Join(";", conn.Wait(b))); -// Assert.AreEqual("10;1.1;2;3", string.Join(";", conn.Wait(c))); -// Assert.AreEqual(4, conn.Wait(d)); -// Assert.AreEqual("1.1;2;3;10", string.Join(";", conn.Wait(e))); -// Assert.AreEqual("10;3;1.1;2", string.Join(";", conn.Wait(f))); - -// } - -// } - -// [Test] -// public void ItemType() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(4, new[]{"type-none", "type-list", "type-string", -// "type-set", "type-zset", "type-hash"}); -// conn.Strings.Set(4, "type-string", "blah"); -// conn.Lists.AddLast(4, "type-list", "blah"); -// conn.Sets.Add(4, "type-set", "blah"); -// conn.SortedSets.Add(4, "type-zset", "blah", 123); -// conn.Hashes.Set(4, "type-hash", "foo", "blah"); - -// var x0 = conn.Keys.Type(4, "type-none"); -// var x1 = conn.Keys.Type(4, "type-list"); -// var x2 = conn.Keys.Type(4, "type-string"); -// var x3 = conn.Keys.Type(4, "type-set"); -// var x4 = conn.Keys.Type(4, "type-zset"); -// var x5 = conn.Keys.Type(4, "type-hash"); - -// Assert.AreEqual(RedisConnection.ItemTypes.None, conn.Wait(x0)); -// Assert.AreEqual(RedisConnection.ItemTypes.List, conn.Wait(x1)); -// Assert.AreEqual(RedisConnection.ItemTypes.String, conn.Wait(x2)); -// Assert.AreEqual(RedisConnection.ItemTypes.Set, conn.Wait(x3)); -// Assert.AreEqual(RedisConnection.ItemTypes.SortedSet, conn.Wait(x4)); -// Assert.AreEqual(RedisConnection.ItemTypes.Hash, conn.Wait(x5)); -// } -// } -// [Test] -// public void RenameKeyWithOverwrite() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "foo"); -// conn.Keys.Remove(1, "bar"); - -// var check1 = conn.Keys.Rename(1, "foo", "bar"); // neither -// var after1_foo = conn.Strings.GetString(1, "foo"); -// var after1_bar = conn.Strings.GetString(1, "bar"); - -// conn.Strings.Set(1, "foo", "foo-value"); - -// var check2 = conn.Keys.Rename(1, "foo", "bar"); // source only -// var after2_foo = conn.Strings.GetString(1, "foo"); -// var after2_bar = conn.Strings.GetString(1, "bar"); - -// var check3 = conn.Keys.Rename(1, "foo", "bar"); // dest only -// var after3_foo = conn.Strings.GetString(1, "foo"); -// var after3_bar = conn.Strings.GetString(1, "bar"); - -// conn.Strings.Set(1, "foo", "new-value"); -// var check4 = conn.Keys.Rename(1, "foo", "bar"); // both -// var after4_foo = conn.Strings.GetString(1, "foo"); -// var after4_bar = conn.Strings.GetString(1, "bar"); - -// try -// { -// conn.Wait(check1); -// Assert.Fail(); -// } -// catch (RedisException) { } -// Assert.IsNull(conn.Wait(after1_foo)); -// Assert.IsNull(conn.Wait(after1_bar)); - -// conn.Wait(check2); -// Assert.IsNull(conn.Wait(after2_foo)); -// Assert.AreEqual("foo-value", conn.Wait(after2_bar)); - -// try -// { -// conn.Wait(check3); -// Assert.Fail(); -// } -// catch (RedisException) { } -// Assert.IsNull(conn.Wait(after3_foo)); -// Assert.AreEqual("foo-value", conn.Wait(after3_bar)); - -// conn.Wait(check4); -// Assert.IsNull(conn.Wait(after4_foo)); -// Assert.AreEqual("new-value", conn.Wait(after4_bar)); - -// } -// } - -// [Test] -// public void RenameKeyWithoutOverwrite() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "foo"); -// conn.Keys.Remove(1, "bar"); - -// var check1 = conn.Keys.RenameIfNotExists(1, "foo", "bar"); // neither -// var after1_foo = conn.Strings.GetString(1, "foo"); -// var after1_bar = conn.Strings.GetString(1, "bar"); - -// conn.Strings.Set(1, "foo", "foo-value"); - -// var check2 = conn.Keys.RenameIfNotExists(1, "foo", "bar"); // source only -// var after2_foo = conn.Strings.GetString(1, "foo"); -// var after2_bar = conn.Strings.GetString(1, "bar"); - -// var check3 = conn.Keys.RenameIfNotExists(1, "foo", "bar"); // dest only -// var after3_foo = conn.Strings.GetString(1, "foo"); -// var after3_bar = conn.Strings.GetString(1, "bar"); - -// conn.Strings.Set(1, "foo", "new-value"); -// var check4 = conn.Keys.RenameIfNotExists(1, "foo", "bar"); // both -// var after4_foo = conn.Strings.GetString(1, "foo"); -// var after4_bar = conn.Strings.GetString(1, "bar"); - -// try -// { -// conn.Wait(check1); -// Assert.Fail(); -// } -// catch (RedisException) { } -// Assert.IsNull(conn.Wait(after1_foo)); -// Assert.IsNull(conn.Wait(after1_bar)); - -// Assert.IsTrue(conn.Wait(check2)); -// Assert.IsNull(conn.Wait(after2_foo)); -// Assert.AreEqual("foo-value", conn.Wait(after2_bar)); - -// try -// { -// conn.Wait(check3); -// Assert.Fail(); -// } -// catch (RedisException) { } -// Assert.IsNull(conn.Wait(after3_foo)); -// Assert.AreEqual("foo-value", conn.Wait(after3_bar)); - -// Assert.IsFalse(conn.Wait(check4)); -// Assert.AreEqual("new-value", conn.Wait(after4_foo)); -// Assert.AreEqual("foo-value", conn.Wait(after4_bar)); - -// } -// } - -// [Test] -// public void TestFind() -// { -// using(var conn = Config.GetUnsecuredConnection(allowAdmin:true)) -// { -// conn.Server.FlushDb(5); -// conn.Strings.Set(5, "abc", "def"); -// conn.Strings.Set(5, "abd", "ghi"); -// conn.Strings.Set(5, "aef", "jkl"); -// var arr = conn.Wait(conn.Keys.Find(5, "ab*")); -// Assert.AreEqual(2, arr.Length); -// Assert.Contains("abc", arr); -// Assert.Contains("abd", arr); -// } -// } - -// [Test] -// public void TestDBSize() -// { -// using(var conn = Config.GetUnsecuredConnection(allowAdmin:true)) -// { -// conn.Server.FlushDb(5); -// var empty = conn.Keys.GetLength(5); -// for (int i = 0; i < 10; i++ ) -// conn.Strings.Set(5, "abc" + i, "def" + i); -// var withData = conn.Keys.GetLength(5); - -// Assert.AreEqual(0, conn.Wait(empty)); -// Assert.AreEqual(10, conn.Wait(withData)); -// } -// } - -// [Test] -// public void TestDebugObject() -// { -// using (var conn = Config.GetUnsecuredConnection(allowAdmin: true)) -// { -// conn.Strings.Set(3, "test-debug", "some value"); -// var s = conn.Wait(conn.Keys.DebugObject(3, "test-debug")); -// Assert.IsTrue(Regex.IsMatch(s, @"\bserializedlength:([0-9]+)\b")); -// } -// } -// } -//} - - - - diff --git a/MigratedBookSleeveTestSuite/Lists.cs b/MigratedBookSleeveTestSuite/Lists.cs deleted file mode 100644 index 8e1de87fb..000000000 --- a/MigratedBookSleeveTestSuite/Lists.cs +++ /dev/null @@ -1,713 +0,0 @@ -//using System.Text; -//using NUnit.Framework; -//using System.Linq; -//using System; -//using System.Threading; -//namespace Tests -//{ -// [TestFixture] -// public class Lists // http://redis.io/commands#list -// { -// [Test] -// public void CheckLengthWhenEmpty() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(4, "mylist"); -// var len = conn.Lists.GetLength(4, "mylist"); - -// Assert.AreEqual(0, conn.Wait(len)); -// } -// } - -// [Test] -// public void CheckLengthWithContents() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(4, "mylist"); -// for (int i = 0; i < 100; i++) -// conn.Lists.AddLast(4, "mylist", new[] { (byte)i }); -// var len = conn.Lists.GetLength(4, "mylist"); - -// Assert.AreEqual(100, conn.Wait(len)); -// } -// } - -// static byte[] Encode(string value) { return Encoding.UTF8.GetBytes(value); } -// static string Decode(byte[] value) { return Encoding.UTF8.GetString(value); } -// [Test] -// public void CheckRightPush() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// conn.Keys.Remove(4, "mylist"); -// var lenNil = conn.Features.PushIfNotExists ? conn.Lists.AddLast(4, "mylist", Encode("value1"), createIfMissing: false) : null; -// var len1 = conn.Lists.AddLast(4, "mylist", Encode("value1")); -// var len2 = conn.Lists.AddLast(4, "mylist", Encode("value2")); -// var items = conn.Lists.Range(4, "mylist", 0, -1); - -// if (lenNil != null) Assert.AreEqual(0, conn.Wait(lenNil)); -// Assert.AreEqual(1, conn.Wait(len1)); -// Assert.AreEqual(2, conn.Wait(len2)); -// var arr = conn.Wait(items); -// Assert.AreEqual(2, arr.Length); -// Assert.AreEqual("value1", Decode(arr[0])); -// Assert.AreEqual("value2", Decode(arr[1])); -// } -// } -// [Test] -// public void CheckLeftPush() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// conn.Keys.Remove(4, "mylist"); -// var lenNil = conn.Features.PushIfNotExists ? conn.Lists.AddLast(4, "mylist", Encode("value1"), createIfMissing: false) : null; -// var len1 = conn.Lists.AddFirst(4, "mylist", Encode("value1")); -// var len2 = conn.Lists.AddFirst(4, "mylist", Encode("value2")); -// var items = conn.Lists.Range(4, "mylist", 0, -1); - -// if(lenNil != null) Assert.AreEqual(0, conn.Wait(lenNil)); -// Assert.AreEqual(1, conn.Wait(len1)); -// Assert.AreEqual(2, conn.Wait(len2)); -// var arr = conn.Wait(items); -// Assert.AreEqual(2, arr.Length); -// Assert.AreEqual("value2", Decode(arr[0])); -// Assert.AreEqual("value1", Decode(arr[1])); -// } -// } -// [Test] -// public void CheckLeftPop() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(4, "mylist"); -// conn.Lists.AddLast(4, "mylist", Encode("value1")); -// conn.Lists.AddLast(4, "mylist", Encode("value2")); - -// var first = conn.Lists.RemoveFirst(4, "mylist"); -// var second = conn.Lists.RemoveFirstString(4, "mylist"); -// var third = conn.Lists.RemoveFirst(4, "mylist"); -// var len = conn.Lists.GetLength(4, "mylist"); - -// Assert.AreEqual("value1", Decode(conn.Wait(first))); -// Assert.AreEqual("value2", conn.Wait(second)); -// Assert.IsNull(conn.Wait(third)); -// Assert.AreEqual(0, conn.Wait(len)); -// } -// } -// [Test] -// public void CheckRightPop() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(4, "mylist"); -// conn.Lists.AddLast(4, "mylist", Encode("value1")); -// conn.Lists.AddLast(4, "mylist", Encode("value2")); - -// var first = conn.Lists.RemoveLast(4, "mylist"); -// var second = conn.Lists.RemoveLastString(4, "mylist"); -// var third = conn.Lists.RemoveLast(4, "mylist"); -// var len = conn.Lists.GetLength(4, "mylist"); - -// Assert.AreEqual("value2", Decode(conn.Wait(first))); -// Assert.AreEqual("value1", conn.Wait(second)); -// Assert.IsNull(conn.Wait(third)); -// Assert.AreEqual(0, conn.Wait(len)); -// } -// } - - -// [Test] -// public void CheckPushPop() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(4, "source"); -// conn.Keys.Remove(4, "dest"); - -// var empty0 = conn.Lists.RemoveLastAndAddFirst(4, "source", "dest"); -// var empty1 = conn.Lists.RemoveLastAndAddFirstString(4, "source", "dest"); -// conn.Lists.AddLast(4, "source", "abc"); -// conn.Lists.AddLast(4, "source", "def"); - -// var s = conn.Lists.RemoveLastAndAddFirstString(4, "source", "dest"); -// var b = conn.Lists.RemoveLastAndAddFirst(4, "source", "dest"); -// var l0 = conn.Lists.GetLength(4, "source"); -// var l1 = conn.Lists.GetLength(4, "dest"); -// var final = conn.Lists.RangeString(4, "dest", 0, 3); - -// Assert.IsNull(conn.Wait(empty0)); -// Assert.IsNull(conn.Wait(empty1)); -// Assert.AreEqual("def", conn.Wait(s)); -// Assert.AreEqual("abc", Decode(conn.Wait(b))); -// Assert.AreEqual(0, conn.Wait(l0)); -// Assert.AreEqual(2, conn.Wait(l1)); - -// var arr = conn.Wait(final); -// Assert.AreEqual(2, arr.Length); -// Assert.AreEqual("abc", arr[0]); -// Assert.AreEqual("def", arr[1]); - - -// } -// } - - - -// [Test] -// public void GetStringFromList() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(4, "mylist"); -// var missing = conn.Lists.GetString(4, "mylist", 0); - -// conn.Lists.AddLast(4, "mylist", "abc"); -// conn.Lists.AddLast(4, "mylist", "def"); -// conn.Lists.AddLast(4, "mylist", "ghi"); - -// var x0 = conn.Lists.GetString(4, "mylist", 0); -// var x1 = conn.Lists.GetString(4, "mylist", 1); -// var x2 = conn.Lists.GetString(4, "mylist", 2); -// var x3 = conn.Lists.GetString(4, "mylist", 3); - -// var m1 = conn.Lists.GetString(4, "mylist", -1); -// var m2 = conn.Lists.GetString(4, "mylist", -2); -// var m3 = conn.Lists.GetString(4, "mylist", -3); -// var m4 = conn.Lists.GetString(4, "mylist", -4); - -// Assert.IsNull(conn.Wait(missing)); - -// Assert.AreEqual("abc", conn.Wait(x0)); -// Assert.AreEqual("def", conn.Wait(x1)); -// Assert.AreEqual("ghi", conn.Wait(x2)); -// Assert.IsNull(conn.Wait(x3)); - -// Assert.AreEqual("ghi", conn.Wait(m1)); -// Assert.AreEqual("def", conn.Wait(m2)); -// Assert.AreEqual("abc", conn.Wait(m3)); -// Assert.IsNull(conn.Wait(m4)); - -// } -// } - -// [Test] -// public void GetBytesFromList() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(4, "mylist"); -// var missing = conn.Lists.GetString(4, "mylist", 0); - -// conn.Lists.AddLast(4, "mylist", Encode("abc")); -// conn.Lists.AddLast(4, "mylist", Encode("def")); -// conn.Lists.AddLast(4, "mylist", Encode("ghi")); - -// var x0 = conn.Lists.Get(4, "mylist", 0); -// var x1 = conn.Lists.Get(4, "mylist", 1); -// var x2 = conn.Lists.Get(4, "mylist", 2); -// var x3 = conn.Lists.Get(4, "mylist", 3); - -// var m1 = conn.Lists.Get(4, "mylist", -1); -// var m2 = conn.Lists.Get(4, "mylist", -2); -// var m3 = conn.Lists.Get(4, "mylist", -3); -// var m4 = conn.Lists.Get(4, "mylist", -4); - -// Assert.IsNull(conn.Wait(missing)); - -// Assert.AreEqual("abc", Decode(conn.Wait(x0))); -// Assert.AreEqual("def", Decode(conn.Wait(x1))); -// Assert.AreEqual("ghi", Decode(conn.Wait(x2))); -// Assert.IsNull(conn.Wait(x3)); - -// Assert.AreEqual("ghi", Decode(conn.Wait(m1))); -// Assert.AreEqual("def", Decode(conn.Wait(m2))); -// Assert.AreEqual("abc", Decode(conn.Wait(m3))); -// Assert.IsNull(conn.Wait(m4)); - -// } -// } - -// [Test] -// public void TestListInsertString() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen:true)) -// { -// if (conn.Features.ListInsert) -// { -// conn.Keys.Remove(2, "ins"); -// conn.Lists.AddFirst(2, "ins", "x"); -// var missingB = conn.Lists.InsertBefore(2, "ins", "abc", "AAA"); -// var missingA = conn.Lists.InsertAfter(2, "ins", "abc", "BBB"); - -// conn.Lists.AddFirst(2, "ins", "abc"); -// conn.Lists.AddFirst(2, "ins", "y"); -// var existB = conn.Lists.InsertBefore(2, "ins", "abc", "CCC"); -// var existA = conn.Lists.InsertAfter(2, "ins", "abc", "DDD"); -// var all = conn.Lists.RangeString(2, "ins", 0, -1); -// Assert.AreEqual(-1, conn.Wait(missingB)); -// Assert.AreEqual(-1, conn.Wait(missingA)); - -// Assert.AreEqual(4, conn.Wait(existB)); -// Assert.AreEqual(5, conn.Wait(existA)); - -// string seq = string.Join(" ", conn.Wait(all)); -// Assert.AreEqual("y CCC abc DDD x", seq); -// } -// } -// } -// [Test] -// public void TestListInsertBlob() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen:true)) -// { -// if (conn.Features.ListInsert) -// { -// conn.Keys.Remove(2, "ins"); -// conn.Lists.AddFirst(2, "ins", Encode("x")); -// var missingB = conn.Lists.InsertBefore(2, "ins", Encode("abc"), Encode("AAA")); -// var missingA = conn.Lists.InsertAfter(2, "ins", Encode("abc"), Encode("BBB")); - -// conn.Lists.AddFirst(2, "ins", Encode("abc")); -// conn.Lists.AddFirst(2, "ins", Encode("y")); -// var existB = conn.Lists.InsertBefore(2, "ins", Encode("abc"), Encode("CCC")); -// var existA = conn.Lists.InsertAfter(2, "ins", Encode("abc"), Encode("DDD")); -// var all = conn.Lists.Range(2, "ins", 0, -1); -// Assert.AreEqual(-1, conn.Wait(missingB)); -// Assert.AreEqual(-1, conn.Wait(missingA)); - -// Assert.AreEqual(4, conn.Wait(existB)); -// Assert.AreEqual(5, conn.Wait(existA)); -// string seq = string.Join(" ", conn.Wait(all).Select(Decode)); -// Assert.AreEqual("y CCC abc DDD x", seq); -// } -// } -// } -// [Test] -// public void TestListByIndexString() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(2, "byindex"); -// var notExists = conn.Lists.GetString(2, "byindex", 1); -// conn.Lists.AddLast(2, "byindex", "a"); -// conn.Lists.AddLast(2, "byindex", "b"); -// conn.Lists.AddLast(2, "byindex", "c"); - -// var item = conn.Lists.GetString(2, "byindex", 1); -// var outOfRange = conn.Lists.GetString(3, "byindex", 1); -// Assert.IsNull(conn.Wait(notExists)); -// Assert.AreEqual("b", conn.Wait(item)); -// Assert.IsNull(conn.Wait(outOfRange)); -// } -// } -// [Test] -// public void TestListByIndexBlob() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(2, "byindex"); -// var notExists = conn.Lists.Get(2, "byindex", 1); -// conn.Lists.AddLast(2, "byindex", Encode("a")); -// conn.Lists.AddLast(2, "byindex", Encode("b")); -// conn.Lists.AddLast(2, "byindex", Encode("c")); - -// var item = conn.Lists.Get(2, "byindex", 1); -// var outOfRange = conn.Lists.Get(3, "byindex", 1); -// Assert.IsNull(conn.Wait(notExists)); -// Assert.AreEqual("b", Decode(conn.Wait(item))); -// Assert.IsNull(conn.Wait(outOfRange)); -// } -// } -// [Test] -// public void TestTrim() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(2, "trim"); - -// conn.Lists.Trim(2, "trim", 1); -// var ne = conn.Lists.GetLength(2, "trim"); - -// conn.Lists.AddLast(2, "trim", Encode("a")); -// conn.Lists.AddLast(2, "trim", Encode("b")); -// conn.Lists.AddLast(2, "trim", Encode("c")); - - -// conn.Lists.Trim(2, "trim", 1); -// var e = conn.Lists.GetLength(2, "trim"); - -// Assert.AreEqual(0, conn.Wait(ne)); -// Assert.AreEqual(1, conn.Wait(e)); -// } -// } -// [Test] -// public void TestSetByIndexString() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(2, "setbyindex"); - -// conn.Lists.AddLast(2, "setbyindex", "a"); -// conn.Lists.AddLast(2, "setbyindex", "b"); -// conn.Lists.AddLast(2, "setbyindex", "c"); - -// conn.Lists.Set(2, "setbyindex", 1, "d"); -// var item = conn.Lists.GetString(2, "setbyindex", 1); - -// Assert.AreEqual("d", conn.Wait(item)); -// } -// } -// [Test] -// public void TestSetByIndexBlob() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(2, "setbyindex"); - -// conn.Lists.AddLast(2, "setbyindex", Encode("a")); -// conn.Lists.AddLast(2, "setbyindex", Encode("b")); -// conn.Lists.AddLast(2, "setbyindex", Encode("c")); - -// conn.Lists.Set(2, "setbyindex", 1, Encode("d")); -// var item = conn.Lists.Get(2, "setbyindex", 1); - -// Assert.AreEqual("d", Decode(conn.Wait(item))); -// } -// } -// [Test] -// public void TestRemoveString() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(2, "remove"); -// var ne = conn.Lists.Remove(2, "remove", "b"); - -// conn.Lists.AddLast(2, "remove", "b"); -// conn.Lists.AddLast(2, "remove", "a"); -// conn.Lists.AddLast(2, "remove", "b"); -// conn.Lists.AddLast(2, "remove", "c"); -// conn.Lists.AddLast(2, "remove", "b"); - -// var e = conn.Lists.Remove(2, "remove", "b", count: 2); -// var count = conn.Lists.GetLength(2, "remove"); -// Assert.AreEqual(0, conn.Wait(ne)); -// Assert.AreEqual(2, conn.Wait(e)); -// Assert.AreEqual(3, conn.Wait(count)); -// } -// } -// [Test] -// public void TestRemoveBlob() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(2, "remove"); -// var ne = conn.Lists.Remove(2, "remove", Encode("b")); - -// conn.Lists.AddLast(2, "remove", Encode("b")); -// conn.Lists.AddLast(2, "remove", Encode("a")); -// conn.Lists.AddLast(2, "remove", Encode("b")); -// conn.Lists.AddLast(2, "remove", Encode("c")); -// conn.Lists.AddLast(2, "remove", Encode("b")); - -// var e = conn.Lists.Remove(2, "remove", Encode("b"), count: 2); -// var count = conn.Lists.GetLength(2, "remove"); -// Assert.AreEqual(0, conn.Wait(ne)); -// Assert.AreEqual(2, conn.Wait(e)); -// Assert.AreEqual(3, conn.Wait(count)); -// } -// } - -// [Test, ExpectedException(typeof(ArgumentOutOfRangeException))] -// public void TestTrimNeg() -// { -// using(var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "trim"); -// conn.Lists.AddLast(1, "trim", "x"); -// conn.Lists.AddLast(1, "trim", "x"); -// conn.Lists.Trim(1, "trim", -1); -// } -// } -// [Test] -// public void TestTrimZero() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "trim"); -// conn.Lists.AddLast(1, "trim", "x"); -// conn.Lists.AddLast(1, "trim", "x"); -// conn.Lists.Trim(1, "trim", 0); -// Assert.IsFalse(conn.Wait(conn.Keys.Exists(1, "trim"))); -// Assert.AreEqual(0, conn.Wait(conn.Lists.GetLength(1, "trim"))); -// } -// } -// [Test] -// public void TestTrimOne() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "trim"); -// conn.Lists.AddLast(1, "trim", "x"); -// conn.Lists.AddLast(1, "trim", "x"); -// conn.Lists.Trim(1, "trim", 1); -// Assert.IsTrue(conn.Wait(conn.Keys.Exists(1, "trim"))); -// Assert.AreEqual(1, conn.Wait(conn.Lists.GetLength(1, "trim"))); -// } -// } - -// [Test, ExpectedException(typeof(TimeoutException), ExpectedMessage = "The operation has timed out; possibly blocked by: 1: BLPOP \"blocking\" 5")] -// public void TestBlockingTimeout() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "blocking"); -// conn.Lists.BlockingRemoveFirst(1, new[]{"blocking"}, 5); -// var next = conn.Strings.Get(1, "foofoo"); -// conn.Wait(next); -// } -// } - -// [Test] -// public void TestLeftBlockingPop() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// using (var conn2 = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "blocking"); -// conn.Keys.Remove(1, "blocking-A"); -// conn.Keys.Remove(1, "blocking-B"); -// // empty, no data -// Assert.IsNull(conn.Lists.BlockingRemoveFirstString(1, new[] { "blocking" }, 1).Result); - -// // empty, successful blocking getting data from another client -// var found = conn.Lists.BlockingRemoveFirstString(1, new[] { "blocking" }, 1); -// var len = conn2.Lists.AddLast(1, "blocking", "another client"); -// Assert.AreEqual("blocking", conn.Wait(found).Item1); -// Assert.AreEqual("another client", conn.Wait(found).Item2); -// Assert.AreEqual(1, conn2.Wait(len)); - -// // empty, successful blocking getting data from another client, multiple keys -// found = conn.Lists.BlockingRemoveFirstString(1, new[] { "blocking-A", "blocking", "blocking-B" }, 1); -// len = conn2.Lists.AddLast(1, "blocking", "another client"); -// Assert.AreEqual("blocking", conn.Wait(found).Item1); -// Assert.AreEqual("another client", conn.Wait(found).Item2); -// Assert.AreEqual(1, conn2.Wait(len)); - -// // data, no need to block -// conn.Lists.AddLast(1, "blocking", "abc"); -// len = conn.Lists.AddLast(1, "blocking", "def"); -// var found0 = conn.Lists.BlockingRemoveFirstString(1, new[] { "blocking" }, 1); -// var found1 = conn.Lists.BlockingRemoveFirstString(1, new[] { "blocking" }, 1); -// var found2 = conn.Lists.BlockingRemoveFirstString(1, new[] { "blocking" }, 1); -// Assert.AreEqual(2, conn.Wait(len)); -// Assert.AreEqual("blocking", conn.Wait(found0).Item1); -// Assert.AreEqual("abc", conn.Wait(found0).Item2); -// Assert.AreEqual("blocking", conn.Wait(found1).Item1); -// Assert.AreEqual("def", conn.Wait(found1).Item2); -// Assert.IsNull(found2.Result); -// } -// } - -// [Test] -// public void TestRightBlockingPop() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// using (var conn2 = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "blocking"); -// conn.Keys.Remove(1, "blocking-A"); -// conn.Keys.Remove(1, "blocking-B"); -// // empty, no data -// Assert.IsNull(conn.Lists.BlockingRemoveLastString(1, new[] { "blocking" }, 1).Result); - -// // empty, successful blocking getting data from another client -// var found = conn.Lists.BlockingRemoveLastString(1, new[] { "blocking" }, 1); -// var len = conn2.Lists.AddLast(1, "blocking", "another client"); -// Assert.AreEqual("blocking", conn.Wait(found).Item1); -// Assert.AreEqual("another client", conn.Wait(found).Item2); -// Assert.AreEqual(1, conn2.Wait(len)); - -// // empty, successful blocking getting data from another client, multiple keys -// found = conn.Lists.BlockingRemoveLastString(1, new[] { "blocking-A", "blocking", "blocking-B" }, 1); -// len = conn2.Lists.AddLast(1, "blocking", "another client"); -// Assert.AreEqual("blocking", conn.Wait(found).Item1); -// Assert.AreEqual("another client", conn.Wait(found).Item2); -// Assert.AreEqual(1, conn2.Wait(len)); - -// // data, no need to block -// conn.Lists.AddLast(1, "blocking", "abc"); -// len = conn.Lists.AddLast(1, "blocking", "def"); -// var found0 = conn.Lists.BlockingRemoveLastString(1, new[] { "blocking" }, 1); -// var found1 = conn.Lists.BlockingRemoveLastString(1, new[] { "blocking" }, 1); -// var found2 = conn.Lists.BlockingRemoveLastString(1, new[] { "blocking" }, 1); -// Assert.AreEqual(2, conn.Wait(len)); -// Assert.AreEqual("blocking", conn.Wait(found0).Item1); -// Assert.AreEqual("def", conn.Wait(found0).Item2); -// Assert.AreEqual("blocking", conn.Wait(found1).Item1); -// Assert.AreEqual("abc", conn.Wait(found1).Item2); -// Assert.IsNull(found2.Result); -// } -// } - -// [Test] -// public void TestLeftBlockingPopBytes() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// using (var conn2 = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "blocking"); -// conn.Keys.Remove(1, "blocking-A"); -// conn.Keys.Remove(1, "blocking-B"); -// // empty, no data -// Assert.IsNull(conn.Lists.BlockingRemoveFirst(1, new[] { "blocking" }, 1).Result); - -// // empty, successful blocking getting data from another client -// var found = conn.Lists.BlockingRemoveFirst(1, new[] { "blocking" }, 1); -// var len = conn2.Lists.AddLast(1, "blocking", "another client"); -// Assert.AreEqual("blocking", conn.Wait(found).Item1); -// Assert.AreEqual("another client", Encoding.UTF8.GetString(conn.Wait(found).Item2)); -// Assert.AreEqual(1, conn2.Wait(len)); - -// // empty, successful blocking getting data from another client, multiple keys -// found = conn.Lists.BlockingRemoveFirst(1, new[] { "blocking-A", "blocking", "blocking-B" }, 1); -// len = conn2.Lists.AddLast(1, "blocking", "another client"); -// Assert.AreEqual("blocking", conn.Wait(found).Item1); -// Assert.AreEqual("another client", Encoding.UTF8.GetString(conn.Wait(found).Item2)); -// Assert.AreEqual(1, conn2.Wait(len)); - -// // data, no need to block -// conn.Lists.AddLast(1, "blocking", "abc"); -// len = conn.Lists.AddLast(1, "blocking", "def"); -// var found0 = conn.Lists.BlockingRemoveFirst(1, new[] { "blocking" }, 1); -// var found1 = conn.Lists.BlockingRemoveFirst(1, new[] { "blocking" }, 1); -// var found2 = conn.Lists.BlockingRemoveFirst(1, new[] { "blocking" }, 1); -// Assert.AreEqual(2, conn.Wait(len)); -// Assert.AreEqual("blocking", conn.Wait(found0).Item1); -// Assert.AreEqual("abc", Encoding.UTF8.GetString(conn.Wait(found0).Item2)); -// Assert.AreEqual("blocking", conn.Wait(found1).Item1); -// Assert.AreEqual("def", Encoding.UTF8.GetString(conn.Wait(found1).Item2)); -// Assert.IsNull(found2.Result); -// } -// } - -// [Test] -// public void TestRightBlockingPopBytes() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// using (var conn2 = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "blocking"); -// conn.Keys.Remove(1, "blocking-A"); -// conn.Keys.Remove(1, "blocking-B"); -// // empty, no data -// Assert.IsNull(conn.Lists.BlockingRemoveLast(1, new[] { "blocking" }, 1).Result); - -// // empty, successful blocking getting data from another client -// var found = conn.Lists.BlockingRemoveLast(1, new[] { "blocking" }, 1); -// var len = conn2.Lists.AddLast(1, "blocking", "another client"); -// Assert.AreEqual("blocking", conn.Wait(found).Item1); -// Assert.AreEqual("another client", Encoding.UTF8.GetString(conn.Wait(found).Item2)); -// Assert.AreEqual(1, conn2.Wait(len)); - -// // empty, successful blocking getting data from another client, multiple keys -// found = conn.Lists.BlockingRemoveLast(1, new[] { "blocking-A", "blocking", "blocking-B" }, 1); -// len = conn2.Lists.AddLast(1, "blocking", "another client"); -// Assert.AreEqual("blocking", conn.Wait(found).Item1); -// Assert.AreEqual("another client", Encoding.UTF8.GetString(conn.Wait(found).Item2)); -// Assert.AreEqual(1, conn2.Wait(len)); - -// // data, no need to block -// conn.Lists.AddLast(1, "blocking", "abc"); -// len = conn.Lists.AddLast(1, "blocking", "def"); -// var found0 = conn.Lists.BlockingRemoveLast(1, new[] { "blocking" }, 1); -// var found1 = conn.Lists.BlockingRemoveLast(1, new[] { "blocking" }, 1); -// var found2 = conn.Lists.BlockingRemoveLast(1, new[] { "blocking" }, 1); -// Assert.AreEqual(2, conn.Wait(len)); -// Assert.AreEqual("blocking", conn.Wait(found0).Item1); -// Assert.AreEqual("def", Encoding.UTF8.GetString(conn.Wait(found0).Item2)); -// Assert.AreEqual("blocking", conn.Wait(found1).Item1); -// Assert.AreEqual("abc", Encoding.UTF8.GetString(conn.Wait(found1).Item2)); -// Assert.IsNull(found2.Result); -// } -// } - -// [Test] -// public void TestBlockingPopPush() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// using (var conn2 = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "source"); -// conn.Keys.Remove(1, "target"); - -// // empty -// var found = conn.Lists.BlockingRemoveLastAndAddFirstString(1, "source", "target", 1); -// Assert.IsNull(found.Result); - -// // already data -// conn.Lists.AddLast(1, "source", "abc"); -// conn.Lists.AddLast(1, "source", "def"); -// var found0 = conn.Lists.BlockingRemoveLastAndAddFirstString(1, "source", "target", 1); -// var found1 = conn.Lists.BlockingRemoveLastAndAddFirstString(1, "source", "target", 1); -// var found2 = conn.Lists.BlockingRemoveLastAndAddFirstString(1, "source", "target", 1); -// Assert.AreEqual("def", found0.Result); -// Assert.AreEqual("abc", found1.Result); -// Assert.IsNull(found2.Result); - -// // add data from another client -// conn.Lists.AddFirst(1, "source", "abc"); -// found0 = conn.Lists.BlockingRemoveLastAndAddFirstString(1, "source", "target", 1); -// found1 = conn.Lists.BlockingRemoveLastAndAddFirstString(1, "source", "target", 1); -// found2 = conn.Lists.BlockingRemoveLastAndAddFirstString(1, "source", "target", 1); -// var found3 = conn.Lists.BlockingRemoveLastAndAddFirstString(1, "source", "target", 1); -// conn2.Lists.AddFirst(1, "source", "def"); -// conn2.Lists.AddFirst(1, "source", "ghi"); -// Assert.AreEqual("abc", found0.Result); -// Assert.AreEqual("def", found1.Result); -// Assert.AreEqual("ghi", found2.Result); -// Assert.IsNull(found3.Result); -// } -// } -// [Test] -// public void TestBlockingPopPushBytes() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// using (var conn2 = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "source"); -// conn.Keys.Remove(1, "target"); - -// // empty -// var found = conn.Lists.BlockingRemoveLastAndAddFirst(1, "source", "target", 1); -// Assert.IsNull(found.Result); - -// // already data -// conn.Lists.AddLast(1, "source", "abc"); -// conn.Lists.AddLast(1, "source", "def"); -// var found0 = conn.Lists.BlockingRemoveLastAndAddFirst(1, "source", "target", 1); -// var found1 = conn.Lists.BlockingRemoveLastAndAddFirst(1, "source", "target", 1); -// var found2 = conn.Lists.BlockingRemoveLastAndAddFirst(1, "source", "target", 1); -// Assert.AreEqual("def", Encoding.UTF8.GetString(found0.Result)); -// Assert.AreEqual("abc", Encoding.UTF8.GetString(found1.Result)); -// Assert.IsNull(found2.Result); - -// // add data from another client -// conn.Lists.AddFirst(1, "source", "abc"); -// found0 = conn.Lists.BlockingRemoveLastAndAddFirst(1, "source", "target", 1); -// found1 = conn.Lists.BlockingRemoveLastAndAddFirst(1, "source", "target", 1); -// found2 = conn.Lists.BlockingRemoveLastAndAddFirst(1, "source", "target", 1); -// var found3 = conn.Lists.BlockingRemoveLastAndAddFirst(1, "source", "target", 1); -// Thread.Sleep(100); // make sure those commands at least get queued before conn2 starts monkeying - -// conn2.Lists.AddFirst(1, "source", "def"); -// conn2.Lists.AddFirst(1, "source", "ghi"); -// Assert.AreEqual("abc", Encoding.UTF8.GetString(found0.Result)); -// Assert.AreEqual("def", Encoding.UTF8.GetString(found1.Result)); -// Assert.AreEqual("ghi", Encoding.UTF8.GetString(found2.Result)); -// Assert.IsNull(found3.Result); -// } -// } -// } -//} diff --git a/MigratedBookSleeveTestSuite/Locking.cs b/MigratedBookSleeveTestSuite/Locking.cs deleted file mode 100644 index 21b178e01..000000000 --- a/MigratedBookSleeveTestSuite/Locking.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using NUnit.Framework; -using StackExchange.Redis; - -namespace Tests -{ - [TestFixture] - public class Locking - { - [Test] - public void AggressiveParallel() - { - int count = 2; - int errorCount = 0; - ManualResetEvent evt = new ManualResetEvent(false); - using (var c1 = Config.GetUnsecuredConnection(waitForOpen: true)) - using (var c2 = Config.GetUnsecuredConnection(waitForOpen: true)) - { - WaitCallback cb = obj => - { - var conn = (ConnectionMultiplexer)obj; - conn.InternalError += delegate { Interlocked.Increment(ref errorCount); }; - conn.ErrorMessage += delegate { Interlocked.Increment(ref errorCount); }; - var db = conn.GetDatabase(2); - for (int i = 0; i < 1000; i++) - { - db.LockTake("abc", "def", TimeSpan.FromSeconds(5)); - } - db.Ping(); - conn.Close(false); - if (Interlocked.Decrement(ref count) == 0) evt.Set(); - }; - ThreadPool.QueueUserWorkItem(cb, c1); - ThreadPool.QueueUserWorkItem(cb, c2); - evt.WaitOne(8000); - } - Assert.AreEqual(0, Interlocked.CompareExchange(ref errorCount, 0, 0)); - } - - [Test] - public void TestOpCountByVersionLocal_UpLevel() - { - using (var conn = Config.GetUnsecuredConnection(open: false)) - { - TestLockOpCountByVersion(conn, 1, false); - TestLockOpCountByVersion(conn, 1, true); - //TestManualLockOpCountByVersion(conn, 5, false); - //TestManualLockOpCountByVersion(conn, 3, true); - } - } - //[Test] - //public void TestOpCountByVersionLocal_DownLevel() - //{ - // var config = new ConfigurationOptions - // { - // EndPoints = { { Config.LocalHost } }, - // DefaultVersion = new Version(2, 6, 0), - // CommandMap = CommandMap.Create( - // new HashSet { "info" }, false) - // }; - // using (var conn = ConnectionMultiplexer.Connect(config)) - // { - // TestLockOpCountByVersion(conn, 5, false); - // TestLockOpCountByVersion(conn, 3, true); - // //TestManualLockOpCountByVersion(conn, 5, false); - // //TestManualLockOpCountByVersion(conn, 3, true); - // } - //} - - //[Test] - //public void TestOpCountByVersionRemote() - //{ - // using (var conn = Config.GetRemoteConnection(open: false)) - // { - // TestLockOpCountByVersion(conn, 1, false); - // TestLockOpCountByVersion(conn, 1, true); - // //TestManualLockOpCountByVersion(conn, 1, false); - // //TestManualLockOpCountByVersion(conn, 1, true); - // } - //} - public void TestLockOpCountByVersion(ConnectionMultiplexer conn, int expected, bool existFirst) - { - const int DB = 0, LockDuration = 30; - const string Key = "TestOpCountByVersion"; - - var db = conn.GetDatabase(DB); - db.KeyDelete(Key); - var newVal = "us:" + Config.CreateUniqueName(); - string expectedVal = newVal; - if (existFirst) - { - expectedVal = "other:" + Config.CreateUniqueName(); - db.StringSet(Key, expectedVal, TimeSpan.FromSeconds(LockDuration)); - } - long countBefore = conn.GetCounters().Interactive.OperationCount; - var taken = db.LockTake(Key, newVal, TimeSpan.FromSeconds(LockDuration)); - long countAfter = conn.GetCounters().Interactive.OperationCount; - string valAfter = db.StringGet(Key); - Assert.AreEqual(!existFirst, taken, "lock taken"); - Assert.AreEqual(expectedVal, valAfter, "taker"); - Console.WriteLine("{0} ops before, {1} ops after", countBefore, countAfter); - Assert.AreEqual(expected, (countAfter - countBefore), "expected ops (including ping)"); - // note we get a ping from GetCounters - } - - [Test] - public void TakeLockAndExtend() - { - using (var conn = Config.GetUnsecuredConnection()) - { - string right = Guid.NewGuid().ToString(), - wrong = Guid.NewGuid().ToString(); - - const int DB = 7; - const string Key = "lock-key"; - - //conn.SuspendFlush(); - var db = conn.GetDatabase(DB); - - db.KeyDelete(Key); - var t1 = db.LockTakeAsync(Key, right, TimeSpan.FromSeconds(20)); - var t1b = db.LockTakeAsync(Key, wrong, TimeSpan.FromSeconds(10)); - var t2 = db.StringGetAsync(Key); - var t3 = db.LockReleaseAsync(Key, wrong); - var t4 = db.StringGetAsync(Key); - var t5 = db.LockExtendAsync(Key, wrong, TimeSpan.FromSeconds(60)); - var t6 = db.StringGetAsync(Key); - var t7 = db.KeyTimeToLiveAsync(Key); - var t8 = db.LockExtendAsync(Key, right, TimeSpan.FromSeconds(60)); - var t9 = db.StringGetAsync(Key); - var t10 = db.KeyTimeToLiveAsync(Key); - var t11 = db.LockReleaseAsync(Key, right); - var t12 = db.StringGetAsync(Key); - var t13 = db.LockTakeAsync(Key, wrong, TimeSpan.FromSeconds(10)); - - Assert.IsNotNull(right); - Assert.IsNotNull(wrong); - Assert.AreNotEqual(right, (string)wrong); - Assert.IsTrue(conn.Wait(t1), "1"); - Assert.IsFalse(conn.Wait(t1b), "1b"); - Assert.AreEqual(right, (string)conn.Wait(t2), "2"); - Assert.IsFalse(conn.Wait(t3), "3"); - Assert.AreEqual(right, (string)conn.Wait(t4), "4"); - Assert.IsFalse(conn.Wait(t5), "5"); - Assert.AreEqual(right, (string)conn.Wait(t6), "6"); - var ttl = conn.Wait(t7).Value.TotalSeconds; - Assert.IsTrue(ttl > 0 && ttl <= 20, "7"); - Assert.IsTrue(conn.Wait(t8), "8"); - Assert.AreEqual(right, (string)conn.Wait(t9), "9"); - ttl = conn.Wait(t10).Value.TotalSeconds; - Assert.IsTrue(ttl > 50 && ttl <= 60, "10"); - Assert.IsTrue(conn.Wait(t11), "11"); - Assert.IsNull((string)conn.Wait(t12), "12"); - Assert.IsTrue(conn.Wait(t13), "13"); - } - } - - - ////public void TestManualLockOpCountByVersion(RedisConnection conn, int expected, bool existFirst) - ////{ - //// const int DB = 0, LockDuration = 30; - //// const string Key = "TestManualLockOpCountByVersion"; - //// conn.Wait(conn.Open()); - //// conn.Keys.Remove(DB, Key); - //// var newVal = "us:" + Config.CreateUniqueName(); - //// string expectedVal = newVal; - //// if (existFirst) - //// { - //// expectedVal = "other:" + Config.CreateUniqueName(); - //// conn.Strings.Set(DB, Key, expectedVal, LockDuration); - //// } - //// int countBefore = conn.GetCounters().MessagesSent; - - //// var tran = conn.CreateTransaction(); - //// tran.AddCondition(Condition.KeyNotExists(DB, Key)); - //// tran.Strings.Set(DB, Key, newVal, LockDuration); - //// var taken = conn.Wait(tran.Execute()); - - //// int countAfter = conn.GetCounters().MessagesSent; - //// var valAfter = conn.Wait(conn.Strings.GetString(DB, Key)); - //// Assert.AreEqual(!existFirst, taken, "lock taken (manual)"); - //// Assert.AreEqual(expectedVal, valAfter, "taker (manual)"); - //// Assert.AreEqual(expected, (countAfter - countBefore) - 1, "expected ops (including ping) (manual)"); - //// // note we get a ping from GetCounters - ////} - - - - [Test] - public void TestBasicLockNotTaken() - { - using (var conn = Config.GetUnsecuredConnection()) - { - int errorCount = 0; - - conn.InternalError += delegate { Interlocked.Increment(ref errorCount); }; - - var db = conn.GetDatabase(0); - Task taken = null; - Task newValue = null; - Task ttl = null; - - const int LOOP = 50; - for (int i = 0; i < LOOP; i++) - { - db.KeyDeleteAsync("lock-not-exists"); - taken = db.LockTakeAsync("lock-not-exists", "new-value", TimeSpan.FromSeconds(10)); - newValue = db.StringGetAsync("lock-not-exists"); - ttl = db.KeyTimeToLiveAsync("lock-not-exists"); - } - Assert.IsTrue(conn.Wait(taken), "taken"); - Assert.AreEqual("new-value", (string)conn.Wait(newValue)); - var ttlValue = conn.Wait(ttl).Value.TotalSeconds; - Assert.IsTrue(ttlValue >= 8 && ttlValue <= 10, "ttl"); - - Assert.AreEqual(0, errorCount); - } - } - - [Test] - public void TestBasicLockTaken() - { - using (var conn = Config.GetUnsecuredConnection()) - { - var db = conn.GetDatabase(0); - db.KeyDelete("lock-exists"); - db.StringSet("lock-exists", "old-value", TimeSpan.FromSeconds(20)); - var taken = db.LockTakeAsync("lock-exists", "new-value", TimeSpan.FromSeconds(10)); - var newValue = db.StringGetAsync("lock-exists"); - var ttl = db.KeyTimeToLiveAsync("lock-exists"); - - Assert.IsFalse(conn.Wait(taken), "taken"); - Assert.AreEqual("old-value", (string)conn.Wait(newValue)); - var ttlValue = conn.Wait(ttl).Value.TotalSeconds; - Assert.IsTrue(ttlValue >= 18 && ttlValue <= 20, "ttl"); - } - } - } -} diff --git a/MigratedBookSleeveTestSuite/MigratedBookSleeveTestSuite.csproj b/MigratedBookSleeveTestSuite/MigratedBookSleeveTestSuite.csproj deleted file mode 100644 index 2fab71e24..000000000 --- a/MigratedBookSleeveTestSuite/MigratedBookSleeveTestSuite.csproj +++ /dev/null @@ -1,56 +0,0 @@ - - - - MigratedBookSleeveTestSuite - net45;netcoreapp1.0 - MigratedBookSleeveTestSuite - MigratedBookSleeveTestSuite - false - true - $(PackageTargetFallback);portable-net45+win8 - 1.0.4 - false - false - false - false - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - $(DefineConstants);PLAT_SAFE_CONTINUATIONS;CORE_CLR - - - - - - - - - - - - - - - diff --git a/MigratedBookSleeveTestSuite/Performance.cs b/MigratedBookSleeveTestSuite/Performance.cs deleted file mode 100644 index 0b17549e2..000000000 --- a/MigratedBookSleeveTestSuite/Performance.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Diagnostics; -using System.Text; -using System.Threading.Tasks; -using NUnit.Framework; -using StackExchange.Redis; - -namespace Tests -{ - [TestFixture] - public class Performance - { - [Test] - public void VerifyPerformanceImprovement() - { - int asyncTimer, sync, op = 0, asyncFaF, syncFaF; - using (var muxer= Config.GetUnsecuredConnection()) - { - // do these outside the timings, just to ensure the core methods are JITted etc - for (int db = 0; db < 5; db++) - { - muxer.GetDatabase(db).KeyDeleteAsync("perftest"); - } - - var timer = Stopwatch.StartNew(); - for (int i = 0; i < 100; i++) - { - // want to test multiplex scenario; test each db, but to make it fair we'll - // do in batches of 10 on each - for (int db = 0; db < 5; db++) - { - var conn = muxer.GetDatabase(db); - for (int j = 0; j < 10; j++) - conn.StringIncrementAsync("perftest"); - } - } - asyncFaF = (int)timer.ElapsedMilliseconds; - Task[] final = new Task[5]; - for (int db = 0; db < 5; db++) - final[db] = muxer.GetDatabase(db).StringGetAsync("perftest"); - muxer.WaitAll(final); - timer.Stop(); - asyncTimer = (int)timer.ElapsedMilliseconds; - Console.WriteLine("async to completion (local): {0}ms", timer.ElapsedMilliseconds); - for (int db = 0; db < 5; db++) - Assert.AreEqual(1000, (long)final[db].Result, "async, db:" + db); - } - - using (var conn = new Redis(Config.LocalHost, 6379)) - { - // do these outside the timings, just to ensure the core methods are JITted etc - for (int db = 0; db < 5; db++) - { - conn.Db = db; - conn.Remove("perftest"); - } - - var timer = Stopwatch.StartNew(); - for (int i = 0; i < 100; i++) - { - // want to test multiplex scenario; test each db, but to make it fair we'll - // do in batches of 10 on each - for (int db = 0; db < 5; db++) - { - conn.Db = db; - op++; - for (int j = 0; j < 10; j++) - { - conn.Increment("perftest"); - op++; - } - } - } - syncFaF = (int)timer.ElapsedMilliseconds; - string[] final = new string[5]; - for (int db = 0; db < 5; db++) - { - conn.Db = db; - final[db] = Encoding.ASCII.GetString(conn.Get("perftest")); - } - timer.Stop(); - sync = (int)timer.ElapsedMilliseconds; - Console.WriteLine("sync to completion (local): {0}ms", timer.ElapsedMilliseconds); - for (int db = 0; db < 5; db++) - Assert.AreEqual("1000", final[db], "async, db:" + db); - } - int effectiveAsync = ((10 * asyncTimer) + 3) / 10; - int effectiveSync = ((10 * sync) + (op * 3)) / 10; - Console.WriteLine("async to completion with assumed 0.3ms LAN latency: " + effectiveAsync); - Console.WriteLine("sync to completion with assumed 0.3ms LAN latency: " + effectiveSync); - Console.WriteLine("fire-and-forget: {0}ms sync vs {1}ms async ", syncFaF, asyncFaF); - Assert.Less(effectiveAsync, effectiveSync, "Everything"); - Assert.Less(asyncFaF, syncFaF, "Fire and Forget"); - } - } -} diff --git a/MigratedBookSleeveTestSuite/Program.cs b/MigratedBookSleeveTestSuite/Program.cs deleted file mode 100644 index 0d204d272..000000000 --- a/MigratedBookSleeveTestSuite/Program.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace Tests -{ - class Program - { - // static void Main() - // { - // try - // { - // Main2(); - // } - // catch (Exception ex) - // { - // Console.WriteLine(); - // Console.WriteLine("CRAZY ERRORS: " + ex); - // } - // finally - // { - // Console.WriteLine("Press any key to exit"); - // Console.ReadKey(); - // } - // } - static void Main2() - { -#if !CORE_CLR - // why is this here? because some dumbass forgot to install a decent test-runner before going to the airport - var epicFail = new List(); - var testTypes = from type in typeof(Program).Assembly.GetTypes() - where Attribute.IsDefined(type, typeof(TestFixtureAttribute)) - && !Attribute.IsDefined(type, typeof(IgnoreAttribute)) - let methods = type.GetMethods() - select new - { - Type = type, - Methods = methods, - ActiveMethods = methods.Where(x => Attribute.IsDefined(x, typeof(ActiveTestAttribute))).ToArray(), - Setup = methods.SingleOrDefault(x => Attribute.IsDefined(x, typeof(OneTimeSetUpAttribute))), - TearDown = methods.SingleOrDefault(x => Attribute.IsDefined(x, typeof(OneTimeTearDownAttribute))) - }; - int pass = 0, fail = 0; - - bool activeOnly = testTypes.SelectMany(x => x.ActiveMethods).Any(); - - TaskScheduler.UnobservedTaskException += (sender, args) => - { - args.SetObserved(); - //if (args.Exception is AggregateException) - //{ - // foreach (var ex in ((AggregateException)args.Exception).InnerExceptions) - // { - // Console.WriteLine(ex.Message); - // } - //} - //else - //{ - // Console.WriteLine(args.Exception.Message); - //} - }; - - foreach (var type in testTypes) - { - var tests = (from method in (activeOnly ? type.ActiveMethods : type.Methods) - where Attribute.IsDefined(method, typeof(TestAttribute)) - && !Attribute.IsDefined(method, typeof(IgnoreAttribute)) - select method).ToArray(); - - if (tests.Length == 0) continue; - - Console.WriteLine(type.Type.FullName); - object obj; - try - { - obj = Activator.CreateInstance(type.Type); - if (obj == null) throw new InvalidOperationException("the world has gone mad"); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - continue; - } - using (obj as IDisposable) - { - if (type.Setup != null) - { - try - { type.Setup.Invoke(obj, null); } - catch (Exception ex) - { - Console.WriteLine("Test fixture startup failed: " + ex.Message); - fail++; - epicFail.Add(type.Setup.DeclaringType.FullName + "." + type.Setup.Name); - continue; - } - } - - foreach (var test in tests) - { - Console.Write(test.Name + ": "); - Exception err = null; - - try - { - int count = 1; - if (activeOnly) - { - var ata = test.GetCustomAttribute(typeof(ActiveTestAttribute)) as ActiveTestAttribute; - if (ata != null) count = ata.Count; - } - while (count-- > 0) - { - test.Invoke(obj, null); - } - } - catch (TargetInvocationException ex) - { - err = ex.InnerException; - } - catch (Exception ex) - { - err = ex; - } - - if (err is AggregateException && ((AggregateException)err).InnerExceptions.Count == 1) - { - err = ((AggregateException)err).InnerExceptions[0]; - } - - if (err == null) - { - Console.WriteLine("pass"); - pass++; - } - else - { - Console.WriteLine(err.Message); - fail++; - epicFail.Add(test.DeclaringType.FullName + "." + test.Name); - } - } - if (type.TearDown != null) - { - try - { type.TearDown.Invoke(obj, null); } - catch (Exception ex) - { - Console.WriteLine("Test fixture teardown failed: " + ex.Message); - fail++; - epicFail.Add(type.TearDown.DeclaringType.FullName + "." + type.TearDown.Name); - } - } - } - } - Console.WriteLine("Passed: {0}; Failed: {1}", pass, fail); - foreach (var msg in epicFail) Console.WriteLine(msg); -//#if DEBUG -// Console.WriteLine(); -// Console.WriteLine("Callbacks: {0:###,###,##0} sync, {1:###,###,##0} async", -// BookSleeve.RedisConnectionBase.AllSyncCallbacks, BookSleeve.RedisConnectionBase.AllAsyncCallbacks); -//#endif - -#endif - } - } -} - -[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] -public sealed class ActiveTestAttribute : Attribute -{ - public int Count { get; } - public ActiveTestAttribute() : this(1) { } - public ActiveTestAttribute(int count) { this.Count = count; } -} \ No newline at end of file diff --git a/MigratedBookSleeveTestSuite/Properties/AssemblyInfo.cs b/MigratedBookSleeveTestSuite/Properties/AssemblyInfo.cs deleted file mode 100644 index 9dc391d1f..000000000 --- a/MigratedBookSleeveTestSuite/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("MigratedBookSleeveTestSuite")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("MigratedBookSleeveTestSuite")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("350d3b30-78dd-4b74-a76d-bb593a05e8d1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MigratedBookSleeveTestSuite/PubSub.cs b/MigratedBookSleeveTestSuite/PubSub.cs deleted file mode 100644 index c6af2cb86..000000000 --- a/MigratedBookSleeveTestSuite/PubSub.cs +++ /dev/null @@ -1,269 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using NUnit.Framework; -using StackExchange.Redis; - -namespace Tests -{ - [TestFixture] - public class PubSub // http://redis.io/commands#pubsub - { - [Test] - public void TestPublishWithNoSubscribers() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetSubscriber(); - Assert.AreEqual(0, conn.Publish("channel", "message")); - } - } - - [Test] - public void TestMassivePublishWithWithoutFlush_Local() - { - using (var muxer = Config.GetUnsecuredConnection(waitForOpen: true)) - { - var conn = muxer.GetSubscriber(); - TestMassivePublish(conn, "local"); - } - } - [Test] - public void TestMassivePublishWithWithoutFlush_Remote() - { - using (var muxer = Config.GetRemoteConnection(waitForOpen: true)) - { - var conn = muxer.GetSubscriber(); - TestMassivePublish(conn, "remote"); - } - } - - private void TestMassivePublish(ISubscriber conn, string caption) - { - const int loop = 100000; - - GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); - GC.WaitForPendingFinalizers(); - - var tasks = new Task[loop]; - - var withFAF = Stopwatch.StartNew(); - for (int i = 0; i < loop; i++) - conn.Publish("foo", "bar", CommandFlags.FireAndForget); - withFAF.Stop(); - - GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); - GC.WaitForPendingFinalizers(); - - var withAsync = Stopwatch.StartNew(); - for (int i = 0; i < loop; i++) - tasks[i] = conn.PublishAsync("foo", "bar"); - conn.WaitAll(tasks); - withAsync.Stop(); - - Assert.Less(1, 2, "sanity check"); - Assert.Less(withFAF.ElapsedMilliseconds, withAsync.ElapsedMilliseconds, caption); - Console.WriteLine("{2}: {0}ms (F+F) vs {1}ms (async)", - withFAF.ElapsedMilliseconds, withAsync.ElapsedMilliseconds, caption); - } - - - [Test] - public void PubSubOrder() - { - using (var muxer = Config.GetRemoteConnection(waitForOpen: true)) - { - var sub = muxer.GetSubscriber(); - string channel = "PubSubOrder"; - const int count = 500000; - object syncLock = new object(); - - List data = new List(count); - muxer.PreserveAsyncOrder = true; - sub.SubscribeAsync(channel, (key, val) => - { - bool pulse; - lock (data) - { - data.Add(int.Parse(Encoding.UTF8.GetString(val))); - pulse = data.Count == count; - if ((data.Count % 10) == 99) Console.WriteLine(data.Count); - } - if (pulse) - lock (syncLock) - Monitor.PulseAll(syncLock); - }).Wait(); - - lock (syncLock) - { - for (int i = 0; i < count; i++) - { - sub.Publish(channel, i.ToString(), CommandFlags.FireAndForget); - } - sub.Ping(); - if (!Monitor.Wait(syncLock, 10000)) - { - throw new TimeoutException("Items: " + data.Count); - } - for (int i = 0; i < count; i++) - Assert.AreEqual(i, data[i]); - } - } - - } - - [Test] - public void TestPublishWithSubscribers() - { - using (var muxerA = Config.GetUnsecuredConnection()) - using (var muxerB = Config.GetUnsecuredConnection()) - using (var conn = Config.GetUnsecuredConnection()) - { - var listenA = muxerA.GetSubscriber(); - var listenB = muxerB.GetSubscriber(); - var t1 = listenA.SubscribeAsync("channel", delegate { }); - var t2 = listenB.SubscribeAsync("channel", delegate { }); - - listenA.Wait(t1); - listenB.Wait(t2); - - var pub = conn.GetSubscriber().PublishAsync("channel", "message"); - Assert.AreEqual(2, conn.Wait(pub), "delivery count"); - } - } - - [Test] - public void TestMultipleSubscribersGetMessage() - { - using (var muxerA = Config.GetUnsecuredConnection()) - using (var muxerB = Config.GetUnsecuredConnection()) - using (var conn = Config.GetUnsecuredConnection()) - { - var listenA = muxerA.GetSubscriber(); - var listenB = muxerB.GetSubscriber(); - conn.GetDatabase().Ping(); - var pub = conn.GetSubscriber(); - int gotA = 0, gotB = 0; - var tA = listenA.SubscribeAsync("channel", (s, msg) => { if (msg == "message") Interlocked.Increment(ref gotA); }); - var tB = listenB.SubscribeAsync("channel", (s, msg) => { if (msg == "message") Interlocked.Increment(ref gotB); }); - listenA.Wait(tA); - listenB.Wait(tB); - Assert.AreEqual(2, pub.Publish("channel", "message")); - AllowReasonableTimeToPublishAndProcess(); - Assert.AreEqual(1, Interlocked.CompareExchange(ref gotA, 0, 0)); - Assert.AreEqual(1, Interlocked.CompareExchange(ref gotB, 0, 0)); - - // and unsubscibe... - tA = listenA.UnsubscribeAsync("channel"); - listenA.Wait(tA); - Assert.AreEqual(1, pub.Publish("channel", "message")); - AllowReasonableTimeToPublishAndProcess(); - Assert.AreEqual(1, Interlocked.CompareExchange(ref gotA, 0, 0)); - Assert.AreEqual(2, Interlocked.CompareExchange(ref gotB, 0, 0)); - } - } - - [Test] - public void Issue38() - { // https://code.google.com/p/booksleeve/issues/detail?id=38 - - using (var pub = Config.GetUnsecuredConnection(waitForOpen: true)) - { - var sub = pub.GetSubscriber(); - int count = 0; - Action handler = (channel, payload) => Interlocked.Increment(ref count); - var a0 = sub.SubscribeAsync("foo", handler); - var a1 = sub.SubscribeAsync("bar", handler); - var b0 = sub.SubscribeAsync("f*o", handler); - var b1 = sub.SubscribeAsync("b*r", handler); - sub.WaitAll(a0, a1, b0, b1); - - var c = sub.PublishAsync("foo", "foo"); - var d = sub.PublishAsync("f@o", "f@o"); - var e = sub.PublishAsync("bar", "bar"); - var f = sub.PublishAsync("b@r", "b@r"); - - pub.WaitAll(c, d, e, f); - long total = c.Result + d.Result + e.Result + f.Result; - - AllowReasonableTimeToPublishAndProcess(); - - Assert.AreEqual(6, total, "sent"); - Assert.AreEqual(6, Interlocked.CompareExchange(ref count, 0, 0), "received"); - - - } - } - - internal static void AllowReasonableTimeToPublishAndProcess() - { - Thread.Sleep(50); - } - - [Test] - public void TestPartialSubscriberGetMessage() - { - using (var muxerA = Config.GetUnsecuredConnection()) - using (var muxerB = Config.GetUnsecuredConnection()) - using (var conn = Config.GetUnsecuredConnection()) - { - int gotA = 0, gotB = 0; - var listenA = muxerA.GetSubscriber(); - var listenB = muxerB.GetSubscriber(); - var pub = conn.GetSubscriber(); - var tA = listenA.SubscribeAsync("channel", (s, msg) => { if (s == "channel" && msg == "message") Interlocked.Increment(ref gotA); }); - var tB = listenB.SubscribeAsync("chann*", (s, msg) => { if (s == "channel" && msg == "message") Interlocked.Increment(ref gotB); }); - listenA.Wait(tA); - listenB.Wait(tB); - Assert.AreEqual(2, pub.Publish("channel", "message")); - AllowReasonableTimeToPublishAndProcess(); - Assert.AreEqual(1, Interlocked.CompareExchange(ref gotA, 0, 0)); - Assert.AreEqual(1, Interlocked.CompareExchange(ref gotB, 0, 0)); - - // and unsubscibe... - tB = listenB.UnsubscribeAsync("chann*", null); - listenB.Wait(tB); - Assert.AreEqual(1, pub.Publish("channel", "message")); - AllowReasonableTimeToPublishAndProcess(); - Assert.AreEqual(2, Interlocked.CompareExchange(ref gotA, 0, 0)); - Assert.AreEqual(1, Interlocked.CompareExchange(ref gotB, 0, 0)); - } - } - - [Test] - public void TestSubscribeUnsubscribeAndSubscribeAgain() - { - using (var pubMuxer = Config.GetUnsecuredConnection()) - using (var subMuxer = Config.GetUnsecuredConnection()) - { - var pub = pubMuxer.GetSubscriber(); - var sub = subMuxer.GetSubscriber(); - int x = 0, y = 0; - var t1 = sub.SubscribeAsync("abc", delegate { Interlocked.Increment(ref x); }); - var t2 = sub.SubscribeAsync("ab*", delegate { Interlocked.Increment(ref y); }); - sub.WaitAll(t1, t2); - pub.Publish("abc", ""); - AllowReasonableTimeToPublishAndProcess(); - Assert.AreEqual(1, Volatile.Read(ref x)); - Assert.AreEqual(1, Volatile.Read(ref y)); - t1 = sub.UnsubscribeAsync("abc", null); - t2 = sub.UnsubscribeAsync("ab*", null); - sub.WaitAll(t1, t2); - pub.Publish("abc", ""); - Assert.AreEqual(1, Volatile.Read(ref x)); - Assert.AreEqual(1, Volatile.Read(ref y)); - t1 = sub.SubscribeAsync("abc", delegate { Interlocked.Increment(ref x); }); - t2 = sub.SubscribeAsync("ab*", delegate { Interlocked.Increment(ref y); }); - sub.WaitAll(t1, t2); - pub.Publish("abc", ""); - AllowReasonableTimeToPublishAndProcess(); - Assert.AreEqual(2, Volatile.Read(ref x)); - Assert.AreEqual(2, Volatile.Read(ref y)); - - } - } - } -} diff --git a/MigratedBookSleeveTestSuite/Scripting.cs b/MigratedBookSleeveTestSuite/Scripting.cs deleted file mode 100644 index dff281bd4..000000000 --- a/MigratedBookSleeveTestSuite/Scripting.cs +++ /dev/null @@ -1,369 +0,0 @@ -using System; -using System.Diagnostics; -using System.Linq; -using System.Text; -using NUnit.Framework; -using StackExchange.Redis; - -namespace Tests -{ - [TestFixture] - public class Scripting - { - static ConnectionMultiplexer GetScriptConn(bool allowAdmin = false) - { - int syncTimeout = 5000; - if (Debugger.IsAttached) syncTimeout = 500000; - var muxer = Config.GetUnsecuredConnection(waitForOpen: true, allowAdmin: allowAdmin, syncTimeout: syncTimeout); - if (!Config.GetFeatures(muxer).Scripting) - { - Assert.Inconclusive("The server does not support scripting"); - } - return muxer; - - } - [Test] - public void ClientScripting() - { - using (var conn = GetScriptConn()) - { - var result = conn.GetDatabase().ScriptEvaluate("return redis.call('info','server')", null, null); - } - } - - [Test] - public void BasicScripting() - { - using (var muxer = GetScriptConn()) - { - var conn = muxer.GetDatabase(); - var noCache = conn.ScriptEvaluateAsync("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", - new RedisKey[] { "key1", "key2" }, new RedisValue[] { "first", "second" }); - var cache = conn.ScriptEvaluateAsync("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", - new RedisKey[] { "key1", "key2" }, new RedisValue[] { "first", "second" }); - var results = (string[])conn.Wait(noCache); - Assert.AreEqual(4, results.Length); - Assert.AreEqual("key1", results[0]); - Assert.AreEqual("key2", results[1]); - Assert.AreEqual("first", results[2]); - Assert.AreEqual("second", results[3]); - - results = (string[])conn.Wait(cache); - Assert.AreEqual(4, results.Length); - Assert.AreEqual("key1", results[0]); - Assert.AreEqual("key2", results[1]); - Assert.AreEqual("first", results[2]); - Assert.AreEqual("second", results[3]); - } - } - [Test] - public void KeysScripting() - { - using (var muxer = GetScriptConn()) - { - var conn = muxer.GetDatabase(); - conn.StringSet("foo", "bar"); - var result = (string)conn.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { "foo" }, null); - Assert.AreEqual("bar", result); - } - } - - [Test] - public void TestRandomThingFromForum() - { - const string script = @"local currentVal = tonumber(redis.call('GET', KEYS[1])); - if (currentVal <= 0 ) then return 1 elseif (currentVal - (tonumber(ARGV[1])) < 0 ) then return 0 end; - return redis.call('INCRBY', KEYS[1], -tonumber(ARGV[1]));"; - - using (var muxer = GetScriptConn()) - { - var conn = muxer.GetDatabase(); - conn.StringSetAsync("A", "0"); - conn.StringSetAsync("B", "5"); - conn.StringSetAsync("C", "10"); - - var a = conn.ScriptEvaluateAsync(script, new RedisKey[] { "A" }, new RedisValue[] { 6 }); - var b = conn.ScriptEvaluateAsync(script, new RedisKey[] { "B" }, new RedisValue[] { 6 }); - var c = conn.ScriptEvaluateAsync(script, new RedisKey[] { "C" }, new RedisValue[] { 6 }); - - var vals = conn.StringGetAsync(new RedisKey[] { "A", "B", "C" }); - - Assert.AreEqual(1, (long)conn.Wait(a)); // exit code when current val is non-positive - Assert.AreEqual(0, (long)conn.Wait(b)); // exit code when result would be negative - Assert.AreEqual(4, (long)conn.Wait(c)); // 10 - 6 = 4 - Assert.AreEqual("0", (string)conn.Wait(vals)[0]); - Assert.AreEqual("5", (string)conn.Wait(vals)[1]); - Assert.AreEqual("4", (string)conn.Wait(vals)[2]); - } - } - - [Test] - public void HackyGetPerf() - { - using (var muxer = GetScriptConn()) - { - var conn = muxer.GetDatabase(); - conn.StringSetAsync("foo", "bar"); - var key = Config.CreateUniqueName(); - var result = (long)conn.ScriptEvaluate(@" -redis.call('psetex', KEYS[1], 60000, 'timing') -for i = 1,100000 do - redis.call('set', 'ignore','abc') -end -local timeTaken = 60000 - redis.call('pttl', KEYS[1]) -redis.call('del', KEYS[1]) -return timeTaken -", new RedisKey[] { key }, null); - Console.WriteLine(result); - Assert.IsTrue(result > 0); - } - } - - [Test] - public void MultiIncrWithoutReplies() - { - using (var muxer = GetScriptConn()) - { - const int DB = 0; // any database number - var conn = muxer.GetDatabase(DB); - // prime some initial values - conn.KeyDeleteAsync(new RedisKey[] { "a", "b", "c" }); - conn.StringIncrementAsync("b"); - conn.StringIncrementAsync("c"); - conn.StringIncrementAsync("c"); - - // run the script, passing "a", "b", "c", "c" to - // increment a & b by 1, c twice - var result = conn.ScriptEvaluateAsync( - @"for i,key in ipairs(KEYS) do redis.call('incr', key) end", - new RedisKey[] { "a", "b", "c", "c" }, // <== aka "KEYS" in the script - null); // <== aka "ARGV" in the script - - // check the incremented values - var a = conn.StringGetAsync("a"); - var b = conn.StringGetAsync("b"); - var c = conn.StringGetAsync("c"); - - Assert.IsTrue(conn.Wait(result).IsNull, "result"); - Assert.AreEqual(1, (long)conn.Wait(a), "a"); - Assert.AreEqual(2, (long)conn.Wait(b), "b"); - Assert.AreEqual(4, (long)conn.Wait(c), "c"); - } - } - - [Test] - public void MultiIncrByWithoutReplies() - { - using (var muxer = GetScriptConn()) - { - const int DB = 0; // any database number - var conn = muxer.GetDatabase(DB); - // prime some initial values - conn.KeyDeleteAsync(new RedisKey[] { "a", "b", "c" }); - conn.StringIncrementAsync("b"); - conn.StringIncrementAsync("c"); - conn.StringIncrementAsync("c"); - - //run the script, passing "a", "b", "c" and 1,2,3 - // increment a &b by 1, c twice - var result = conn.ScriptEvaluateAsync( - @"for i,key in ipairs(KEYS) do redis.call('incrby', key, ARGV[i]) end", - new RedisKey[] { "a", "b", "c" }, // <== aka "KEYS" in the script - new RedisValue[] { 1, 1, 2 }); // <== aka "ARGV" in the script - - // check the incremented values - var a = conn.StringGetAsync("a"); - var b = conn.StringGetAsync("b"); - var c = conn.StringGetAsync("c"); - - Assert.IsTrue(conn.Wait(result).IsNull, "result"); - Assert.AreEqual(1, (long)conn.Wait(a), "a"); - Assert.AreEqual(2, (long)conn.Wait(b), "b"); - Assert.AreEqual(4, (long)conn.Wait(c), "c"); - } - } - - [Test] - public void DisableStringInference() - { - using (var muxer = GetScriptConn()) - { - var conn = muxer.GetDatabase(0); - conn.StringSet("foo", "bar"); - var result = (byte[])conn.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { "foo" }); - Assert.AreEqual("bar", Encoding.UTF8.GetString(result)); - } - } - - [Test] - public void FlushDetection() - { // we don't expect this to handle everything; we just expect it to be predictable - using (var muxer = GetScriptConn(allowAdmin: true)) - { - var conn = muxer.GetDatabase(0); - conn.StringSet("foo", "bar"); - var result = (string)conn.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { "foo" }, null); - Assert.AreEqual("bar", result); - - // now cause all kinds of problems - Config.GetServer(muxer).ScriptFlush(); - - //expect this one to fail just work fine (self-fix) - conn.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { "foo" }, null); - - result = (string)conn.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { "foo" }, null); - Assert.AreEqual("bar", result); - } - } - - [Test] - public void PrepareScript() - { - string[] scripts = { "return redis.call('get', KEYS[1])", "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" }; - using (var muxer = GetScriptConn(allowAdmin: true)) - { - var server = Config.GetServer(muxer); - server.ScriptFlush(); - - // when vanilla - server.ScriptLoad(scripts[0]); - server.ScriptLoad(scripts[1]); - - //when known to exist - server.ScriptLoad(scripts[0]); - server.ScriptLoad(scripts[1]); - } - using (var muxer = GetScriptConn()) - { - var server = Config.GetServer(muxer); - - //when vanilla - server.ScriptLoad(scripts[0]); - server.ScriptLoad(scripts[1]); - - //when known to exist - server.ScriptLoad(scripts[0]); - server.ScriptLoad(scripts[1]); - - //when known to exist - server.ScriptLoad(scripts[0]); - server.ScriptLoad(scripts[1]); - } - } - [Test] - public void NonAsciiScripts() - { - using (var muxer = GetScriptConn()) - { - const string evil = "return '僕'"; - var conn = muxer.GetDatabase(0); - Config.GetServer(muxer).ScriptLoad(evil); - - var result = (string)conn.ScriptEvaluate(evil, null, null); - Assert.AreEqual("僕", result); - } - } - - [Test] - public void ScriptThrowsError() - { - Assert.Throws(() => - { - using (var muxer = GetScriptConn()) - { - var conn = muxer.GetDatabase(0); - var result = conn.ScriptEvaluateAsync("return redis.error_reply('oops')", null, null); - try - { - conn.Wait(result); - } - catch (AggregateException ex) - { - throw ex.InnerExceptions[0]; - } - } - }, - message: "oops"); - } - - [Test] - public void ScriptThrowsErrorInsideTransaction() - { - using (var muxer = GetScriptConn()) - { - const int db = 0; - const string key = "ScriptThrowsErrorInsideTransaction"; - var conn = muxer.GetDatabase(db); - conn.KeyDeleteAsync(key); - var beforeTran = (string)conn.StringGet(key); - Assert.IsNull(beforeTran); - var tran = conn.CreateTransaction(); - { - var a = tran.StringIncrementAsync(key); - var b = tran.ScriptEvaluateAsync("return redis.error_reply('oops')", null, null); - var c = tran.StringIncrementAsync(key); - var complete = tran.ExecuteAsync(); - - Assert.IsTrue(tran.Wait(complete)); - Assert.IsTrue(a.IsCompleted); - Assert.IsTrue(c.IsCompleted); - Assert.AreEqual(1L, a.Result); - Assert.AreEqual(2L, c.Result); - - Assert.IsTrue(b.IsFaulted); - Assert.AreEqual(1, b.Exception.InnerExceptions.Count); - var ex = b.Exception.InnerExceptions.Single(); - Assert.IsInstanceOf(ex); - Assert.AreEqual("oops", ex.Message); - - } - var afterTran = conn.StringGetAsync(key); - Assert.AreEqual(2L, (long)conn.Wait(afterTran)); - } - } - - - - [Test] - public void ChangeDbInScript() - { - using (var muxer = GetScriptConn()) - { - muxer.GetDatabase(1).StringSet("foo", "db 1"); - muxer.GetDatabase(2).StringSet("foo", "db 2"); - - var conn = muxer.GetDatabase(2); - var evalResult = conn.ScriptEvaluateAsync(@"redis.call('select', 1) - return redis.call('get','foo')", null, null); - var getResult = conn.StringGetAsync("foo"); - - Assert.AreEqual("db 1", (string)conn.Wait(evalResult)); - // now, our connection thought it was in db 2, but the script changed to db 1 - Assert.AreEqual("db 2", (string)conn.Wait(getResult)); - - } - } - - [Test] - public void ChangeDbInTranScript() - { - using (var muxer = GetScriptConn()) - { - muxer.GetDatabase(1).StringSet("foo", "db 1"); - muxer.GetDatabase(2).StringSet("foo", "db 2"); - - var conn = muxer.GetDatabase(2); - var tran = conn.CreateTransaction(); - var evalResult = tran.ScriptEvaluateAsync(@"redis.call('select', 1) - return redis.call('get','foo')", null, null); - var getResult = tran.StringGetAsync("foo"); - Assert.IsTrue(tran.Execute()); - - Assert.AreEqual("db 1", (string)conn.Wait(evalResult)); - // now, our connection thought it was in db 2, but the script changed to db 1 - Assert.AreEqual("db 2", (string)conn.Wait(getResult)); - - } - } - } -} diff --git a/MigratedBookSleeveTestSuite/Server.cs b/MigratedBookSleeveTestSuite/Server.cs deleted file mode 100644 index 9cc139c54..000000000 --- a/MigratedBookSleeveTestSuite/Server.cs +++ /dev/null @@ -1,446 +0,0 @@ -//using System.Linq; -//using BookSleeve; -//using NUnit.Framework; -//using System.Threading; -//using System; -//using System.Threading.Tasks; -//using System.Collections.Generic; -//using System.Diagnostics; - -//namespace Tests -//{ -// [TestFixture] -// public class Server // http://redis.io/commands#server -// { -// [Test] -// public void TestGetConfigAll() -// { -// using (var db = Config.GetUnsecuredConnection()) -// { -// var pairs = db.Wait(db.Server.GetConfig("*")); -// Assert.Greater(1, 0); // I always get double-check which arg is which -// Assert.Greater(pairs.Count, 0); -// } -// } - -// [Test] -// public void BGSaveAndLastSave() -// { -// using(var db = Config.GetUnsecuredConnection(allowAdmin: true)) -// { -// var oldWhen = db.Server.GetLastSaveTime(); -// db.Wait(db.Server.SaveDatabase(foreground: false)); - -// bool saved = false; -// for(int i = 0; i < 50; i++) -// { -// var newWhen = db.Server.GetLastSaveTime(); -// db.Wait(newWhen); -// if(newWhen.Result > oldWhen.Result) -// { -// saved = true; -// break; -// } -// Console.WriteLine("waiting..."); -// Thread.Sleep(200); -// } -// Assert.IsTrue(saved); -// } -// } - -// [Test] -// [TestCase(true)] -// [TestCase(false)] -// public void Slowlog(bool remote) -// { -// using(var db = remote ? Config.GetRemoteConnection(allowAdmin: true) : Config.GetUnsecuredConnection(allowAdmin: true)) -// { -// var oldWhen = db.Wait(db.Server.Time()); -// db.Server.FlushAll(); -// db.Server.ResetSlowCommands(); -// for (int i = 0; i < 100000; i++) -// { -// db.Strings.Set(1, Guid.NewGuid().ToString(), Guid.NewGuid().ToString()); -// } -// var settings = db.Wait(db.Server.GetConfig("slowlog-*")); -// var count = int.Parse(settings["slowlog-max-len"]); -// var threshold = int.Parse(settings["slowlog-log-slower-than"]); - -// var ping = db.Server.Ping(); -// Assert.IsTrue(ping.Wait(10000)); // wait until inserted -// db.Server.SaveDatabase(foreground: true); -// var keys = db.Wait(db.Keys.Find(1, "*")); -// var slow = db.Wait(db.Server.GetSlowCommands()); -// var slow2 = db.Wait(db.Server.GetSlowCommands(slow.Length)); // different command syntax -// Assert.AreEqual(slow.Length, slow2.Length); - - - -// foreach(var cmd in slow) -// { -// Console.WriteLine(cmd.UniqueId + ": " + cmd.Duration.Milliseconds + "ms; " + -// string.Join(", ", cmd.Arguments), cmd.GetHelpUrl()); -// Assert.IsTrue(cmd.Time > oldWhen && cmd.Time < oldWhen.AddMinutes(1)); -// } - -// Assert.AreEqual(2, slow.Length); - -// Assert.AreEqual(2, slow[0].Arguments.Length); -// Assert.AreEqual("KEYS", slow[0].Arguments[0]); -// Assert.AreEqual("*", slow[0].Arguments[1]); -// Assert.AreEqual("http://redis.io/commands/keys", slow[0].GetHelpUrl()); - -// Assert.AreEqual(1, slow[1].Arguments.Length); -// Assert.AreEqual("SAVE", slow[1].Arguments[0]); -// Assert.AreEqual("http://redis.io/commands/save", slow[1].GetHelpUrl()); -// } -// } - -// [Test] -// public void TestTime() -// { -// using (var db = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// Assert.IsNotNull(db.Features); // we waited, after all -// if (db.Features.Time) -// { -// var local = DateTime.UtcNow; -// var server = db.Wait(db.Server.Time()); - -// Assert.True(Math.Abs((local - server).TotalMilliseconds) < 10); - -// } -// } -// } - -// [Test] -// public void TestTimeWithExplicitVersion() -// { -// using (var db = Config.GetUnsecuredConnection(open: false)) -// { -// db.SetServerVersion(new Version("2.6.9"), ServerType.Master); -// db.SetKeepAlive(10); -// Assert.IsNotNull(db.Features, "Features"); // we waited, after all -// Assert.IsTrue(db.Features.ClientName, "ClientName"); -// Assert.IsTrue(db.Features.Time, "Time"); -// db.Name = "FooFoo"; -// db.Wait(db.Open()); - -// var local = DateTime.UtcNow; -// var server = db.Wait(db.Server.Time()); - -// Assert.True(Math.Abs((local - server).TotalMilliseconds) < 10, "Latency"); -// } -// } - -// [Test, ExpectedException(typeof(TimeoutException), ExpectedMessage = "The operation has timed out; the connection is not open")] -// public void TimeoutMessageNotOpened() -// { -// using (var conn = Config.GetUnsecuredConnection(open: false)) -// { -// conn.Wait(conn.Strings.Get(0, "abc")); -// } -// } - -// [Test, ExpectedException(typeof(TimeoutException), ExpectedMessage = "The operation has timed out.")] -// public void TimeoutMessageNoDetail() -// { -// using (var conn = Config.GetUnsecuredConnection(open: true)) -// { -// conn.IncludeDetailInTimeouts = false; -// conn.Keys.Remove(0, "noexist"); -// conn.Lists.BlockingRemoveFirst(0, new[] { "noexist" }, 5); -// conn.Wait(conn.Strings.Get(0, "abc")); -// } -// } - -// [Test, ExpectedException(typeof(TimeoutException), ExpectedMessage = "The operation has timed out; possibly blocked by: 0: BLPOP \"noexist\" 5")] -// public void TimeoutMessageWithDetail() -// { -// using (var conn = Config.GetUnsecuredConnection(open: true, waitForOpen: true)) -// { -// conn.IncludeDetailInTimeouts = true; -// conn.Keys.Remove(0, "noexist"); -// conn.Lists.BlockingRemoveFirst(0, new[] { "noexist" }, 5); -// conn.Wait(conn.Strings.Get(0, "abc")); -// } -// } - -// [Test] -// public void ClientList() -// { -// using (var killMe = Config.GetUnsecuredConnection()) -// using (var conn = Config.GetUnsecuredConnection(allowAdmin: true)) -// { -// killMe.Wait(killMe.Strings.GetString(7, "kill me quick")); -// var clients = conn.Wait(conn.Server.ListClients()); -// var target = clients.Single(x => x.Database == 7); -// conn.Wait(conn.Server.KillClient(target.Address)); -// Assert.IsTrue(clients.Length > 0); - -// try -// { -// killMe.Wait(killMe.Strings.GetString(7, "kill me quick")); -// Assert.Fail("Should have been dead"); -// } -// catch (Exception) { } -// } -// } - -// [Test] -// public void HappilyMurderedClientDoesntGetError() -// { -// using (var victim = Config.GetUnsecuredConnection(waitForOpen: true)) -// using (var murderer = Config.GetUnsecuredConnection(allowAdmin: true)) -// { -// const int VictimDB = 4; -// victim.Wait(victim.Strings.GetString(VictimDB, "kill me quick")); -// victim.CompletionMode = ResultCompletionMode.PreserveOrder; -// var clients = murderer.Wait(murderer.Server.ListClients()); -// var target = clients.Single(x => x.Database == VictimDB); - -// int i = 0; -// victim.Closed += (s, a) => -// { -// Interlocked.Increment(ref i); -// }; -// var errors = new List(); -// victim.Shutdown += (s, a) => -// { -// if (a.Exception != null) -// { -// lock (errors) -// { -// errors.Add(a.Exception); -// } -// } -// }; -// victim.Error += (s, a) => -// { -// lock (errors) -// { -// errors.Add(a.Exception); -// } -// }; -// victim.Wait(victim.Server.Ping()); -// murderer.Wait(murderer.Server.KillClient(target.Address)); - -// PubSub.AllowReasonableTimeToPublishAndProcess(); - -// Assert.AreEqual(1, Interlocked.CompareExchange(ref i, 0, 0)); -// lock(errors) -// { -// foreach (var err in errors) Console.WriteLine(err.Message); -// Assert.AreEqual(0, errors.Count); -// } -// Assert.AreEqual(ShutdownType.ServerClosed, victim.ShutdownType); - - -// } -// } -// [Test] -// public void MurderedClientKnowsAboutIt() -// { -// using (var victim = Config.GetUnsecuredConnection(waitForOpen: true)) -// using (var murderer = Config.GetUnsecuredConnection(allowAdmin: true)) -// { -// const int VictimDB = 3; -// victim.Wait(victim.Strings.GetString(VictimDB, "kill me quick")); -// victim.CompletionMode = ResultCompletionMode.PreserveOrder; -// var clients = murderer.Wait(murderer.Server.ListClients()); -// var target = clients.Single(x => x.Database == VictimDB); - -// object sync = new object(); -// ErrorEventArgs args = null; -// Exception ex = null; -// ManualResetEvent shutdownGate = new ManualResetEvent(false), -// exGate = new ManualResetEvent(false); -// victim.Shutdown += (s,a) => -// { -// Console.WriteLine("shutdown"); -// Interlocked.Exchange(ref args, a); -// shutdownGate.Set(); -// }; -// lock (sync) -// { -// ThreadPool.QueueUserWorkItem(x => -// { -// try -// { - -// for (int i = 0; i < 50000; i++) -// { -// if (i == 5) lock (sync) { Monitor.PulseAll(sync); } -// victim.Wait(victim.Strings.Set(VictimDB, "foo", "foo")); -// } -// } -// catch(Exception ex2) -// { -// Console.WriteLine("ex"); -// Interlocked.Exchange(ref ex, ex2); -// exGate.Set(); -// } -// }, null); -// // want the other thread to be running -// Monitor.Wait(sync); -// Console.WriteLine("got pulse; victim is ready"); -// } - -// Console.WriteLine("killing " + target.Address); -// murderer.Wait(murderer.Server.KillClient(target.Address)); - -// Console.WriteLine("waiting on gates..."); -// Assert.IsTrue(shutdownGate.WaitOne(10000), "shutdown gate"); -// Assert.IsTrue(exGate.WaitOne(10000), "exception gate"); -// Console.WriteLine("gates passed"); - -// Assert.AreEqual(ShutdownType.ServerClosed, victim.ShutdownType); -// var args_final = Interlocked.Exchange(ref args, null); -// var ex_final = Interlocked.Exchange(ref ex, null); -// Assert.IsNotNull(ex_final, "ex"); -// Assert.IsNotNull(args_final, "args"); -// } -// } - -// [Test] -// public void CleanCloseKnowsReason() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// conn.Wait(conn.Server.Ping()); -// conn.Close(false); -// Assert.AreEqual(ShutdownType.ClientClosed, conn.ShutdownType); -// } -// } -// [Test] -// public void DisposeKnowsReason() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// conn.Wait(conn.Server.Ping()); -// conn.Dispose(); -// Assert.AreEqual(ShutdownType.ClientDisposed, conn.ShutdownType); -// } -// } - -// [Test] -// public void TestLastSentCounter() -// { -// using (var db = Config.GetUnsecuredConnection(open: false)) -// { -// db.SetServerVersion(new Version("2.6.0"), ServerType.Master); -// db.SetKeepAlive(0); // turn off keep-alives so we don't get unexpected pings - -// db.Wait(db.Open()); -// db.Wait(db.Server.Ping()); -// var first = db.GetCounters(false); -// Assert.LessOrEqual(0, 1, "0 <= 1"); -// Assert.LessOrEqual(first.LastSentMillisecondsAgo, 100, "first"); - -// Thread.Sleep(2000); -// var second = db.GetCounters(false); -// Assert.GreaterOrEqual(1, 0, "1 >= 0"); -// Assert.GreaterOrEqual(second.LastSentMillisecondsAgo, 1900, "second"); -// Assert.LessOrEqual(second.LastSentMillisecondsAgo, 2100, "second"); - -// db.Wait(db.Server.Ping()); -// var third = db.GetCounters(false); -// Assert.LessOrEqual(0, 1, "0 <= 1"); -// Assert.LessOrEqual(third.LastSentMillisecondsAgo, 100, "third"); -// } -// } - -// [Test] -// public void TestKeepAlive() -// { -// string oldValue = null; -// try -// { -// using (var db = Config.GetUnsecuredConnection(allowAdmin: true)) -// { -// oldValue = db.Wait(db.Server.GetConfig("timeout")).Single().Value; -// db.Server.SetConfig("timeout", "20"); -// } -// using (var db = Config.GetUnsecuredConnection(allowAdmin: false, waitForOpen:true)) -// { -// var before = db.GetCounters(false); -// Assert.AreEqual(4, before.KeepAliveSeconds, "keep-alive"); -// Thread.Sleep(13 * 1000); -// var after = db.GetCounters(false); -// // 3 here is 2 * keep-alive, and one PING in GetCounters() -// int sent = after.MessagesSent - before.MessagesSent; -// Assert.GreaterOrEqual(1, 0); -// Assert.GreaterOrEqual(sent, 3); -// Assert.LessOrEqual(0, 4); -// Assert.LessOrEqual(sent, 4); -// } -// } -// finally -// { -// if (oldValue != null) -// { -// Task t; -// using (var db = Config.GetUnsecuredConnection(allowAdmin: true)) -// { -// t = db.Server.SetConfig("timeout", oldValue); -// } -// Assert.IsTrue(t.Wait(5000)); -// if (t.Exception != null) throw t.Exception; -// } -// } -// } - -// [Test, ActiveTest] -// public void SetValueWhileDisposing() -// { -// const int LOOP = 10; -// for (int i = 0; i < LOOP; i++) -// { -// var guid = Config.CreateUniqueName(); -// Task t1, t3; -// Task t2; -// string key = "SetValueWhileDisposing:" + i; -// using (var db = Config.GetUnsecuredConnection(open: true)) -// { -// t1 = db.Strings.Set(0, key, guid); -// } -// Assert.IsTrue(t1.Wait(500)); -// using (var db = Config.GetUnsecuredConnection()) -// { -// t2 = db.Strings.GetString(0, key); -// t3 = db.Keys.Remove(0, key); -// } -// Assert.IsTrue(t2.Wait(500)); -// Assert.AreEqual(guid, t2.Result); -// Assert.IsTrue(t3.Wait(500)); -// } -// } - -// [Test] -// public void TestMasterSlaveSetup() -// { -// using (var unsec = Config.GetUnsecuredConnection(true, true, true)) -// using (var sec = Config.GetUnsecuredConnection(true, true, true)) -// { -// try -// { -// var makeSlave = sec.Server.MakeSlave(unsec.Host, unsec.Port); -// var info = sec.Wait(sec.Server.GetInfo()); -// sec.Wait(makeSlave); -// Assert.AreEqual("slave", info["role"], "slave"); -// Assert.AreEqual(unsec.Host, info["master_host"], "host"); -// Assert.AreEqual(unsec.Port.ToString(), info["master_port"], "port"); -// var makeMaster = sec.Server.MakeMaster(); -// info = sec.Wait(sec.Server.GetInfo()); -// sec.Wait(makeMaster); -// Assert.AreEqual("master", info["role"], "master"); -// } -// finally -// { -// sec.Server.MakeMaster(); -// } - -// } -// } -// } -//} diff --git a/MigratedBookSleeveTestSuite/Sets.cs b/MigratedBookSleeveTestSuite/Sets.cs deleted file mode 100644 index 37fc64b65..000000000 --- a/MigratedBookSleeveTestSuite/Sets.cs +++ /dev/null @@ -1,513 +0,0 @@ -//using System; -//using NUnit.Framework; -//using System.Text; -//using System.Linq; -//namespace Tests -//{ -// [TestFixture] -// public class Sets // http://redis.io/commands#set -// { -// [Test] -// public void AddSingle() -// { -// using(var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "set"); -// var r0 = conn.Sets.Add(3, "set", "abc"); -// var r1 = conn.Sets.Add(3, "set", "abc"); -// var len = conn.Sets.GetLength(3, "set"); - -// Assert.AreEqual(true, r0.Result); -// Assert.AreEqual(false, r1.Result); -// Assert.AreEqual(1, len.Result); -// } -// } -// [Test] -// public void Scan() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// if (!conn.Features.Scan) Assert.Inconclusive(); - -// const int db = 3; -// const string key = "set-scan"; -// conn.Keys.Remove(db, key); -// conn.Sets.Add(db, key, "abc"); -// conn.Sets.Add(db, key, "def"); -// conn.Sets.Add(db, key, "ghi"); - -// var t1 = conn.Sets.Scan(db, key); -// var t3 = conn.Sets.ScanString(db, key); -// var t4 = conn.Sets.ScanString(db, key, "*h*"); - -// var v1 = t1.ToArray(); -// var v3 = t3.ToArray(); -// var v4 = t4.ToArray(); - -// Assert.AreEqual(3, v1.Length); -// Assert.AreEqual(3, v3.Length); -// Assert.AreEqual(1, v4.Length); -// Array.Sort(v1, (x, y) => string.Compare(Encoding.UTF8.GetString(x), Encoding.UTF8.GetString(y))); -// Array.Sort(v3); -// Array.Sort(v4); - -// Assert.AreEqual("abc,def,ghi", string.Join(",", v1.Select(x => Encoding.UTF8.GetString(x)))); -// Assert.AreEqual("abc,def,ghi", string.Join(",", v3)); -// Assert.AreEqual("ghi", string.Join(",", v4)); -// } -// } -// [Test] -// public void AddSingleBinary() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "set"); -// var r0 = conn.Sets.Add(3, "set", Encode("abc")); -// var r1 = conn.Sets.Add(3, "set", Encode("abc")); -// var len = conn.Sets.GetLength(3, "set"); - -// Assert.AreEqual(true, r0.Result); -// Assert.AreEqual(false, r1.Result); -// Assert.AreEqual(1, len.Result); -// } -// } -// static byte[] Encode(string value) { return Encoding.UTF8.GetBytes(value); } -// static string Decode(byte[] value) { return Encoding.UTF8.GetString(value); } -// [Test] -// public void RemoveSingle() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "set"); -// conn.Sets.Add(3, "set", "abc"); -// conn.Sets.Add(3, "set", "def"); - -// var r0 = conn.Sets.Remove(3, "set", "abc"); -// var r1 = conn.Sets.Remove(3, "set", "abc"); -// var len = conn.Sets.GetLength(3, "set"); - -// Assert.AreEqual(true, r0.Result); -// Assert.AreEqual(false, r1.Result); -// Assert.AreEqual(1, len.Result); -// } -// } - -// [Test] -// public void RemoveSingleBinary() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "set"); -// conn.Sets.Add(3, "set", Encode("abc")); -// conn.Sets.Add(3, "set", Encode("def")); - -// var r0 = conn.Sets.Remove(3, "set", Encode("abc")); -// var r1 = conn.Sets.Remove(3, "set", Encode("abc")); -// var len = conn.Sets.GetLength(3, "set"); - -// Assert.AreEqual(true, r0.Result); -// Assert.AreEqual(false, r1.Result); -// Assert.AreEqual(1, len.Result); -// } -// } - -// [Test] -// public void AddMulti() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// conn.Keys.Remove(3, "set"); -// var r0 = conn.Sets.Add(3, "set", "abc"); -// var r1 = conn.Sets.Add(3, "set", new[] {"abc", "def"}); -// var len = conn.Sets.GetLength(3, "set"); - -// Assert.AreEqual(true, r0.Result); -// Assert.AreEqual(1, r1.Result); -// Assert.AreEqual(2, len.Result); -// } -// } - -// [Test] -// public void RemoveMulti() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// conn.Keys.Remove(3, "set"); -// conn.Sets.Add(3, "set", "abc"); -// conn.Sets.Add(3, "set", "ghi"); - -// var r0 = conn.Sets.Remove(3, "set", new[] {"abc", "def"}); -// var len = conn.Sets.GetLength(3, "set"); - -// Assert.AreEqual(1, r0.Result); -// Assert.AreEqual(1, len.Result); -// } -// } - -// [Test] -// public void AddMultiBinary() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen:true)) -// { -// conn.Keys.Remove(3, "set"); -// var r0 = conn.Sets.Add(3, "set", Encode("abc")); -// var r1 = conn.Sets.Add(3, "set", new[] { Encode("abc"), Encode("def") }); -// var len = conn.Sets.GetLength(3, "set"); - -// Assert.AreEqual(true, r0.Result); -// Assert.AreEqual(1, r1.Result); -// Assert.AreEqual(2, len.Result); -// } -// } - -// [Test] -// public void RemoveMultiBinary() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// conn.Keys.Remove(3, "set"); -// conn.Sets.Add(3, "set", Encode("abc")); -// conn.Sets.Add(3, "set", Encode("ghi")); - -// var r0 = conn.Sets.Remove(3, "set", new[] { Encode("abc"), Encode("def") }); -// var len = conn.Sets.GetLength(3, "set"); - -// Assert.AreEqual(1, r0.Result); -// Assert.AreEqual(1, len.Result); -// } -// } - -// [Test] -// public void Exists() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "set"); -// var r0 = conn.Sets.Contains(3, "set", "def"); - -// conn.Sets.Add(3, "set", "abc"); -// var r1 = conn.Sets.Contains(3, "set", "def"); - -// conn.Sets.Add(3, "set", "def"); -// var r2 = conn.Sets.Contains(3, "set", "def"); - -// conn.Sets.Remove(3, "set", "def"); -// var r3 = conn.Sets.Contains(3, "set", "def"); - -// Assert.AreEqual(false, r0.Result); -// Assert.AreEqual(false, r1.Result); -// Assert.AreEqual(true, r2.Result); -// Assert.AreEqual(false, r3.Result); -// } -// } - - -// [Test] -// public void ExistsBinary() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "set"); -// var r0 = conn.Sets.Contains(3, "set", Encode("def")); - -// conn.Sets.Add(3, "set", "abc"); -// var r1 = conn.Sets.Contains(3, "set", Encode("def")); - -// conn.Sets.Add(3, "set", "def"); -// var r2 = conn.Sets.Contains(3, "set", Encode("def")); - -// conn.Sets.Remove(3, "set", "def"); -// var r3 = conn.Sets.Contains(3, "set", Encode("def")); - -// Assert.AreEqual(false, r0.Result); -// Assert.AreEqual(false, r1.Result); -// Assert.AreEqual(true, r2.Result); -// Assert.AreEqual(false, r3.Result); -// } -// } - -// [Test] -// public void GetRandom() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "set"); - -// Assert.IsNull(conn.Sets.GetRandomString(3, "set").Result); -// Assert.IsNull(conn.Sets.GetRandom(3, "set").Result); - -// conn.Sets.Add(3, "set", "abc"); -// Assert.AreEqual("abc", conn.Sets.GetRandomString(3, "set").Result); -// Assert.AreEqual("abc", Decode(conn.Sets.GetRandom(3, "set").Result)); - -// conn.Sets.Add(3, "set", Encode("def")); -// var result = conn.Sets.GetRandomString(3, "set").Result; -// Assert.IsTrue(result == "abc" || result == "def"); -// result = Decode(conn.Sets.GetRandom(3, "set").Result); -// Assert.IsTrue(result == "abc" || result == "def"); -// } -// } - -// [Test] -// public void GetRandomMulti() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// if (conn.Features.MultipleRandom) -// { -// conn.Keys.Remove(3, "set"); - -// Assert.AreEqual(0, conn.Sets.GetRandomString(3, "set", 2).Result.Length); -// Assert.AreEqual(0, conn.Sets.GetRandom(3, "set", 2).Result.Length); - -// conn.Sets.Add(3, "set", "abc"); -// var a1 = conn.Sets.GetRandomString(3, "set", 2).Result; -// var a2 = conn.Sets.GetRandom(3, "set", 2).Result; -// Assert.AreEqual(1, a1.Length); -// Assert.AreEqual(1, a2.Length); -// Assert.AreEqual("abc", a1[0]); -// Assert.AreEqual("abc", Decode(a2[0])); - -// conn.Sets.Add(3, "set", Encode("def")); -// var a3 = conn.Sets.GetRandomString(3, "set", 3).Result; -// var a4 = Array.ConvertAll(conn.Sets.GetRandom(3, "set", 3).Result, Decode); - -// Assert.AreEqual(2, a3.Length); -// Assert.AreEqual(2, a4.Length); -// Assert.Contains("abc", a3); -// Assert.Contains("def", a3); -// Assert.Contains("abc", a4); -// Assert.Contains("def", a4); - -// var a5 = conn.Sets.GetRandomString(3, "set", -3).Result; -// var a6 = Array.ConvertAll(conn.Sets.GetRandom(3, "set", -3).Result, Decode); -// Assert.AreEqual(3, a5.Length); -// Assert.AreEqual(3, a6.Length); -// Assert.IsTrue(a5.All(x => x == "abc" || x == "def")); -// Assert.IsTrue(a6.All(x => x == "abc" || x == "def")); -// } -// } -// } - -// [Test] -// public void RemoveRandom() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "set"); - -// Assert.IsNull(conn.Sets.RemoveRandomString(3, "set").Result); -// Assert.IsNull(conn.Sets.RemoveRandom(3, "set").Result); - -// conn.Sets.Add(3, "set", "abc"); -// Assert.AreEqual("abc", conn.Sets.RemoveRandomString(3, "set").Result); -// Assert.AreEqual(0, conn.Sets.GetLength(3, "set").Result); - -// conn.Sets.Add(3, "set", "abc"); -// Assert.AreEqual("abc", Decode(conn.Sets.RemoveRandom(3, "set").Result)); -// Assert.AreEqual(0, conn.Sets.GetLength(3, "set").Result); - -// conn.Sets.Add(3, "set", "abc"); -// conn.Sets.Add(3, "set", Encode("def")); -// var result1 = conn.Sets.RemoveRandomString(3, "set").Result; -// var result2 = Decode(conn.Sets.RemoveRandom(3, "set").Result); -// Assert.AreEqual(0, conn.Sets.GetLength(3, "set").Result); - -// Assert.AreNotEqual(result1, result2); -// Assert.IsTrue(result1 == "abc" || result1 == "def"); -// Assert.IsTrue(result2 == "abc" || result2 == "def"); -// } -// } - -// [Test] -// public void GetAll() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "set"); - -// var b0 = conn.Sets.GetAll(3, "set"); -// var s0 = conn.Sets.GetAllString(3, "set"); -// conn.Sets.Add(3, "set", "abc"); -// conn.Sets.Add(3, "set", "def"); -// var b1 = conn.Sets.GetAll(3, "set"); -// var s1 = conn.Sets.GetAllString(3, "set"); - -// Assert.AreEqual(0, conn.Wait(b0).Length); -// Assert.AreEqual(0, conn.Wait(s0).Length); -// // check strings -// var s = conn.Wait(s1); -// Assert.AreEqual(2, s.Length); -// Array.Sort(s); -// Assert.AreEqual("abc", s[0]); -// Assert.AreEqual("def", s[1]); -// // check binary -// s = Array.ConvertAll(conn.Wait(b1), Decode); -// Assert.AreEqual(2, s.Length); -// Array.Sort(s); -// Assert.AreEqual("abc", s[0]); -// Assert.AreEqual("def", s[1]); -// } -// } - -// [Test] -// public void Move() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "from"); -// conn.Keys.Remove(3, "to"); -// conn.Sets.Add(3, "from", "abc"); -// conn.Sets.Add(3, "from", "def"); - -// Assert.AreEqual(2, conn.Sets.GetLength(3, "from").Result); -// Assert.AreEqual(0, conn.Sets.GetLength(3, "to").Result); - -// Assert.IsFalse(conn.Sets.Move(3, "from", "to", "nix").Result); -// Assert.IsFalse(conn.Sets.Move(3, "from", "to", Encode("nix")).Result); - -// Assert.IsTrue(conn.Sets.Move(3, "from", "to", "abc").Result); -// Assert.IsTrue(conn.Sets.Move(3, "from", "to", Encode("def")).Result); - -// Assert.AreEqual(0, conn.Sets.GetLength(3, "from").Result); -// Assert.AreEqual(2, conn.Sets.GetLength(3, "to").Result); -// } -// } - -// [Test] -// public void Diff() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "key1"); -// conn.Keys.Remove(3, "key2"); -// conn.Keys.Remove(3, "key3"); -// conn.Keys.Remove(3, "to"); -// conn.Sets.Add(3, "key1", "a"); -// conn.Sets.Add(3, "key1", "b"); -// conn.Sets.Add(3, "key1", "c"); -// conn.Sets.Add(3, "key1", "d"); -// conn.Sets.Add(3, "key2", "c"); -// conn.Sets.Add(3, "key3", "a"); -// conn.Sets.Add(3, "key3", "c"); -// conn.Sets.Add(3, "key3", "e"); - -// var diff1 = conn.Sets.Difference(3, new[] {"key1", "key2", "key3"}); -// var diff2 = conn.Sets.DifferenceString(3, new[] { "key1", "key2", "key3" }); -// var len = conn.Sets.DifferenceAndStore(3, "to", new[] { "key1", "key2", "key3" }); -// var diff3 = conn.Sets.GetAllString(3, "to"); - -// var s = Array.ConvertAll(conn.Wait(diff1), Decode); -// Assert.AreEqual(2, s.Length); -// Array.Sort(s); -// Assert.AreEqual("b", s[0]); -// Assert.AreEqual("d", s[1]); - -// s = conn.Wait(diff2); -// Assert.AreEqual(2, s.Length); -// Array.Sort(s); -// Assert.AreEqual("b", s[0]); -// Assert.AreEqual("d", s[1]); - -// Assert.AreEqual(2, conn.Wait(len)); -// s = conn.Wait(diff3); -// Assert.AreEqual(2, s.Length); -// Array.Sort(s); -// Assert.AreEqual("b", s[0]); -// Assert.AreEqual("d", s[1]); -// } -// } - - -// [Test] -// public void Intersect() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "key1"); -// conn.Keys.Remove(3, "key2"); -// conn.Keys.Remove(3, "key3"); -// conn.Keys.Remove(3, "to"); -// conn.Sets.Add(3, "key1", "a"); -// conn.Sets.Add(3, "key1", "b"); -// conn.Sets.Add(3, "key1", "c"); -// conn.Sets.Add(3, "key1", "d"); -// conn.Sets.Add(3, "key2", "c"); -// conn.Sets.Add(3, "key3", "a"); -// conn.Sets.Add(3, "key3", "c"); -// conn.Sets.Add(3, "key3", "e"); - -// var diff1 = conn.Sets.Intersect(3, new[] { "key1", "key2", "key3" }); -// var diff2 = conn.Sets.IntersectString(3, new[] { "key1", "key2", "key3" }); -// var len = conn.Sets.IntersectAndStore(3, "to", new[] { "key1", "key2", "key3" }); -// var diff3 = conn.Sets.GetAllString(3, "to"); - -// var s = Array.ConvertAll(conn.Wait(diff1), Decode); -// Assert.AreEqual(1, s.Length); -// Assert.AreEqual("c", s[0]); - -// s = conn.Wait(diff2); -// Assert.AreEqual(1, s.Length); -// Assert.AreEqual("c", s[0]); - -// Assert.AreEqual(1, conn.Wait(len)); -// s = conn.Wait(diff3); -// Assert.AreEqual(1, s.Length); -// Assert.AreEqual("c", s[0]); -// } -// } - -// [Test] -// public void Union() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "key1"); -// conn.Keys.Remove(3, "key2"); -// conn.Keys.Remove(3, "key3"); -// conn.Keys.Remove(3, "to"); -// conn.Sets.Add(3, "key1", "a"); -// conn.Sets.Add(3, "key1", "b"); -// conn.Sets.Add(3, "key1", "c"); -// conn.Sets.Add(3, "key1", "d"); -// conn.Sets.Add(3, "key2", "c"); -// conn.Sets.Add(3, "key3", "a"); -// conn.Sets.Add(3, "key3", "c"); -// conn.Sets.Add(3, "key3", "e"); - -// var diff1 = conn.Sets.Union(3, new[] { "key1", "key2", "key3" }); -// var diff2 = conn.Sets.UnionString(3, new[] { "key1", "key2", "key3" }); -// var len = conn.Sets.UnionAndStore(3, "to", new[] { "key1", "key2", "key3" }); -// var diff3 = conn.Sets.GetAllString(3, "to"); - -// var s = Array.ConvertAll(conn.Wait(diff1), Decode); -// Assert.AreEqual(5, s.Length); -// Array.Sort(s); -// Assert.AreEqual("a", s[0]); -// Assert.AreEqual("b", s[1]); -// Assert.AreEqual("c", s[2]); -// Assert.AreEqual("d", s[3]); -// Assert.AreEqual("e", s[4]); - -// s = conn.Wait(diff2); -// Assert.AreEqual(5, s.Length); -// Array.Sort(s); -// Assert.AreEqual("a", s[0]); -// Assert.AreEqual("b", s[1]); -// Assert.AreEqual("c", s[2]); -// Assert.AreEqual("d", s[3]); -// Assert.AreEqual("e", s[4]); - -// Assert.AreEqual(5, conn.Wait(len)); -// s = conn.Wait(diff3); -// Assert.AreEqual(5, s.Length); -// Array.Sort(s); -// Assert.AreEqual("a", s[0]); -// Assert.AreEqual("b", s[1]); -// Assert.AreEqual("c", s[2]); -// Assert.AreEqual("d", s[3]); -// Assert.AreEqual("e", s[4]); -// } -// } -// } -//} diff --git a/MigratedBookSleeveTestSuite/SortedSets.cs b/MigratedBookSleeveTestSuite/SortedSets.cs deleted file mode 100644 index 55d40a983..000000000 --- a/MigratedBookSleeveTestSuite/SortedSets.cs +++ /dev/null @@ -1,376 +0,0 @@ -//using System.Linq; -//using NUnit.Framework; -//using System; -//using System.Text; -//using BookSleeve; -//using System.Collections.Generic; -//using System.Threading.Tasks; - -//namespace Tests -//{ -// [TestFixture] -// public class SortedSets // http://redis.io/commands#sorted_set -// { -// [Test] -// public void SortedTrim() -// { -// using(var conn = Config.GetUnsecuredConnection()) -// { -// const int db = 0; -// const string key = "sorted-trim"; -// for(int i = 0; i < 200; i++) -// { -// conn.SortedSets.Add(db, key, i.ToString(), i); -// } -// conn.SortedSets.RemoveRange(db, key, 0, -21); -// var count = conn.SortedSets.GetLength(db, key); -// Assert.AreEqual(20, conn.Wait(count)); -// } -// } - -// [Test] -// public void Scan() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen:true)) -// { -// if (!conn.Features.Scan) Assert.Inconclusive(); - -// const int db = 3; -// const string key = "sorted-set-scan"; -// conn.Keys.Remove(db, key); -// conn.SortedSets.Add(db, key, "abc", 1); -// conn.SortedSets.Add(db, key, "def", 2); -// conn.SortedSets.Add(db, key, "ghi", 3); - -// var t1 = conn.SortedSets.Scan(db, key); -// var t3 = conn.SortedSets.ScanString(db, key); -// var t4 = conn.SortedSets.ScanString(db, key, "*h*"); - -// var v1 = t1.ToArray(); -// var v3 = t3.ToArray(); -// var v4 = t4.ToArray(); - -// Assert.AreEqual(3, v1.Length); -// Assert.AreEqual(3, v3.Length); -// Assert.AreEqual(1, v4.Length); -// Array.Sort(v1, (x, y) => string.Compare(Encoding.UTF8.GetString(x.Key), Encoding.UTF8.GetString(y.Key))); -// Array.Sort(v3, (x, y) => string.Compare(x.Key, y.Key)); -// Array.Sort(v4, (x, y) => string.Compare(x.Key, y.Key)); - -// Assert.AreEqual("abc=1,def=2,ghi=3", string.Join(",", v1.Select(pair => Encoding.UTF8.GetString(pair.Key) + "=" + pair.Value))); -// Assert.AreEqual("abc=1,def=2,ghi=3", string.Join(",", v3.Select(pair => pair.Key + "=" + pair.Value))); -// Assert.AreEqual("ghi=3", string.Join(",", v4.Select(pair => pair.Key + "=" + pair.Value))); -// } -// } -// [Test] -// public void Range() // http://code.google.com/p/booksleeve/issues/detail?id=12 -// { -// using(var conn = Config.GetUnsecuredConnection()) -// { -// const double value = 634614442154715; -// conn.SortedSets.Add(3, "zset", "abc", value); -// var range = conn.SortedSets.Range(3, "zset", 0, -1); - -// Assert.AreEqual(value, conn.Wait(range).Single().Value); -// } -// } -// [Test] -// public void RangeString() // http://code.google.com/p/booksleeve/issues/detail?id=18 -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// const double value = 634614442154715; -// conn.SortedSets.Add(3, "zset", "abc", value); -// var range = conn.SortedSets.RangeString(3, "zset", 0, -1); - -// Assert.AreEqual(value, conn.Wait(range).Single().Value); -// } -// } - -// [Test] -// public void Score() // http://code.google.com/p/booksleeve/issues/detail?id=23 -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(0, "abc"); -// conn.SortedSets.Add(0, "abc", "def", 1.0); -// var s1 = conn.SortedSets.Score(0, "abc", "def"); -// var s2 = conn.SortedSets.Score(0, "abc", "ghi"); - -// Assert.AreEqual(1.0, conn.Wait(s1)); -// Assert.IsNull(conn.Wait(s2)); -// } -// } - -// [Test] -// public void Rank() // http://code.google.com/p/booksleeve/issues/detail?id=23 -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(0, "abc"); -// conn.SortedSets.Add(0, "abc", "def", 1.0); -// conn.SortedSets.Add(0, "abc", "jkl", 2.0); -// var a1 = conn.SortedSets.Rank(0, "abc", "def", ascending: true); -// var a2 = conn.SortedSets.Rank(0, "abc", "ghi", ascending: true); -// var a3 = conn.SortedSets.Rank(0, "abc", "jkl", ascending: true); - -// var d1 = conn.SortedSets.Rank(0, "abc", "def", ascending: false); -// var d2 = conn.SortedSets.Rank(0, "abc", "ghi", ascending: false); -// var d3 = conn.SortedSets.Rank(0, "abc", "jkl", ascending: false); - -// Assert.AreEqual(0, conn.Wait(a1)); -// Assert.IsNull(conn.Wait(a2)); -// Assert.AreEqual(1, conn.Wait(a3)); - -// Assert.AreEqual(1, conn.Wait(d1)); -// Assert.IsNull(conn.Wait(d2)); -// Assert.AreEqual(0, conn.Wait(d3)); -// } -// } - -// static string SeedRange(RedisConnection connection, out double min, out double max) -// { -// var rand = new Random(123456); -// const string key = "somerange"; -// connection.Keys.Remove(0, key); -// min = max = 0; -// for (int i = 0; i < 50; i++) -// { -// double value = rand.NextDouble(); -// if (i == 0) -// { -// min = max = value; -// } -// else -// { -// if (value < min) min = value; -// if (value > max) max= value; -// } -// connection.SortedSets.Add(0, key, "item " + i, value); -// } -// return key; -// } -// [Test] -// public void GetAll() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// double minActual, maxActual; -// string key = SeedRange(conn, out minActual, out maxActual); - -// var all = conn.Wait(conn.SortedSets.Range(0, key, 0.0, 1.0)); -// Assert.AreEqual(50, all.Length, "all between 0.0 and 1.0"); - -// var subset = conn.Wait(conn.SortedSets.Range(0, key, 0.0, 1.0, offset: 2, count: 46)); -// Assert.AreEqual(46, subset.Length); - -// var subVals = new HashSet(subset.Select(x => x.Value)); - -// Assert.IsFalse(subVals.Contains(all[0].Value)); -// Assert.IsFalse(subVals.Contains(all[1].Value)); -// Assert.IsFalse(subVals.Contains(all[48].Value)); -// Assert.IsFalse(subVals.Contains(all[49].Value)); -// for (int i = 2; i < 48; i++) -// { -// Assert.IsTrue(subVals.Contains(all[i].Value)); -// } -// } -// } - -// [Test] -// public void FindMinMax() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// double minActual, maxActual; -// string key = SeedRange(conn, out minActual, out maxActual); - -// var min = conn.SortedSets.Range(0, key, ascending: true, count: 1); -// var max = conn.SortedSets.Range(0, key, ascending: false, count: 1); - -// var minScore = conn.Wait(min).Single().Value; -// var maxScore = conn.Wait(max).Single().Value; - -// Assert.Less(1, 2); // I *always* get these args the wrong way around -// Assert.Less(Math.Abs(minActual - minScore), 0.0000001, "min"); -// Assert.Less(Math.Abs(maxActual - maxScore), 0.0000001, "max"); -// } -// } - -// [Test] -// public void CheckInfinity() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(0, "infs"); -// conn.SortedSets.Add(0, "infs", "neg", double.NegativeInfinity); -// conn.SortedSets.Add(0, "infs", "pos", double.PositiveInfinity); -// conn.SortedSets.Add(0, "infs", "zero", 0.0); -// var pairs = conn.Wait(conn.SortedSets.RangeString(0, "infs", 0, -1)); -// Assert.AreEqual(3, pairs.Length); -// Assert.AreEqual("neg", pairs[0].Key); -// Assert.AreEqual("zero", pairs[1].Key); -// Assert.AreEqual("pos", pairs[2].Key); -// Assert.IsTrue(double.IsNegativeInfinity(pairs[0].Value), "-inf"); -// Assert.AreEqual(0.0, pairs[1].Value); -// Assert.IsTrue(double.IsPositiveInfinity(pairs[2].Value), "+inf"); -// } -// } - -// [Test] -// public void UnionAndStore() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "key1"); -// conn.Keys.Remove(3, "key2"); -// conn.Keys.Remove(3, "to"); - -// conn.SortedSets.Add(3, "key1", "a", 1); -// conn.SortedSets.Add(3, "key1", "b", 2); -// conn.SortedSets.Add(3, "key1", "c", 3); - -// conn.SortedSets.Add(3, "key2", "a", 1); -// conn.SortedSets.Add(3, "key2", "b", 2); -// conn.SortedSets.Add(3, "key2", "c", 3); - -// var numberOfElementsT = conn.SortedSets.UnionAndStore(3, "to", new string[] { "key1", "key2" }, BookSleeve.RedisAggregate.Sum); -// var resultSetT = conn.SortedSets.RangeString(3, "to", 0, -1); - -// var numberOfElements = conn.Wait(numberOfElementsT); -// Assert.AreEqual(3, numberOfElements); - -// var s = conn.Wait(resultSetT); - -// Assert.AreEqual("a", s[0].Key); -// Assert.AreEqual("b", s[1].Key); -// Assert.AreEqual("c", s[2].Key); - -// Assert.AreEqual(2, s[0].Value); -// Assert.AreEqual(4, s[1].Value); -// Assert.AreEqual(6, s[2].Value); -// } -// } - -// [Test] -// public void UnionAndStoreMax() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "key1"); -// conn.Keys.Remove(3, "key2"); -// conn.Keys.Remove(3, "to"); - -// conn.SortedSets.Add(3, "key1", "a", 1); -// conn.SortedSets.Add(3, "key1", "b", 2); -// conn.SortedSets.Add(3, "key1", "c", 3); - -// conn.SortedSets.Add(3, "key2", "a", 4); -// conn.SortedSets.Add(3, "key2", "b", 5); -// conn.SortedSets.Add(3, "key2", "c", 6); - -// var numberOfElementsT = conn.SortedSets.UnionAndStore(3, "to", new string[] { "key1", "key2" }, BookSleeve.RedisAggregate.Max); -// var resultSetT = conn.SortedSets.RangeString(3, "to", 0, -1); - -// var numberOfElements = conn.Wait(numberOfElementsT); -// Assert.AreEqual(3, numberOfElements); - -// var s = conn.Wait(resultSetT); - -// Assert.AreEqual("a", s[0].Key); -// Assert.AreEqual("b", s[1].Key); -// Assert.AreEqual("c", s[2].Key); - -// Assert.AreEqual(4, s[0].Value); -// Assert.AreEqual(5, s[1].Value); -// Assert.AreEqual(6, s[2].Value); -// } -// } - -// [Test] -// public void UnionAndStoreMin() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(3, "key1"); -// conn.Keys.Remove(3, "key2"); -// conn.Keys.Remove(3, "to"); - -// conn.SortedSets.Add(3, "key1", "a", 1); -// conn.SortedSets.Add(3, "key1", "b", 2); -// conn.SortedSets.Add(3, "key1", "c", 3); - -// conn.SortedSets.Add(3, "key2", "a", 4); -// conn.SortedSets.Add(3, "key2", "b", 5); -// conn.SortedSets.Add(3, "key2", "c", 6); - -// var numberOfElementsT = conn.SortedSets.UnionAndStore(3, "to", new string[] { "key1", "key2" }, BookSleeve.RedisAggregate.Min); -// var resultSetT = conn.SortedSets.RangeString(3, "to", 0, -1); - -// var numberOfElements = conn.Wait(numberOfElementsT); -// Assert.AreEqual(3, numberOfElements); - -// var s = conn.Wait(resultSetT); - -// Assert.AreEqual("a", s[0].Key); -// Assert.AreEqual("b", s[1].Key); -// Assert.AreEqual("c", s[2].Key); - -// Assert.AreEqual(1, s[0].Value); -// Assert.AreEqual(2, s[1].Value); -// Assert.AreEqual(3, s[2].Value); -// } -// } - -// [Test] -// public void TestZUNIONSTORElimit() -// { -// const int SIZE = 10000; -// using (var conn = Config.GetUnsecuredConnection()) -// { -// for (int i = 0; i < SIZE; i++) -// { -// string key = "z_" + i; -// conn.Keys.Remove(0, key); -// for (int j = 0; j < 5; j++) -// conn.SortedSets.Add(0, key, "s" + j.ToString(), j); -// } -// conn.Wait(conn.Server.Ping()); - -// List results = new List(SIZE); -// for (int i = 0; i < SIZE; i+=100) -// { -// string[] keys = Enumerable.Range(0,i+1).Select(x => "z_" + x).ToArray(); -// results.Add(conn.SortedSets.UnionAndStore(0, "zu_" + i, keys, RedisAggregate.Max)); -// } -// foreach (var task in results) -// conn.WaitAll(task); -// } -// } - -// [Test] -// public void SO14991819() -// { -// const int _db = 0; -// const string _thisChannel = "SO14991819"; -// string thisChannel = string.Format("urn:{0}", _thisChannel); -// const string message = "hi"; -// using (var _connection = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// _connection.Keys.Remove(_db, thisChannel); // start from known state - -// TimeSpan span = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0)); -// double val = span.TotalSeconds; - -// _connection.SortedSets.Add(_db, thisChannel, message, val, false); - -// var subset = _connection.Wait(_connection.SortedSets.RangeString( -// _db, thisChannel, span.TotalSeconds - 10000, span.TotalSeconds, offset: 0, count: 50)); - -// Assert.AreEqual(1, subset.Length); -// Config.AssertNearlyEqual(val, subset[0].Value); -// Assert.AreEqual(message, subset[0].Key); -// } -// } -// } -//} diff --git a/MigratedBookSleeveTestSuite/Strings.cs b/MigratedBookSleeveTestSuite/Strings.cs deleted file mode 100644 index 0674f2a7f..000000000 --- a/MigratedBookSleeveTestSuite/Strings.cs +++ /dev/null @@ -1,253 +0,0 @@ -using System.Linq; -using System.Text; -using NUnit.Framework; -using StackExchange.Redis; - -namespace Tests -{ - [TestFixture] - public class Strings // http://redis.io/commands#string - { - [Test] - public void Append() - { - using (var muxer = Config.GetUnsecuredConnection(waitForOpen: true)) - { - var conn = muxer.GetDatabase(2); - var server = Config.GetServer(muxer); - conn.KeyDelete("append"); - var l0 = server.Features.StringLength ? conn.StringLengthAsync("append") : null; - - var s0 = conn.StringGetAsync("append"); - - conn.StringSetAsync("append", "abc"); - var s1 = conn.StringGetAsync("append"); - var l1 = server.Features.StringLength ? conn.StringLengthAsync("append") : null; - - var result = conn.StringAppendAsync("append", Encode("defgh")); - var s3 = conn.StringGetAsync("append"); - var l2 = server.Features.StringLength ? conn.StringLengthAsync("append") : null; - - Assert.AreEqual(null, (string)conn.Wait(s0)); - Assert.AreEqual("abc", (string)conn.Wait(s1)); - Assert.AreEqual(8, conn.Wait(result)); - Assert.AreEqual("abcdefgh", (string)conn.Wait(s3)); - - if (server.Features.StringLength) - { - Assert.AreEqual(0, conn.Wait(l0)); - Assert.AreEqual(3, conn.Wait(l1)); - Assert.AreEqual(8, conn.Wait(l2)); - } - } - } - [Test] - public void Set() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(2); - conn.KeyDeleteAsync("set"); - - conn.StringSetAsync("set", "abc"); - var v1 = conn.StringGetAsync("set"); - - conn.StringSetAsync("set", Encode("def")); - var v2 = conn.StringGetAsync("set"); - - Assert.AreEqual("abc", (string)conn.Wait(v1)); - Assert.AreEqual("def", (string)Decode(conn.Wait(v2))); - } - } - - [Test] - public void SetNotExists() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(2); - conn.KeyDeleteAsync("set"); - conn.KeyDeleteAsync("set2"); - conn.KeyDeleteAsync("set3"); - conn.StringSetAsync("set", "abc"); - - var x0 = conn.StringSetAsync("set", "def", when: When.NotExists); - var x1 = conn.StringSetAsync("set", Encode("def"), when: When.NotExists); - var x2 = conn.StringSetAsync("set2", "def", when: When.NotExists); - var x3 = conn.StringSetAsync("set3", Encode("def"), when: When.NotExists); - - var s0 = conn.StringGetAsync("set"); - var s2 = conn.StringGetAsync("set2"); - var s3 = conn.StringGetAsync("set3"); - - Assert.IsFalse(conn.Wait(x0)); - Assert.IsFalse(conn.Wait(x1)); - Assert.IsTrue(conn.Wait(x2)); - Assert.IsTrue(conn.Wait(x3)); - Assert.AreEqual("abc", (string)conn.Wait(s0)); - Assert.AreEqual("def", (string)conn.Wait(s2)); - Assert.AreEqual("def", (string)conn.Wait(s3)); - } - } - - [Test] - public void Ranges() - { - using (var muxer = Config.GetUnsecuredConnection(waitForOpen: true)) - { - if (!Config.GetFeatures(muxer).StringSetRange) Assert.Inconclusive(); - var conn = muxer.GetDatabase(2); - - conn.KeyDeleteAsync("range"); - - conn.StringSetAsync("range", "abcdefghi"); - conn.StringSetRangeAsync("range", 2, "xy"); - conn.StringSetRangeAsync("range", 4, Encode("z")); - - var val = conn.StringGetAsync("range"); - - Assert.AreEqual("abxyzfghi", (string)conn.Wait(val)); - } - } - - [Test] - public void IncrDecr() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(2); - conn.KeyDeleteAsync("incr"); - - conn.StringSetAsync("incr", "2"); - var v1 = conn.StringIncrementAsync("incr"); - var v2 = conn.StringIncrementAsync("incr", 5); - var v3 = conn.StringIncrementAsync("incr", -2); - var v4 = conn.StringDecrementAsync("incr"); - var v5 = conn.StringDecrementAsync("incr", 5); - var v6 = conn.StringDecrementAsync("incr", -2); - var s = conn.StringGetAsync("incr"); - - Assert.AreEqual(3, conn.Wait(v1)); - Assert.AreEqual(8, conn.Wait(v2)); - Assert.AreEqual(6, conn.Wait(v3)); - Assert.AreEqual(5, conn.Wait(v4)); - Assert.AreEqual(0, conn.Wait(v5)); - Assert.AreEqual(2, conn.Wait(v6)); - Assert.AreEqual("2", (string)conn.Wait(s)); - } - } - [Test] - public void IncrDecrFloat() - { - using (var muxer = Config.GetUnsecuredConnection(waitForOpen: true)) - { - if (!Config.GetFeatures(muxer).IncrementFloat) Assert.Inconclusive(); - var conn = muxer.GetDatabase(2); - conn.KeyDelete("incr"); - - conn.StringSetAsync("incr", "2"); - var v1 = conn.StringIncrementAsync("incr", 1.1); - var v2 = conn.StringIncrementAsync("incr", 5.0); - var v3 = conn.StringIncrementAsync("incr", -2.0); - var v4 = conn.StringIncrementAsync("incr", -1.0); - var v5 = conn.StringIncrementAsync("incr", -5.0); - var v6 = conn.StringIncrementAsync("incr", 2.0); - - var s = conn.StringGetAsync("incr"); - - Config.AssertNearlyEqual(3.1, conn.Wait(v1)); - Config.AssertNearlyEqual(8.1, conn.Wait(v2)); - Config.AssertNearlyEqual(6.1, conn.Wait(v3)); - Config.AssertNearlyEqual(5.1, conn.Wait(v4)); - Config.AssertNearlyEqual(0.1, conn.Wait(v5)); - Config.AssertNearlyEqual(2.1, conn.Wait(v6)); - Assert.AreEqual("2.1", (string)conn.Wait(s)); - } - } - - [Test] - public void GetRange() - { - using (var muxer = Config.GetUnsecuredConnection(waitForOpen: true)) - { - var conn = muxer.GetDatabase(2); - conn.KeyDeleteAsync("range"); - - conn.StringSetAsync("range", "abcdefghi"); - var s = conn.StringGetRangeAsync("range", 2, 4); - var b = conn.StringGetRangeAsync("range", 2, 4); - - Assert.AreEqual("cde", (string)conn.Wait(s)); - Assert.AreEqual("cde", Decode(conn.Wait(b))); - } - } - - [Test] - public void BitCount() - { - using (var muxer = Config.GetUnsecuredConnection(waitForOpen: true)) - { - if (!Config.GetFeatures(muxer).BitwiseOperations) Assert.Inconclusive(); - - var conn = muxer.GetDatabase(0); - conn.StringSetAsync("mykey", "foobar"); - var r1 = conn.StringBitCountAsync("mykey"); - var r2 = conn.StringBitCountAsync("mykey", 0, 0); - var r3 = conn.StringBitCountAsync("mykey", 1, 1); - - Assert.AreEqual(26, conn.Wait(r1)); - Assert.AreEqual(4, conn.Wait(r2)); - Assert.AreEqual(6, conn.Wait(r3)); - } - } - - [Test] - public void BitOp() - { - using (var muxer = Config.GetUnsecuredConnection(waitForOpen: true)) - { - if (!Config.GetFeatures(muxer).BitwiseOperations) Assert.Inconclusive(); - var conn = muxer.GetDatabase(0); - conn.StringSetAsync("key1", new byte[] { 3 }); - conn.StringSetAsync("key2", new byte[] { 6 }); - conn.StringSetAsync("key3", new byte[] { 12 }); - - var len_and = conn.StringBitOperationAsync(Bitwise.And, "and", new RedisKey[] { "key1", "key2", "key3" }); - var len_or = conn.StringBitOperationAsync(Bitwise.Or, "or", new RedisKey[] { "key1", "key2", "key3" }); - var len_xor = conn.StringBitOperationAsync(Bitwise.Xor, "xor", new RedisKey[] { "key1", "key2", "key3" }); - var len_not = conn.StringBitOperationAsync(Bitwise.Not, "not", "key1"); - - Assert.AreEqual(1, conn.Wait(len_and)); - Assert.AreEqual(1, conn.Wait(len_or)); - Assert.AreEqual(1, conn.Wait(len_xor)); - Assert.AreEqual(1, conn.Wait(len_not)); - - var r_and = ((byte[])conn.Wait(conn.StringGetAsync("and"))).Single(); - var r_or = ((byte[])conn.Wait(conn.StringGetAsync("or"))).Single(); - var r_xor = ((byte[])conn.Wait(conn.StringGetAsync("xor"))).Single(); - var r_not = ((byte[])conn.Wait(conn.StringGetAsync("not"))).Single(); - - Assert.AreEqual((byte)(3 & 6 & 12), r_and); - Assert.AreEqual((byte)(3 | 6 | 12), r_or); - Assert.AreEqual((byte)(3 ^ 6 ^ 12), r_xor); - Assert.AreEqual(unchecked((byte)(~3)), r_not); - - } - - } - - [Test] - public void RangeString() - { - using (var muxer = Config.GetUnsecuredConnection()) - { - var conn = muxer.GetDatabase(0); - conn.StringSetAsync("my key", "hello world"); - var result = conn.StringGetRangeAsync("my key", 2, 6); - Assert.AreEqual("llo w", (string)conn.Wait(result)); - } - } - static byte[] Encode(string value) { return Encoding.UTF8.GetBytes(value); } - static string Decode(byte[] value) { return Encoding.UTF8.GetString(value); } - } -} diff --git a/MigratedBookSleeveTestSuite/Transactions.cs b/MigratedBookSleeveTestSuite/Transactions.cs deleted file mode 100644 index 9192201a7..000000000 --- a/MigratedBookSleeveTestSuite/Transactions.cs +++ /dev/null @@ -1,296 +0,0 @@ -//using System.Threading.Tasks; -//using NUnit.Framework; -//using System.Collections.Generic; -//using System; -//using System.Linq; -//using BookSleeve; -//using System.Text; -//using System.Threading; - -//namespace Tests -//{ -// [TestFixture] -// public class Transactions // http://redis.io/commands#transactions -// { - - -// [Test] -// public void TestBasicMultiExec() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(1, "tran"); -// conn.Keys.Remove(2, "tran"); - -// using (var tran = conn.CreateTransaction()) -// { -// var s1 = tran.Strings.Set(1, "tran", "abc"); -// var s2 = tran.Strings.Set(2, "tran", "def"); -// var g1 = tran.Strings.GetString(1, "tran"); -// var g2 = tran.Strings.GetString(2, "tran"); - -// var outsideTran = conn.Strings.GetString(1, "tran"); - -// var exec = tran.Execute(); - -// Assert.IsNull(conn.Wait(outsideTran)); -// Assert.AreEqual("abc", conn.Wait(g1)); -// Assert.AreEqual("def", conn.Wait(g2)); -// conn.Wait(s1); -// conn.Wait(s2); -// conn.Wait(exec); -// } - -// } -// } - -// [Test] -// public void TestRollback() -// { -// using (var conn = Config.GetUnsecuredConnection()) -// using (var tran = conn.CreateTransaction()) -// { -// var task = tran.Strings.Set(4, "abc", "def"); -// tran.Discard(); - -// Assert.IsTrue(task.IsCanceled, "should be cancelled"); -// try -// { -// conn.Wait(task); -// } -// catch (TaskCanceledException) -// { }// ok, else boom! - -// } -// } - -// [Test] -// public void TestDispose() -// { -// Task task; -// using (var conn = Config.GetUnsecuredConnection()) -// { -// using (var tran = conn.CreateTransaction()) -// { -// task = tran.Strings.Set(4, "abc", "def"); -// } -// Assert.IsTrue(task.IsCanceled, "should be cancelled"); -// try -// { -// conn.Wait(task); -// } -// catch (TaskCanceledException) -// { }// ok, else boom! -// } -// } - -// [Test] -// public void BlogDemo() -// { -// int db = 8; -// using (var conn = Config.GetUnsecuredConnection()) -// { -// conn.Keys.Remove(db, "foo"); // just to reset -// using (var tran = conn.CreateTransaction()) -// { // deliberately ignoring INCRBY here -// tran.AddCondition(Condition.KeyNotExists(db, "foo")); -// var t1 = tran.Strings.Increment(db, "foo"); -// var t2 = tran.Strings.Increment(db, "foo"); -// var val = tran.Strings.GetString(db, "foo"); - -// var t3 = tran.Execute(); // this *still* returns a Task - -// Assert.AreEqual(true, conn.Wait(t3)); -// Assert.AreEqual(1, conn.Wait(t1)); -// Assert.AreEqual(2, conn.Wait(t2)); -// Assert.AreEqual("2", conn.Wait(val)); -// } -// } -// } - -// [Test] -// public void AbortWorks() -// { -// using (var conn = Config.GetUnsecuredConnection(waitForOpen: true)) -// { -// conn.CompletionMode = ResultCompletionMode.PreserveOrder; -// conn.Keys.Remove(0, "AbortWorks"); -// using (var tran = conn.CreateTransaction()) -// { -// var condition = tran.AddCondition(Condition.KeyExists(0, "AbortWorks")); -// var rename = tran.Strings.Increment(0, "key"); -// var success = conn.Wait(tran.Execute()); - -// Assert.IsFalse(success, "success"); -// Assert.IsFalse(conn.Wait(condition), "condition"); -// Assert.AreEqual(TaskStatus.Canceled, rename.Status, "rename"); -// } -// } -// } -// [Test] -// public void SignalRSend() -// { -// const int db = 3; -// const string idKey = "newid"; -// const string channel = "SignalRSend"; -// using (var conn = Config.GetUnsecuredConnection(waitForOpen: true)) -// using (var sub = conn.GetOpenSubscriberChannel()) -// { -// conn.CompletionMode = ResultCompletionMode.ConcurrentIfContinuation; -// sub.CompletionMode = ResultCompletionMode.PreserveOrder; -// var received = new List(); -// sub.Subscribe(channel, (chan, payload) => -// { -// lock (received) { received.Add(Encoding.UTF8.GetString(payload)); } -// }); -// conn.Keys.Remove(db, idKey); - -// int totalAttempts = 0; -// var evt = new ManualResetEvent(false); -// const int threadCount = 2; -// const int perThread = 5; -// int unreadyThreads = threadCount; -// ParameterizedThreadStart work = state => -// { -// string thread = (string)state; -// if (Interlocked.Decrement(ref unreadyThreads) == 0) -// { -// // all threads ready; unleash the hounds! -// evt.Set(); -// } -// else -// { -// evt.WaitOne(); -// } - -// for (int i = 0; i < perThread; i++) -// { -// int attempts = Send(conn, idKey, db, channel, thread + ":" + i).Result; -// Interlocked.Add(ref totalAttempts, attempts); -// } - -// }; -// var threads = new Thread[unreadyThreads]; -// for (int i = 0; i < threads.Length; i++) threads[i] = new Thread(work); -// for (int i = 0; i < threads.Length; i++) threads[i].Start(i.ToString()); -// for (int i = 0; i < threads.Length; i++) threads[i].Join(); - -// const int expected = perThread * threadCount; -// // it doesn't matter that this number is big; we are testing the pathological worst-case -// // scenario here; multiple threads aggressively causing conflicts -// Console.WriteLine("total messages: {0} (vs {1} theoretical)", totalAttempts, expected); - -// // check we got everything we expected, and nothing more; the messages should -// // all be increasing; we should have every thread/loop combination -// Assert.AreEqual(expected, received.Count, "total messages"); -// for (int i = 0; i < expected; i++) -// { -// string want = (i + 1) + ":"; -// Assert.IsTrue(received[i].StartsWith(want), want); -// } -// for (int i = 0; i < threadCount; i++) -// { -// for (int j = 0; j < perThread; j++) -// { -// string want = ":" + i + ":" + j; -// bool found = received.Any(x => x.EndsWith(want)); -// Assert.IsTrue(found, want); -// } -// } -// } -// } -// static async Task Send(RedisConnection conn, string idKey, int db, string channel, string data) -// { -// int attempts = 0; -// bool success; -// do -// { -// var oldId = await conn.Strings.GetInt64(db, idKey).SafeAwaitable().ConfigureAwait(false); // important: let this be nullable; -// // means "doesn't exist" -// var newId = (oldId ?? 0) + 1; -// var payload = Pack(newId, data); - -// using (var tran = conn.CreateTransaction()) -// { -// var x0 = tran.AddCondition(Condition.KeyEquals(db, idKey, oldId)).SafeAwaitable(); -// var x1 = tran.Strings.Increment(db, idKey).SafeAwaitable(); -// var x2 = tran.Publish(channel, payload).SafeAwaitable(); -// success = await tran.Execute().SafeAwaitable().ConfigureAwait(false); - -// if (success) -// { -// // still expect all of these to get answers -// await Task.WhenAll(x0, x1, x2); - -// Assert.IsTrue(x0.Result, "condition passed"); -// Assert.AreEqual(newId, x1.Result); -// } -// else -// { -// // can't say much about x0; could have got past that -// Assert.IsTrue(await IsCancelled(x1)); -// Assert.IsTrue(await IsCancelled(x2)); -// } - -// attempts++; -// } -// } while (!success); -// return attempts; -// } - -// [Test] -// public void Issue43() -// { -// using(var conn = Config.GetRemoteConnection()) -// { -// conn.Keys.Remove(0, "anExistingKey1"); -// conn.Keys.Remove(0, "anExistingKey2"); -// conn.Keys.Remove(0, "anExistingKey3"); -// conn.Strings.Set(0, "anExistingKey1", "anExistingKey1"); -// conn.Strings.Set(0, "anExistingKey2", "anExistingKey2"); -// conn.Strings.Set(0, "anExistingKey3", "anExistingKey3"); -// for(int i = 0; i < 10000; i++) -// { -// using(var tx = conn.CreateTransaction()) -// { -// var cond1 = tx.AddCondition(Condition.KeyExists(0, "anExistingKey1")); -// var cond2 = tx.AddCondition(Condition.KeyExists(0, "anExistingKey2")); -// var cond3 = tx.AddCondition(Condition.KeyExists(0, "anExistingKey3")); - -// tx.Strings.Increment(0, "foo", 1); -// tx.Strings.Increment(0, "foo", 1); -// tx.Strings.Increment(0, "foo", 1); - -// var txRes = tx.Execute(); - -// Assert.IsTrue(tx.Wait(cond1), "cond1" + i); //--> ok -// Assert.IsTrue(tx.Wait(cond2), "cond2" + i); //--> ok -// Assert.IsTrue(tx.Wait(cond3), "cond3" + i); //--> ok -// Assert.IsTrue(tx.Wait(txRes), "txRes" + i); //--> not ok: false -// } -// } -// } -// } - -// static async Task IsCancelled(Task task) -// { -// try -// { -// await task; -// return false; -// } -// catch -// { -// return task.IsCanceled; -// } -// } - -// static byte[] Pack(long id, string data) -// { -// return Encoding.UTF8.GetBytes(id + ":" + data); -// } - - -// } -//} - diff --git a/MigratedBookSleeveTestSuite/redis-sharp.cs b/MigratedBookSleeveTestSuite/redis-sharp.cs deleted file mode 100644 index d9d9b3016..000000000 --- a/MigratedBookSleeveTestSuite/redis-sharp.cs +++ /dev/null @@ -1,877 +0,0 @@ -// preamble: original source: https://github.com/migueldeicaza/redis-sharp/blob/master/redis-sharp.cs -// included here for performance test purposes only; this is a separate and parallel implementation - - -// -// redis-sharp.cs: ECMA CLI Binding to the Redis key-value storage system -// -// Authors: -// Miguel de Icaza (miguel@gnome.org) -// -// Copyright 2010 Novell, Inc. -// -// Licensed under the same terms of reddis: new BSD license. -// - - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net.Sockets; -using System.Text; - -public class Redis : IDisposable -{ - Socket socket; - BufferedStream bstream; - - public enum KeyType - { - None, String, List, Set - } - - public class ResponseException : Exception - { - public ResponseException(string code) - : base("Response error") - { - Code = code; - } - - public string Code { get; private set; } - } - - public Redis(string host, int port) - { - if (host == null) - throw new ArgumentNullException(nameof(host)); - - Host = host; - Port = port; - SendTimeout = -1; - } - - public Redis(string host) - : this(host, 6379) - { - } - - public Redis() - : this("localhost", 6379) - { - } - - public string Host { get; private set; } - public int Port { get; private set; } - public int RetryTimeout { get; set; } - public int RetryCount { get; set; } - public int SendTimeout { get; set; } - public string Password { get; set; } - - int db; - public int Db - { - get - { - return db; - } - - set - { - db = value; - SendExpectSuccess("SELECT {0}\r\n", db); - } - } - - public string this[string key] - { - get { return GetString(key); } - set { Set(key, value); } - } - - public void Set(string key, string value) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - if (value == null) - throw new ArgumentNullException(nameof(value)); - - Set(key, Encoding.UTF8.GetBytes(value)); - } - - public void Set(string key, byte[] value) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - if (value == null) - throw new ArgumentNullException(nameof(value)); - - if (value.Length > 1073741824) - throw new ArgumentException("value exceeds 1G", nameof(value)); - - if (!SendDataCommand(value, "SET {0} {1}\r\n", key, value.Length)) - throw new Exception("Unable to connect"); - ExpectSuccess(); - } - - public bool SetNX(string key, string value) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - if (value == null) - throw new ArgumentNullException(nameof(value)); - - return SetNX(key, Encoding.UTF8.GetBytes(value)); - } - - public bool SetNX(string key, byte[] value) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - if (value == null) - throw new ArgumentNullException(nameof(value)); - - if (value.Length > 1073741824) - throw new ArgumentException("value exceeds 1G", nameof(value)); - - return SendDataExpectInt(value, "SETNX {0} {1}\r\n", key, value.Length) > 0 ? true : false; - } - - public void Set(IDictionary dict) - { - Set(dict.ToDictionary(k => k.Key, v => Encoding.UTF8.GetBytes(v.Value))); - } - - public void Set(IDictionary dict) - { - if (dict == null) - throw new ArgumentNullException(nameof(dict)); - - var nl = Encoding.UTF8.GetBytes("\r\n"); - - var ms = new MemoryStream(); - foreach (var key in dict.Keys) - { - var val = dict[key]; - - var kLength = Encoding.UTF8.GetBytes("$" + key.Length + "\r\n"); - var k = Encoding.UTF8.GetBytes(key + "\r\n"); - var vLength = Encoding.UTF8.GetBytes("$" + val.Length + "\r\n"); - ms.Write(kLength, 0, kLength.Length); - ms.Write(k, 0, k.Length); - ms.Write(vLength, 0, vLength.Length); - ms.Write(val, 0, val.Length); - ms.Write(nl, 0, nl.Length); - } - - SendDataCommand(ms.ToArray(), "*" + (dict.Count * 2 + 1) + "\r\n$4\r\nMSET\r\n"); - ExpectSuccess(); - } - - public byte[] Get(string key) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - return SendExpectData(null, "GET " + key + "\r\n"); - } - - public string GetString(string key) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - return Encoding.UTF8.GetString(Get(key)); - } - - public byte[][] Sort(SortOptions options) - { - return SendDataCommandExpectMultiBulkReply(null, options.ToCommand() + "\r\n"); - } - - public byte[] GetSet(string key, byte[] value) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - if (value == null) - throw new ArgumentNullException(nameof(value)); - - if (value.Length > 1073741824) - throw new ArgumentException("value exceeds 1G", nameof(value)); - - if (!SendDataCommand(value, "GETSET {0} {1}\r\n", key, value.Length)) - throw new Exception("Unable to connect"); - - return ReadData(); - } - - public string GetSet(string key, string value) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - if (value == null) - throw new ArgumentNullException(nameof(value)); - return Encoding.UTF8.GetString(GetSet(key, Encoding.UTF8.GetBytes(value))); - } - - string ReadLine() - { - var sb = new StringBuilder(); - int c; - - while ((c = bstream.ReadByte()) != -1) - { - if (c == '\r') - continue; - if (c == '\n') - break; - sb.Append((char)c); - } - return sb.ToString(); - } - - void Connect() - { - socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - socket.NoDelay = true; - socket.SendTimeout = SendTimeout; - socket.Connect(Host, Port); - if (!socket.Connected) - { - socket.Dispose(); - socket = null; - return; - } - bstream = new BufferedStream(new NetworkStream(socket), 16 * 1024); - - if (Password != null) - SendExpectSuccess("AUTH {0}\r\n", Password); - } - - byte[] end_data = new byte[] { (byte)'\r', (byte)'\n' }; - - bool SendDataCommand(byte[] data, string cmd, params object[] args) - { - if (socket == null) - Connect(); - if (socket == null) - return false; - - var s = args.Length > 0 ? String.Format(cmd, args) : cmd; - byte[] r = Encoding.UTF8.GetBytes(s); - try - { - Log("S: " + String.Format(cmd, args)); - socket.Send(r); - if (data != null) - { - socket.Send(data); - socket.Send(end_data); - } - } - catch (SocketException) - { - // timeout; - socket.Dispose(); - socket = null; - - return false; - } - return true; - } - - bool SendCommand(string cmd, params object[] args) - { - if (socket == null) - Connect(); - if (socket == null) - return false; - - var s = args != null && args.Length > 0 ? String.Format(cmd, args) : cmd; - byte[] r = Encoding.UTF8.GetBytes(s); - try - { - Log("S: " + String.Format(cmd, args)); - socket.Send(r); - } - catch (SocketException) - { - // timeout; - socket.Dispose(); - socket = null; - - return false; - } - return true; - } - - [Conditional("DEBUG")] - void Log(string fmt, params object[] args) - { - Console.WriteLine("{0}", String.Format(fmt, args).Trim()); - } - - void ExpectSuccess() - { - int c = bstream.ReadByte(); - if (c == -1) - throw new ResponseException("No more data"); - - var s = ReadLine(); - Log((char)c + s); - if (c == '-') - throw new ResponseException(s.StartsWith("ERR") ? s.Substring(4) : s); - } - - void SendExpectSuccess(string cmd, params object[] args) - { - if (!SendCommand(cmd, args)) - throw new Exception("Unable to connect"); - - ExpectSuccess(); - } - - int SendDataExpectInt(byte[] data, string cmd, params object[] args) - { - if (!SendDataCommand(data, cmd, args)) - throw new Exception("Unable to connect"); - - int c = bstream.ReadByte(); - if (c == -1) - throw new ResponseException("No more data"); - - var s = ReadLine(); - Log("R: " + s); - if (c == '-') - throw new ResponseException(s.StartsWith("ERR") ? s.Substring(4) : s); - if (c == ':') - { - int i; - if (int.TryParse(s, out i)) - return i; - } - throw new ResponseException("Unknown reply on integer request: " + c + s); - } - - int SendExpectInt(string cmd, params object[] args) - { - if (!SendCommand(cmd, args)) - throw new Exception("Unable to connect"); - - int c = bstream.ReadByte(); - if (c == -1) - throw new ResponseException("No more data"); - - var s = ReadLine(); - Log("R: " + s); - if (c == '-') - throw new ResponseException(s.StartsWith("ERR") ? s.Substring(4) : s); - if (c == ':') - { - int i; - if (int.TryParse(s, out i)) - return i; - } - throw new ResponseException("Unknown reply on integer request: " + c + s); - } - - string SendExpectString(string cmd, params object[] args) - { - if (!SendCommand(cmd, args)) - throw new Exception("Unable to connect"); - - int c = bstream.ReadByte(); - if (c == -1) - throw new ResponseException("No more data"); - - var s = ReadLine(); - Log("R: " + s); - if (c == '-') - throw new ResponseException(s.StartsWith("ERR") ? s.Substring(4) : s); - if (c == '+') - return s; - - throw new ResponseException("Unknown reply on integer request: " + c + s); - } - - // - // This one does not throw errors - // - string SendGetString(string cmd, params object[] args) - { - if (!SendCommand(cmd, args)) - throw new Exception("Unable to connect"); - - return ReadLine(); - } - - byte[] SendExpectData(byte[] data, string cmd, params object[] args) - { - if (!SendDataCommand(data, cmd, args)) - throw new Exception("Unable to connect"); - - return ReadData(); - } - - byte[] ReadData() - { - string r = ReadLine(); - Log("R: {0}", r); - if (r.Length == 0) - throw new ResponseException("Zero length respose"); - - char c = r[0]; - if (c == '-') - throw new ResponseException(r.StartsWith("-ERR") ? r.Substring(5) : r.Substring(1)); - - if (c == '$') - { - if (r == "$-1") - return null; - int n; - - if (Int32.TryParse(r.Substring(1), out n)) - { - byte[] retbuf = new byte[n]; - - int bytesRead = 0; - do - { - int read = bstream.Read(retbuf, bytesRead, n - bytesRead); - if (read < 1) - throw new ResponseException("Invalid termination mid stream"); - bytesRead += read; - } - while (bytesRead < n); - if (bstream.ReadByte() != '\r' || bstream.ReadByte() != '\n') - throw new ResponseException("Invalid termination"); - return retbuf; - } - throw new ResponseException("Invalid length"); - } - - //returns the number of matches - if (c == '*') - { - int n; - if (Int32.TryParse(r.Substring(1), out n)) - return n <= 0 ? new byte[0] : ReadData(); - - throw new ResponseException("Unexpected length parameter" + r); - } - - throw new ResponseException("Unexpected reply: " + r); - } - - public bool ContainsKey(string key) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - return SendExpectInt("EXISTS " + key + "\r\n") == 1; - } - - public bool Remove(string key) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - return SendExpectInt("DEL " + key + "\r\n", key) == 1; - } - - public int Remove(params string[] args) - { - if (args == null) - throw new ArgumentNullException(nameof(args)); - return SendExpectInt("DEL " + string.Join(" ", args) + "\r\n"); - } - - public int Increment(string key) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - return SendExpectInt("INCR " + key + "\r\n"); - } - - public int Increment(string key, int count) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - return SendExpectInt("INCRBY {0} {1}\r\n", key, count); - } - - public int Decrement(string key) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - return SendExpectInt("DECR " + key + "\r\n"); - } - - public int Decrement(string key, int count) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - return SendExpectInt("DECRBY {0} {1}\r\n", key, count); - } - - public KeyType TypeOf(string key) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - switch (SendExpectString("TYPE {0}\r\n", key)) - { - case "none": - return KeyType.None; - case "string": - return KeyType.String; - case "set": - return KeyType.Set; - case "list": - return KeyType.List; - } - throw new ResponseException("Invalid value"); - } - - public string RandomKey() - { - return SendExpectString("RANDOMKEY\r\n"); - } - - public bool Rename(string oldKeyname, string newKeyname) - { - if (oldKeyname == null) - throw new ArgumentNullException(nameof(oldKeyname)); - if (newKeyname == null) - throw new ArgumentNullException(nameof(newKeyname)); - return SendGetString("RENAME {0} {1}\r\n", oldKeyname, newKeyname)[0] == '+'; - } - - public bool Expire(string key, int seconds) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - return SendExpectInt("EXPIRE {0} {1}\r\n", key, seconds) == 1; - } - - public bool ExpireAt(string key, int time) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - return SendExpectInt("EXPIREAT {0} {1}\r\n", key, time) == 1; - } - - public int TimeToLive(string key) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - return SendExpectInt("TTL {0}\r\n", key); - } - - public int DbSize - { - get - { - return SendExpectInt("DBSIZE\r\n"); - } - } - - public string Save() - { - return SendGetString("SAVE\r\n"); - } - - public void BackgroundSave() - { - SendGetString("BGSAVE\r\n"); - } - - public void Shutdown() - { - SendGetString("SHUTDOWN\r\n"); - } - - public void FlushAll() - { - SendGetString("FLUSHALL\r\n"); - } - - public void FlushDb() - { - SendGetString("FLUSHDB\r\n"); - } - - const long UnixEpoch = 621355968000000000L; - - public DateTime LastSave - { - get - { - int t = SendExpectInt("LASTSAVE\r\n"); - - return new DateTime(UnixEpoch) + TimeSpan.FromSeconds(t); - } - } - - public Dictionary GetInfo() - { - byte[] r = SendExpectData(null, "INFO\r\n"); - var dict = new Dictionary(); - - foreach (var line in Encoding.UTF8.GetString(r).Split('\n')) - { - int p = line.IndexOf(':'); - if (p == -1) - continue; - dict.Add(line.Substring(0, p), line.Substring(p + 1)); - } - return dict; - } - - public string[] Keys - { - get - { - string commandResponse = Encoding.UTF8.GetString(SendExpectData(null, "KEYS *\r\n")); - if (commandResponse.Length < 1) - return new string[0]; - else - return commandResponse.Split(' '); - } - } - - public string[] GetKeys(string pattern) - { - if (pattern == null) - throw new ArgumentNullException("key"); - var keys = SendExpectData(null, "KEYS {0}\r\n", pattern); - if (keys.Length == 0) - return new string[0]; - return Encoding.UTF8.GetString(keys).Split(' '); - } - - public byte[][] GetKeys(params string[] keys) - { - if (keys == null) - throw new ArgumentNullException("key1"); - if (keys.Length == 0) - throw new ArgumentException("keys"); - - return SendDataCommandExpectMultiBulkReply(null, "MGET {0}\r\n", string.Join(" ", keys)); - } - - - public byte[][] SendDataCommandExpectMultiBulkReply(byte[] data, string command, params object[] args) - { - if (!SendDataCommand(data, command, args)) - throw new Exception("Unable to connect"); - int c = bstream.ReadByte(); - if (c == -1) - throw new ResponseException("No more data"); - - var s = ReadLine(); - Log("R: " + s); - if (c == '-') - throw new ResponseException(s.StartsWith("ERR") ? s.Substring(4) : s); - if (c == '*') - { - int count; - if (int.TryParse(s, out count)) - { - var result = new byte[count][]; - - for (int i = 0; i < count; i++) - result[i] = ReadData(); - - return result; - } - } - throw new ResponseException("Unknown reply on multi-request: " + c + s); - } - - #region List commands - public byte[][] ListRange(string key, int start, int end) - { - return SendDataCommandExpectMultiBulkReply(null, "LRANGE {0} {1} {2}\r\n", key, start, end); - } - - public void RightPush(string key, string value) - { - SendExpectSuccess("RPUSH {0} {1}\r\n{2}\r\n", key, value.Length, value); - } - - public int ListLength(string key) - { - return SendExpectInt("LLEN {0}\r\n", key); - } - - public byte[] ListIndex(string key, int index) - { - SendCommand("LINDEX {0} {1}\r\n", key, index); - return ReadData(); - } - - public byte[] LeftPop(string key) - { - SendCommand("LPOP {0}\r\n", key); - return ReadData(); - } - #endregion - - #region Set commands - public bool AddToSet(string key, byte[] member) - { - return SendDataExpectInt(member, "SADD {0} {1}\r\n", key, member.Length) > 0; - } - - public bool AddToSet(string key, string member) - { - return AddToSet(key, Encoding.UTF8.GetBytes(member)); - } - - public int CardinalityOfSet(string key) - { - return SendDataExpectInt(null, "SCARD {0}\r\n", key); - } - - public bool IsMemberOfSet(string key, byte[] member) - { - return SendDataExpectInt(member, "SISMEMBER {0} {1}\r\n", key, member.Length) > 0; - } - - public bool IsMemberOfSet(string key, string member) - { - return IsMemberOfSet(key, Encoding.UTF8.GetBytes(member)); - } - - public byte[][] GetMembersOfSet(string key) - { - return SendDataCommandExpectMultiBulkReply(null, "SMEMBERS {0}\r\n", key); - } - - public byte[] GetRandomMemberOfSet(string key) - { - return SendExpectData(null, "SRANDMEMBER {0}\r\n", key); - } - - public byte[] PopRandomMemberOfSet(string key) - { - return SendExpectData(null, "SPOP {0}\r\n", key); - } - - public bool RemoveFromSet(string key, byte[] member) - { - return SendDataExpectInt(member, "SREM {0} {1}\r\n", key, member.Length) > 0; - } - - public bool RemoveFromSet(string key, string member) - { - return RemoveFromSet(key, Encoding.UTF8.GetBytes(member)); - } - - public byte[][] GetUnionOfSets(params string[] keys) - { - if (keys == null) - throw new ArgumentNullException(); - - return SendDataCommandExpectMultiBulkReply(null, "SUNION " + string.Join(" ", keys) + "\r\n"); - - } - - void StoreSetCommands(string cmd, string destKey, params string[] keys) - { - if (String.IsNullOrEmpty(cmd)) - throw new ArgumentNullException(nameof(cmd)); - - if (String.IsNullOrEmpty(destKey)) - throw new ArgumentNullException(nameof(destKey)); - - if (keys == null) - throw new ArgumentNullException(nameof(keys)); - - SendExpectSuccess("{0} {1} {2}\r\n", cmd, destKey, String.Join(" ", keys)); - } - - public void StoreUnionOfSets(string destKey, params string[] keys) - { - StoreSetCommands("SUNIONSTORE", destKey, keys); - } - - public byte[][] GetIntersectionOfSets(params string[] keys) - { - if (keys == null) - throw new ArgumentNullException(); - - return SendDataCommandExpectMultiBulkReply(null, "SINTER " + string.Join(" ", keys) + "\r\n"); - } - - public void StoreIntersectionOfSets(string destKey, params string[] keys) - { - StoreSetCommands("SINTERSTORE", destKey, keys); - } - - public byte[][] GetDifferenceOfSets(params string[] keys) - { - if (keys == null) - throw new ArgumentNullException(); - - return SendDataCommandExpectMultiBulkReply(null, "SDIFF " + string.Join(" ", keys) + "\r\n"); - } - - public void StoreDifferenceOfSets(string destKey, params string[] keys) - { - StoreSetCommands("SDIFFSTORE", destKey, keys); - } - - public bool MoveMemberToSet(string srcKey, string destKey, byte[] member) - { - return SendDataExpectInt(member, "SMOVE {0} {1} {2}\r\n", srcKey, destKey, member.Length) > 0; - } - #endregion - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - ~Redis() - { - Dispose(false); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - SendCommand("QUIT\r\n"); - socket.Dispose(); - socket = null; - } - } -} - -public class SortOptions -{ - public string Key { get; set; } - public bool Descending { get; set; } - public bool Lexographically { get; set; } - public Int32 LowerLimit { get; set; } - public Int32 UpperLimit { get; set; } - public string By { get; set; } - public string StoreInKey { get; set; } - public string Get { get; set; } - - public string ToCommand() - { - var command = "SORT " + this.Key; - if (LowerLimit != 0 || UpperLimit != 0) - command += " LIMIT " + LowerLimit + " " + UpperLimit; - if (Lexographically) - command += " ALPHA"; - if (!string.IsNullOrEmpty(By)) - command += " BY " + By; - if (!string.IsNullOrEmpty(Get)) - command += " GET " + Get; - if (!string.IsNullOrEmpty(StoreInKey)) - command += " STORE " + StoreInKey; - return command; - } -} diff --git a/NRediSearch.Test/ExampleUsage.cs b/NRediSearch.Test/ExampleUsage.cs deleted file mode 100644 index 3d5428899..000000000 --- a/NRediSearch.Test/ExampleUsage.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using Xunit; -using StackExchange.Redis; -using NRediSearch; -using System.Collections.Generic; -using System.Linq; - -namespace NRediSearch.Test -{ - public class ExampleUsage : IDisposable - { - ConnectionMultiplexer conn; - IDatabase db; - public ExampleUsage() - { - conn = ConnectionMultiplexer.Connect("127.0.0.1:6379"); - db = conn.GetDatabase(); - } - public void Dispose() - { - conn?.Dispose(); - conn = null; - db = null; - } - [Fact] - public void BasicUsage() - { - var client = new Client("testung", db); - - try { client.DropIndex(); } catch { } // reset DB - - // Defining a schema for an index and creating it: - var sc = new Schema() - .AddTextField("title", 5.0) - .AddTextField("body", 1.0) - .AddNumericField("price"); - - Assert.True(client.CreateIndex(sc, Client.IndexOptions.Default)); - - // note: using java API equivalent here; it would be nice to - // use meta-programming / reflection instead in .NET - - // Adding documents to the index: - var fields = new Dictionary(); - fields.Add("title", "hello world"); - fields.Add("body", "lorem ipsum"); - fields.Add("price", 1337); - - Assert.True(client.AddDocument("doc1", fields)); - - // Creating a complex query - var q = new Query("hello world") - .AddFilter(new Query.NumericFilter("price", 1300, 1350)) - .Limit(0, 5); - - // actual search - var res = client.Search(q); - - Assert.Equal(1, res.TotalResults); - var item = res.Documents.Single(); - Assert.Equal("doc1", item.Id); - - Assert.True(item.HasProperty("title")); - Assert.True(item.HasProperty("body")); - Assert.True(item.HasProperty("price")); - Assert.False(item.HasProperty("blap")); - - Assert.Equal("hello world", (string)item["title"]); - Assert.Equal("lorem ipsum", (string)item["body"]); - Assert.Equal(1337, (int)item["price"]); - - - - } - } -} diff --git a/NRediSearch.Test/NRediSearch.Test.csproj b/NRediSearch.Test/NRediSearch.Test.csproj deleted file mode 100644 index c6ff73816..000000000 --- a/NRediSearch.Test/NRediSearch.Test.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - netcoreapp1.1 - false - - - - - - - - - - - - - - - diff --git a/NRediSearch/Client.cs b/NRediSearch/Client.cs deleted file mode 100644 index 2d6611464..000000000 --- a/NRediSearch/Client.cs +++ /dev/null @@ -1,420 +0,0 @@ -// .NET port of https://github.com/RedisLabs/JRediSearch/ - -using StackExchange.Redis; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace NRediSearch -{ - public sealed class Client - { - [Flags] - public enum IndexOptions - { - /// - /// All options disabled - /// - None = 0, - /// - /// Set this to tell the index not to save term offset vectors. This reduces memory consumption but does not - /// allow performing exact matches, and reduces overall relevance of multi-term queries - /// - UseTermOffsets = 1, - /// - /// If set (default), we keep flags per index record telling us what fields the term appeared on, - /// and allowing us to filter results by field - /// - KeepFieldFlags = 2, - /// - /// If set, we keep an index of the top entries per term, allowing extremely fast single word queries - /// regardless of index size, at the cost of more memory - /// - UseScoreIndexes = 4, - /// - /// The default indexing options - use term offsets and keep fields flags - /// - Default = UseTermOffsets | KeepFieldFlags - } - private static void SerializeRedisArgs(IndexOptions flags, List args) - { - if ((flags & IndexOptions.UseTermOffsets) == 0) - { - args.Add("NOOFFSETS".Literal()); - } - if ((flags & IndexOptions.KeepFieldFlags) == 0) - { - args.Add("NOFIELDS".Literal()); - } - if ((flags & IndexOptions.UseScoreIndexes) == 0) - { - args.Add("NOSCOREIDX".Literal()); - } - } - private readonly IDatabaseAsync _db; - private IDatabase DbSync - => (_db as IDatabase) ?? throw new InvalidOperationException("Synchronous operations are not available on this database instance"); - - private readonly object _boxedIndexName; - public RedisKey IndexName => (RedisKey)_boxedIndexName; - public Client(RedisKey indexName, IDatabaseAsync db) - { - _db = db ?? throw new ArgumentNullException(nameof(db)); - _boxedIndexName = indexName; // only box once, not per-command - } - public Client(RedisKey indexName, IDatabase db) : this(indexName, (IDatabaseAsync)db) { } - - /// - /// Create the index definition in redis - /// - /// a schema definition - /// index option flags - /// true if successful - public bool CreateIndex(Schema schema, IndexOptions options) - { - var args = new List(); - - args.Add(_boxedIndexName); - SerializeRedisArgs(options, args); - args.Add("SCHEMA".Literal()); - - foreach (var f in schema.Fields) - { - f.SerializeRedisArgs(args); - } - - return (string)DbSync.Execute("FT.CREATE", args) == "OK"; - } - - /// - /// Create the index definition in redis - /// - /// a schema definition - /// index option flags - /// true if successful - public async Task CreateIndexAsync(Schema schema, IndexOptions options) - { - var args = new List(); - - args.Add(_boxedIndexName); - SerializeRedisArgs(options, args); - args.Add("SCHEMA".Literal()); - - foreach (var f in schema.Fields) - { - f.SerializeRedisArgs(args); - } - - return (string)await _db.ExecuteAsync("FT.CREATE", args).ConfigureAwait(false) == "OK"; - } - - /// - /// Search the index - /// - /// a object with the query string and optional parameters - /// a object with the results - public SearchResult Search(Query q) - { - var args = new List(); - args.Add(_boxedIndexName); - q.SerializeRedisArgs(args); - - var resp = (RedisResult[])DbSync.Execute("FT.SEARCH", args); - return new SearchResult(resp, !q.NoContent, q.WithScores, q.WithPayloads); - } - - /// - /// Search the index - /// - /// a object with the query string and optional parameters - /// a object with the results - public async Task SearchAsync(Query q) - { - var args = new List(); - args.Add(_boxedIndexName); - q.SerializeRedisArgs(args); - - var resp = (RedisResult[])await _db.ExecuteAsync("FT.SEARCH", args).ConfigureAwait(false); - return new SearchResult(resp, !q.NoContent, q.WithScores, q.WithPayloads); - } - - /// - /// Add a single document to the query - /// - /// the id of the document. It cannot belong to a document already in the index unless replace is set - /// the document's score, floating point number between 0 and 1 - /// a map of the document's fields - /// if set, we only index the document and do not save its contents. This allows fetching just doc ids - /// if set, and the document already exists, we reindex and update it - /// if set, we can save a payload in the index to be retrieved or evaluated by scoring functions on the server - public bool AddDocument(string docId, Dictionary fields, double score = 1.0, bool noSave = false, bool replace = false, byte[] payload = null) - { - var args = BuildAddDocumentArgs(docId, fields, score, noSave, replace, payload); - return (string)DbSync.Execute("FT.ADD", args) == "OK"; - } - - /// - /// Add a single document to the query - /// - /// the id of the document. It cannot belong to a document already in the index unless replace is set - /// the document's score, floating point number between 0 and 1 - /// a map of the document's fields - /// if set, we only index the document and do not save its contents. This allows fetching just doc ids - /// if set, and the document already exists, we reindex and update it - /// if set, we can save a payload in the index to be retrieved or evaluated by scoring functions on the server - public async Task AddDocumentAsync(string docId, Dictionary fields, double score = 1.0, bool noSave = false, bool replace = false, byte[] payload = null) - { - var args = BuildAddDocumentArgs(docId, fields, score, noSave, replace, payload); - return (string)await _db.ExecuteAsync("FT.ADD", args).ConfigureAwait(false) == "OK"; - } - - private List BuildAddDocumentArgs(string docId, Dictionary fields, double score, bool noSave, bool replace, byte[] payload) - { - var args = new List { _boxedIndexName, docId, score }; - if (noSave) - { - args.Add("NOSAVE".Literal()); - } - if (replace) - { - args.Add("REPLACE".Literal()); - } - if (payload != null) - { - args.Add("PAYLOAD".Literal()); - // TODO: Fix this - args.Add(payload); - } - - args.Add("FIELDS".Literal()); - foreach (var ent in fields) - { - args.Add(ent.Key); - args.Add(ent.Value); - } - return args; - } - - /// - /// replaceDocument is a convenience for calling addDocument with replace=true - /// - public bool ReplaceDocument(string docId, Dictionary fields, double score = 1.0, byte[] payload = null) - => AddDocument(docId, fields, score, false, true, payload); - - /// - /// replaceDocument is a convenience for calling addDocument with replace=true - /// - public Task ReplaceDocumentAsync(string docId, Dictionary fields, double score = 1.0, byte[] payload = null) - => AddDocumentAsync(docId, fields, score, false, true, payload); - - /// - /// Index a document already in redis as a HASH key. - /// - /// the id of the document in redis. This must match an existing, unindexed HASH key - /// the document's index score, between 0 and 1 - /// if set, and the document already exists, we reindex and update it - /// true on success - public bool AddHash(string docId, double score, bool replace) - { - var args = new List { _boxedIndexName, docId, score }; - if (replace) - { - args.Add("REPLACE".Literal()); - } - return (string)DbSync.Execute("FT.ADDHASH", args) == "OK"; - } - /// - /// Index a document already in redis as a HASH key. - /// - /// the id of the document in redis. This must match an existing, unindexed HASH key - /// the document's index score, between 0 and 1 - /// if set, and the document already exists, we reindex and update it - /// true on success - public async Task AddHashAsync(string docId, double score, bool replace) - { - var args = new List { _boxedIndexName, docId, score }; - if (replace) - { - args.Add("REPLACE".Literal()); - } - return (string)await _db.ExecuteAsync("FT.ADDHASH", args).ConfigureAwait(false) == "OK"; - } - - /// - /// Get the index info, including memory consumption and other statistics. - /// - /// TODO: Make a class for easier access to the index properties - /// a map of key/value pairs - public Dictionary GetInfo() - { - return ParseGetInfo(DbSync.Execute("FT.INFO", _boxedIndexName)); - } - /// - /// Get the index info, including memory consumption and other statistics. - /// - /// TODO: Make a class for easier access to the index properties - /// a map of key/value pairs - public async Task> GetInfoAsync() - { - return ParseGetInfo(await _db.ExecuteAsync("FT.INFO", _boxedIndexName).ConfigureAwait(false)); - } - static Dictionary ParseGetInfo(RedisResult value) - { - var res = (RedisValue[])value; - var info = new Dictionary(); - for (int i = 0; i < res.Length; i += 2) - { - var key = (string)res[i]; - var val = res[i + 1]; - info.Add(key, val); - } - return info; - } - - /// - /// Delete a document from the index. - /// - /// the document's id - /// true if it has been deleted, false if it did not exist - public bool DeleteDocument(string docId) - { - return (long)DbSync.Execute("FT.DEL", _boxedIndexName, docId) == 1; - } - - /// - /// Delete a document from the index. - /// - /// the document's id - /// true if it has been deleted, false if it did not exist - public async Task DeleteDocumentAsync(string docId) - { - return (long)await _db.ExecuteAsync("FT.DEL", _boxedIndexName, docId).ConfigureAwait(false) == 1; - } - - /// - /// Drop the index and all associated keys, including documents - /// - /// true on success - public bool DropIndex() - { - return (string)DbSync.Execute("FT.DROP", _boxedIndexName) == "OK"; - } - /// - /// Drop the index and all associated keys, including documents - /// - /// true on success - public async Task DropIndexAsync() - { - return (string) await _db.ExecuteAsync("FT.DROP", _boxedIndexName).ConfigureAwait(false) == "OK"; - } - - /// - /// Optimize memory consumption of the index by removing extra saved capacity. This does not affect speed - /// - public long OptimizeIndex() - { - return (long)DbSync.Execute("FT.OPTIMIZE", _boxedIndexName); - } - - /// - /// Optimize memory consumption of the index by removing extra saved capacity. This does not affect speed - /// - public async Task OptimizeIndexAsync() - { - return (long) await _db.ExecuteAsync("FT.OPTIMIZE", _boxedIndexName).ConfigureAwait(false); - } - - /// - /// Get the size of an autoc-complete suggestion dictionary - /// - public long CountSuggestions() - => (long)DbSync.Execute("FT.SUGLEN", _boxedIndexName); - - /// - /// Get the size of an autoc-complete suggestion dictionary - /// - public async Task CountSuggestionsAsync() - => (long)await _db.ExecuteAsync("FT.SUGLEN", _boxedIndexName).ConfigureAwait(false); - - /// - /// Add a suggestion string to an auto-complete suggestion dictionary. This is disconnected from the index definitions, and leaves creating and updating suggestino dictionaries to the user. - /// - /// the suggestion string we index - /// a floating point number of the suggestion string's weight - /// if set, we increment the existing entry of the suggestion by the given score, instead of replacing the score. This is useful for updating the dictionary based on user queries in real time - /// the current size of the suggestion dictionary. - public long AddSuggestion(string value, double score, bool increment = false) - { - object args = increment - ? new object[] { _boxedIndexName, value, score, "INCR".Literal() } - : new object[] { _boxedIndexName, value, score }; - return (long)DbSync.Execute("FT.SUGADD", args); - } - - /// - /// Add a suggestion string to an auto-complete suggestion dictionary. This is disconnected from the index definitions, and leaves creating and updating suggestino dictionaries to the user. - /// - /// the suggestion string we index - /// a floating point number of the suggestion string's weight - /// if set, we increment the existing entry of the suggestion by the given score, instead of replacing the score. This is useful for updating the dictionary based on user queries in real time - /// the current size of the suggestion dictionary. - public async Task AddSuggestionAsync(string value, double score, bool increment = false) - { - object args = increment - ? new object[] { _boxedIndexName, value, score, "INCR".Literal() } - : new object[] { _boxedIndexName, value, score }; - return (long)await _db.ExecuteAsync("FT.SUGADD", args).ConfigureAwait(false); - } - - /// - /// Delete a string from a suggestion index. - /// - /// the string to delete - public bool DeleteSuggestion(string value) - => (long)DbSync.Execute("FT.SUGDEL", _boxedIndexName, value) == 1; - - /// - /// Delete a string from a suggestion index. - /// - /// the string to delete - public async Task DeleteSuggestionAsync(string value) - => (long)await _db.ExecuteAsync("FT.SUGDEL", _boxedIndexName, value).ConfigureAwait(false) == 1; - - /// - /// Get completion suggestions for a prefix - /// - /// the prefix to complete on - /// if set,we do a fuzzy prefix search, including prefixes at levenshtein distance of 1 from the prefix sent - /// If set, we limit the results to a maximum of num. (Note: The default is 5, and the number cannot be greater than 10). - /// a list of the top suggestions matching the prefix - public string[] GetSuggestions(string prefix, bool fuzzy = false, int max = 5) - { - var args = new List { _boxedIndexName, prefix}; - if (fuzzy) args.Add("FUZZY".Literal()); - if (max != 5) - { - args.Add("MAX".Literal()); - args.Add(max); - } - return (string[])DbSync.Execute("FT.SUGGET", args); - } - /// - /// Get completion suggestions for a prefix - /// - /// the prefix to complete on - /// if set,we do a fuzzy prefix search, including prefixes at levenshtein distance of 1 from the prefix sent - /// If set, we limit the results to a maximum of num. (Note: The default is 5, and the number cannot be greater than 10). - /// a list of the top suggestions matching the prefix - public async Task GetSuggestionsAsync(string prefix, bool fuzzy = false, int max = 5) - { - var args = new List { _boxedIndexName, prefix }; - if (fuzzy) args.Add("FUZZY".Literal()); - if (max != 5) - { - args.Add("MAX".Literal()); - args.Add(max); - } - return (string[])await _db.ExecuteAsync("FT.SUGGET", args).ConfigureAwait(false); - } - } -} diff --git a/NRediSearch/Document.cs b/NRediSearch/Document.cs deleted file mode 100644 index 90a81e846..000000000 --- a/NRediSearch/Document.cs +++ /dev/null @@ -1,51 +0,0 @@ -// .NET port of https://github.com/RedisLabs/JRediSearch/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using StackExchange.Redis; - -namespace NRediSearch -{ - /// - /// Document represents a single indexed document or entity in the engine - /// - public class Document - { - - public string Id { get; } - public double Score { get; } - public byte[] Payload { get; } - private Dictionary properties = new Dictionary(); - - public Document(string id, double score, byte[] payload) - { - Id = id; - Score = score; - Payload = payload; - } - - public static Document Load(string id, double score, byte[] payload, RedisValue[] fields) - { - Document ret = new Document(id, score, payload); - if (fields != null) - { - for (int i = 0; i < fields.Length; i += 2) - { - ret[(string)fields[i]] = fields[i + 1]; - } - } - return ret; - } - - public RedisValue this[string key] - { - get { return properties.TryGetValue(key, out var val) ? val : default(RedisValue); } - internal set { properties[key] = value; } - } - - public bool HasProperty(string key) => properties.ContainsKey(key); - } -} diff --git a/NRediSearch/Literals.cs b/NRediSearch/Literals.cs deleted file mode 100644 index 2310f5e3a..000000000 --- a/NRediSearch/Literals.cs +++ /dev/null @@ -1,36 +0,0 @@ -using StackExchange.Redis; -using System.Collections; -namespace NRediSearch -{ - /// - /// Cache to ensure we encode and box literals once only - /// - internal static class Literals - { - private static Hashtable _boxed = new Hashtable(); - private static object _null = RedisValue.Null; - /// - /// Obtain a lazily-cached pre-encoded and boxed representation of a string - /// - /// This shoul donly be used for fixed values, not user data (the cache is never reclaimed, so it will be a memory leak) - public static object Literal(this string value) - { - if (value == null) return _null; - - object boxed = _boxed[value]; - if (boxed == null) - { - lock (_boxed) - { - boxed = _boxed[value]; - if (boxed == null) - { - boxed = (RedisValue)value; - _boxed.Add(value, boxed); - } - } - } - return boxed; - } - } -} diff --git a/NRediSearch/NRediSearch.csproj b/NRediSearch/NRediSearch.csproj deleted file mode 100644 index 673bb28fd..000000000 --- a/NRediSearch/NRediSearch.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - $(LibraryTargetFrameworks) - 0.1 - false - Redis;Search;Modules;RediSearch - - - - - \ No newline at end of file diff --git a/NRediSearch/Query.cs b/NRediSearch/Query.cs deleted file mode 100644 index 79b4879a3..000000000 --- a/NRediSearch/Query.cs +++ /dev/null @@ -1,294 +0,0 @@ -// .NET port of https://github.com/RedisLabs/JRediSearch/ - -using StackExchange.Redis; -using System; -using System.Collections.Generic; -using System.Globalization; - -namespace NRediSearch -{ - /// - /// Query represents query parameters and filters to load results from the engine - /// - public class Query - { - /// - /// Filter represents a filtering rules in a query - /// - public abstract class Filter - { - - public string Property { get; } - - internal abstract void SerializeRedisArgs(List args); - - internal Filter(string property) - { - Property = property; - } - - } - - /// - /// NumericFilter wraps a range filter on a numeric field. It can be inclusive or exclusive - /// - public class NumericFilter : Filter - { - - private readonly double min, max; - private readonly bool exclusiveMin, exclusiveMax; - - public NumericFilter(string property, double min, bool exclusiveMin, double max, bool exclusiveMax) : base(property) - { - this.min = min; - this.max = max; - this.exclusiveMax = exclusiveMax; - this.exclusiveMin = exclusiveMin; - } - - public NumericFilter(string property, double min, double max) : this(property, min, false, max, false) { } - - - internal override void SerializeRedisArgs(List args) - { - RedisValue FormatNum(double num, bool exclude) - { - if (!exclude || double.IsInfinity(num)) - { - return (RedisValue)num; // can use directly - } - // need to add leading bracket - return "(" + num.ToString("G17", NumberFormatInfo.InvariantInfo); - } - args.Add("FILTER".Literal()); - args.Add(Property); - args.Add(FormatNum(min, exclusiveMin)); - args.Add(FormatNum(max, exclusiveMax)); - } - } - - /// - /// GeoFilter encapsulates a radius filter on a geographical indexed fields - /// - public class GeoFilter : Filter - { - - private readonly double lon, lat, radius; - private GeoUnit unit; - - public GeoFilter(string property, double lon, double lat, double radius, GeoUnit unit) : base(property) - { - this.lon = lon; - this.lat = lat; - this.radius = radius; - this.unit = unit; - } - - internal override void SerializeRedisArgs(List args) - { - args.Add("GEOFILTER".Literal()); - args.Add(Property); - args.Add(lon); - args.Add(lat); - args.Add(radius); - - switch (unit) - { - case GeoUnit.Feet: args.Add("ft".Literal()); break; - case GeoUnit.Kilometers: args.Add("km".Literal()); break; - case GeoUnit.Meters: args.Add("m".Literal()); break; - case GeoUnit.Miles: args.Add("mi".Literal()); break; - default: throw new InvalidOperationException($"Unknown unit: {unit}"); - } - } - } - - private struct Paging - { - public int Offset { get; } - public int Count { get; } - - public Paging(int offset, int count) - { - Offset = offset; - Count = count; - } - } - - /// - /// The query's filter list. We only support AND operation on all those filters - /// - List _filters = new List(); - - /// - /// The textual part of the query - /// - public string QueryString { get; } - - /// - /// The sorting parameters - /// - Paging _paging = new Paging(0, 10); - - /// - /// Set the query to verbatim mode, disabling stemming and query expansion - /// - public bool Verbatim { get; set; } - /// - /// Set the query not to return the contents of documents, and rather just return the ids - /// - public bool NoContent { get; set; } - /// - /// Set the query not to filter for stopwords. In general this should not be used - /// - public bool NoStopwords { get; set; } - /// - /// Set the query to return a factored score for each results. This is useful to merge results from multiple queries. - /// - public bool WithScores { get; set; } - /// - /// Set the query to return object payloads, if any were given - /// - public bool WithPayloads { get; set; } - - /// - /// Set the query language, for stemming purposes; see http://redisearch.io for documentation on languages and stemming - /// - public string Language { get; set; } - protected String[] _fields = null; - /// - /// Set the query payload to be evaluated by the scoring function - /// - public byte[] Payload { get; set; } - - /// - /// Set the query parameter to sort by - /// - public string SortBy { get; set; } - - /// - /// Set the query parameter to sort by ASC by default - /// - public bool SortAscending {get; set;} = true; - - /// - /// Create a new index - /// - public Query(String queryString) - { - QueryString = queryString; - } - - internal void SerializeRedisArgs(List args) - { - args.Add(QueryString); - - if (Verbatim) - { - args.Add("VERBATIM".Literal()); - } - if (NoContent) - { - args.Add("NOCONTENT".Literal()); - } - if (NoStopwords) - { - args.Add("NOSTOPWORDS".Literal()); - } - if (WithScores) - { - args.Add("WITHSCORES".Literal()); - } - if (WithPayloads) - { - args.Add("WITHPAYLOADS".Literal()); - } - if (Language != null) - { - args.Add("LANGUAGE".Literal()); - args.Add(Language); - } - if (_fields != null && _fields.Length > 0) - { - args.Add("INFIELDS".Literal()); - args.Add(_fields.Length); - args.AddRange(_fields); - } - - if(SortBy != null) - { - args.Add("SORTBY".Literal()); - args.Add(SortBy); - args.Add((SortAscending ? "ASC" : "DESC").Literal()); - } - - if (Payload != null) - { - args.Add("PAYLOAD".Literal()); - args.Add(Payload); - } - - if (_paging.Offset != 0 || _paging.Count != 10) - { - args.Add("LIMIT".Literal()); - args.Add(_paging.Offset); - args.Add(_paging.Count); - } - - if (_filters != null && _filters.Count > 0) - { - foreach (var f in _filters) - { - f.SerializeRedisArgs(args); - } - } - } - - /// - /// Limit the results to a certain offset and limit - /// - /// the first result to show, zero based indexing - /// how many results we want to show - /// the query itself, for builder-style syntax - public Query Limit(int offset, int count) - { - _paging = new Paging(offset, count); - return this; - } - - /// - /// Add a filter to the query's filter list - /// - /// either a numeric or geo filter object - /// the query itself - public Query AddFilter(Filter f) - { - _filters.Add(f); - return this; - } - - /// - /// Limit the query to results that are limited to a specific set of fields - /// - /// a list of TEXT fields in the schemas - /// the query object itself - public Query LimitFields(params string[] fields) - { - this._fields = fields; - return this; - } - - /// - /// Set the query to be sorted by a sortable field defined in the schema - /// - /// the sorting field's name - /// if set to true, the sorting order is ascending, else descending - /// the query object itself - public Query SetSortBy(string field, bool ascending = true) - { - SortBy = field; - SortAscending = ascending; - return this; - } - } -} diff --git a/NRediSearch/Schema.cs b/NRediSearch/Schema.cs deleted file mode 100644 index bb729fb93..000000000 --- a/NRediSearch/Schema.cs +++ /dev/null @@ -1,133 +0,0 @@ -// .NET port of https://github.com/RedisLabs/JRediSearch/ - -using System; -using System.Collections.Generic; - -namespace NRediSearch -{ - /// - /// Schema abstracts the schema definition when creating an index. - /// Documents can contain fields not mentioned in the schema, but the index will only index pre-defined fields - /// - public sealed class Schema - { - public enum FieldType - { - FullText, - Geo, - Numeric - } - - public class Field - { - public String Name { get; } - public FieldType Type { get; } - public bool Sortable {get;} - - internal Field(string name, FieldType type, bool sortable) - { - Name = name; - Type = type; - Sortable = sortable; - } - - internal virtual void SerializeRedisArgs(List args) - { - object GetForRedis(FieldType type) - { - switch (type) - { - case FieldType.FullText: return "TEXT".Literal(); - case FieldType.Geo: return "GEO".Literal(); - case FieldType.Numeric: return "NUMERIC".Literal(); - default: throw new ArgumentOutOfRangeException(nameof(type)); - } - } - args.Add(Name); - args.Add(GetForRedis(Type)); - if(Sortable){args.Add("SORTABLE");} - } - } - public class TextField : Field - { - public double Weight { get; } - internal TextField(string name, double weight = 1.0) : base(name, FieldType.FullText, false) - { - Weight = weight; - } - internal TextField(string name, bool sortable, double weight = 1.0) : base(name, FieldType.FullText, sortable) - { - Weight = weight; - } - internal override void SerializeRedisArgs(List args) - { - base.SerializeRedisArgs(args); - if (Weight != 1.0) - { - args.Add("WEIGHT".Literal()); - args.Add(Weight); - } - } - } - - public List Fields { get; } = new List(); - - /// - /// Add a text field to the schema with a given weight - /// - /// the field's name - /// its weight, a positive floating point number - /// the schema object - public Schema AddTextField(string name, double weight = 1.0) - { - Fields.Add(new TextField(name, weight)); - return this; - } - - /// - /// Add a text field that can be sorted on - /// - /// the field's name - /// its weight, a positive floating point number - /// the schema object - public Schema AddSortableTextField(string name, double weight = 1.0) - { - Fields.Add(new TextField(name, true, weight)); - return this; - } - - /// - /// Add a numeric field to the schema - /// - /// the field's name - /// the schema object - public Schema AddGeoField(string name) - { - Fields.Add(new Field(name, FieldType.Geo, false)); - return this; - } - - /// - /// Add a numeric field to the schema - /// - /// the field's name - /// the schema object - public Schema AddNumericField(string name) - { - Fields.Add(new Field(name, FieldType.Numeric, false)); - return this; - } - - /// - /// Add a numeric field that can be sorted on - /// - /// the field's name - /// the schema object - public Schema AddSortableNumericField(string name) - { - Fields.Add(new Field(name, FieldType.Numeric, true)); - return this; - } - - } -} diff --git a/NRediSearch/SearchResult.cs b/NRediSearch/SearchResult.cs deleted file mode 100644 index d974ad28f..000000000 --- a/NRediSearch/SearchResult.cs +++ /dev/null @@ -1,75 +0,0 @@ -// .NET port of https://github.com/RedisLabs/JRediSearch/ - -using StackExchange.Redis; -using System.Collections.Generic; - -namespace NRediSearch -{ - /// - /// SearchResult encapsulates the returned result from a search query. - /// It contains publically accessible fields for the total number of results, and an array of - /// objects conatining the actual returned documents. - /// - public class SearchResult - { - public long TotalResults { get; } - public List Documents { get; } - - - internal SearchResult(RedisResult[] resp, bool hasContent, bool hasScores, bool hasPayloads) - { - // Calculate the step distance to walk over the results. - // The order of results is id, score (if withScore), payLoad (if hasPayloads), fields - int step = 1; - int scoreOffset = 0; - int contentOffset = 1; - int payloadOffset = 0; - if (hasScores) - { - step += 1; - scoreOffset = 1; - contentOffset += 1; - } - if (hasContent) - { - step += 1; - if (hasPayloads) - { - payloadOffset = scoreOffset + 1; - step += 1; - contentOffset += 1; - } - } - - // the first element is always the number of results - TotalResults = (long)resp[0]; - var docs = new List((resp.Length - 1) / step); - Documents = docs; - for (int i = 1; i < resp.Length; i += step) - { - - var id = (string)resp[i]; - double score = 1.0; - byte[] payload = null; - RedisValue[] fields = null; - if (hasScores) - { - score = (double)resp[i + scoreOffset]; - } - if (hasPayloads) - { - payload = (byte[])resp[i + payloadOffset]; - } - - if (hasContent) - { - fields = (RedisValue[])resp[i + contentOffset]; - } - - docs.Add(Document.Load(id, score, payload, fields)); - } - - - } - } -} diff --git a/NuGet.Config b/NuGet.Config index d80bb2963..607e50560 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -2,6 +2,8 @@ + + diff --git a/README.md b/README.md index 3bed651a9..076a128d7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,20 @@ StackExchange.Redis =================== -For all documentation, [see here](http://stackexchange.github.io/StackExchange.Redis/) \ No newline at end of file +StackExchange.Redis is a .NET client for communicating with RESP servers such as [Redis](https://redis.io/), [Garnet](https://microsoft.github.io/garnet/), [Valkey](https://valkey.io/), [Azure Cache for Redis](https://azure.microsoft.com/products/cache), [AWS ElastiCache](https://aws.amazon.com/elasticache/), and a wide range of other Redis-like servers. We do not maintain a list of compatible servers, but if the server has a Redis-like API: it will *probably* work fine. If not: log an issue with details! + +For all documentation, [see here](https://stackexchange.github.io/StackExchange.Redis/). + +#### Build Status + +[![CI](https://github.com/StackExchange/StackExchange.Redis/actions/workflows/CI.yml/badge.svg)](https://github.com/StackExchange/StackExchange.Redis/actions/workflows/CI.yml) + +#### Package Status + +MyGet Pre-release feed: https://www.myget.org/gallery/stackoverflow + +| Package | NuGet Stable | NuGet Pre-release | Downloads | MyGet | +| ------- | ------------ | ----------------- | --------- | ----- | +| [StackExchange.Redis](https://www.nuget.org/packages/StackExchange.Redis/) | [![StackExchange.Redis](https://img.shields.io/nuget/v/StackExchange.Redis.svg)](https://www.nuget.org/packages/StackExchange.Redis/) | [![StackExchange.Redis](https://img.shields.io/nuget/vpre/StackExchange.Redis.svg)](https://www.nuget.org/packages/StackExchange.Redis/absoluteLatest) | [![StackExchange.Redis](https://img.shields.io/nuget/dt/StackExchange.Redis.svg)](https://www.nuget.org/packages/StackExchange.Redis/) | [![StackExchange.Redis MyGet](https://img.shields.io/myget/stackoverflow/vpre/StackExchange.Redis.svg)](https://www.myget.org/feed/stackoverflow/package/nuget/StackExchange.Redis) | + +Release notes at: https://stackexchange.github.io/StackExchange.Redis/ReleaseNotes diff --git a/Redis Configs/Cluster/7000/redis.conf b/Redis Configs/Cluster/7000/redis.conf deleted file mode 100644 index 5e62bb5e9..000000000 --- a/Redis Configs/Cluster/7000/redis.conf +++ /dev/null @@ -1,4 +0,0 @@ -cluster-enabled yes -cluster-config-file nodes.conf -cluster-node-timeout 5000 -appendonly yes \ No newline at end of file diff --git a/Redis Configs/Cluster/7001/redis.conf b/Redis Configs/Cluster/7001/redis.conf deleted file mode 100644 index 5e62bb5e9..000000000 --- a/Redis Configs/Cluster/7001/redis.conf +++ /dev/null @@ -1,4 +0,0 @@ -cluster-enabled yes -cluster-config-file nodes.conf -cluster-node-timeout 5000 -appendonly yes \ No newline at end of file diff --git a/Redis Configs/Cluster/7002/redis.conf b/Redis Configs/Cluster/7002/redis.conf deleted file mode 100644 index 5e62bb5e9..000000000 --- a/Redis Configs/Cluster/7002/redis.conf +++ /dev/null @@ -1,4 +0,0 @@ -cluster-enabled yes -cluster-config-file nodes.conf -cluster-node-timeout 5000 -appendonly yes \ No newline at end of file diff --git a/Redis Configs/Cluster/7003/redis.conf b/Redis Configs/Cluster/7003/redis.conf deleted file mode 100644 index 5e62bb5e9..000000000 --- a/Redis Configs/Cluster/7003/redis.conf +++ /dev/null @@ -1,4 +0,0 @@ -cluster-enabled yes -cluster-config-file nodes.conf -cluster-node-timeout 5000 -appendonly yes \ No newline at end of file diff --git a/Redis Configs/Cluster/7004/redis.conf b/Redis Configs/Cluster/7004/redis.conf deleted file mode 100644 index 5e62bb5e9..000000000 --- a/Redis Configs/Cluster/7004/redis.conf +++ /dev/null @@ -1,4 +0,0 @@ -cluster-enabled yes -cluster-config-file nodes.conf -cluster-node-timeout 5000 -appendonly yes \ No newline at end of file diff --git a/Redis Configs/Cluster/7005/redis.conf b/Redis Configs/Cluster/7005/redis.conf deleted file mode 100644 index 5e62bb5e9..000000000 --- a/Redis Configs/Cluster/7005/redis.conf +++ /dev/null @@ -1,4 +0,0 @@ -cluster-enabled yes -cluster-config-file nodes.conf -cluster-node-timeout 5000 -appendonly yes \ No newline at end of file diff --git a/Redis Configs/master.conf b/Redis Configs/master.conf deleted file mode 100644 index 6e7bd4339..000000000 --- a/Redis Configs/master.conf +++ /dev/null @@ -1,5 +0,0 @@ -port 6379 -dbfilename master.rdb -databases 2000 -maxmemory 6gb -save "" \ No newline at end of file diff --git a/Redis Configs/redis-cli 7000.cmd b/Redis Configs/redis-cli 7000.cmd deleted file mode 100644 index dbbaab946..000000000 --- a/Redis Configs/redis-cli 7000.cmd +++ /dev/null @@ -1 +0,0 @@ -@..\packages\Redis-64.3.0.503\redis-cli.exe -h 127.0.0.1 -p 7000 diff --git a/Redis Configs/redis-cli 7001.cmd b/Redis Configs/redis-cli 7001.cmd deleted file mode 100644 index 22488be5d..000000000 --- a/Redis Configs/redis-cli 7001.cmd +++ /dev/null @@ -1 +0,0 @@ -@..\packages\Redis-64.3.0.503\redis-cli.exe -h 127.0.0.1 -p 7001 diff --git a/Redis Configs/redis-cli 7002.cmd b/Redis Configs/redis-cli 7002.cmd deleted file mode 100644 index cd7c9d056..000000000 --- a/Redis Configs/redis-cli 7002.cmd +++ /dev/null @@ -1 +0,0 @@ -@..\packages\Redis-64.3.0.503\redis-cli.exe -h 127.0.0.1 -p 7002 diff --git a/Redis Configs/redis-cli 7003.cmd b/Redis Configs/redis-cli 7003.cmd deleted file mode 100644 index 16c0f6c49..000000000 --- a/Redis Configs/redis-cli 7003.cmd +++ /dev/null @@ -1 +0,0 @@ -@..\packages\Redis-64.3.0.503\redis-cli.exe -h 127.0.0.1 -p 7003 diff --git a/Redis Configs/redis-cli 7004.cmd b/Redis Configs/redis-cli 7004.cmd deleted file mode 100644 index 574a98dae..000000000 --- a/Redis Configs/redis-cli 7004.cmd +++ /dev/null @@ -1 +0,0 @@ -@..\packages\Redis-64.3.0.503\redis-cli.exe -h 127.0.0.1 -p 7004 diff --git a/Redis Configs/redis-cli 7005.cmd b/Redis Configs/redis-cli 7005.cmd deleted file mode 100644 index 5a702e02b..000000000 --- a/Redis Configs/redis-cli 7005.cmd +++ /dev/null @@ -1 +0,0 @@ -@..\packages\Redis-64.3.0.503\redis-cli.exe -h 127.0.0.1 -p 7005 diff --git a/Redis Configs/redis-cli master.cmd b/Redis Configs/redis-cli master.cmd deleted file mode 100644 index 1bc726667..000000000 --- a/Redis Configs/redis-cli master.cmd +++ /dev/null @@ -1 +0,0 @@ -@..\packages\Redis-64.3.0.503\redis-cli.exe -p 6379 diff --git a/Redis Configs/redis-cli secure.cmd b/Redis Configs/redis-cli secure.cmd deleted file mode 100644 index 84546d7b0..000000000 --- a/Redis Configs/redis-cli secure.cmd +++ /dev/null @@ -1 +0,0 @@ -@..\packages\Redis-64.3.0.503\redis-cli.exe -p 6381 diff --git a/Redis Configs/redis-cli slave.cmd b/Redis Configs/redis-cli slave.cmd deleted file mode 100644 index 91e215bc9..000000000 --- a/Redis Configs/redis-cli slave.cmd +++ /dev/null @@ -1 +0,0 @@ -@..\packages\Redis-64.3.0.503\redis-cli.exe -p 6380 diff --git a/Redis Configs/redis-server all local.cmd b/Redis Configs/redis-server all local.cmd deleted file mode 100644 index 6644b6a92..000000000 --- a/Redis Configs/redis-server all local.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@start ..\packages\Redis-64.3.0.503\redis-server.exe master.conf -@start ..\packages\Redis-64.3.0.503\redis-server.exe slave.conf -@start ..\packages\Redis-64.3.0.503\redis-server.exe secure.conf diff --git a/Redis Configs/redis-server cluster.cmd b/Redis Configs/redis-server cluster.cmd deleted file mode 100644 index 8faec24a5..000000000 --- a/Redis Configs/redis-server cluster.cmd +++ /dev/null @@ -1,19 +0,0 @@ -@echo off -pushd Cluster\7000 -@start /min ..\..\..\packages\Redis-64.3.0.503\redis-server.exe redis.conf --port 7000 -popd -pushd Cluster\7001 -@start /min ..\..\..\packages\Redis-64.3.0.503\redis-server.exe redis.conf --port 7001 -popd -pushd Cluster\7002 -@start /min ..\..\..\packages\Redis-64.3.0.503\redis-server.exe redis.conf --port 7002 -popd -pushd Cluster\7003 -@start /min ..\..\..\packages\Redis-64.3.0.503\redis-server.exe redis.conf --port 7003 -popd -pushd Cluster\7004 -@start /min ..\..\..\packages\Redis-64.3.0.503\redis-server.exe redis.conf --port 7004 -popd -pushd Cluster\7005 -@start /min ..\..\..\packages\Redis-64.3.0.503\redis-server.exe redis.conf --port 7005 -popd \ No newline at end of file diff --git a/Redis Configs/redis-server master.cmd b/Redis Configs/redis-server master.cmd deleted file mode 100644 index 9432c3e50..000000000 --- a/Redis Configs/redis-server master.cmd +++ /dev/null @@ -1 +0,0 @@ -@..\packages\Redis-64.3.0.503\redis-server.exe master.conf diff --git a/Redis Configs/redis-server secure.cmd b/Redis Configs/redis-server secure.cmd deleted file mode 100644 index 2fcb360d7..000000000 --- a/Redis Configs/redis-server secure.cmd +++ /dev/null @@ -1 +0,0 @@ -@..\packages\Redis-64.3.0.503\redis-server.exe secure.conf diff --git a/Redis Configs/redis-server slave.cmd b/Redis Configs/redis-server slave.cmd deleted file mode 100644 index 9f75faa24..000000000 --- a/Redis Configs/redis-server slave.cmd +++ /dev/null @@ -1 +0,0 @@ -@..\packages\Redis-64.3.0.503\redis-server.exe slave.conf diff --git a/Redis Configs/redis-trib.rb b/Redis Configs/redis-trib.rb deleted file mode 100644 index 1c3d7ed83..000000000 --- a/Redis Configs/redis-trib.rb +++ /dev/null @@ -1,1699 +0,0 @@ -#!/usr/bin/env ruby - -# redis-trib from the main redis repo at: -# https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb - -# TODO (temporary here, we'll move this into the Github issues once -# redis-trib initial implementation is completed). -# -# - Make sure that if the rehashing fails in the middle redis-trib will try -# to recover. -# - When redis-trib performs a cluster check, if it detects a slot move in -# progress it should prompt the user to continue the move from where it -# stopped. -# - Gracefully handle Ctrl+C in move_slot to prompt the user if really stop -# while rehashing, and performing the best cleanup possible if the user -# forces the quit. -# - When doing "fix" set a global Fix to true, and prompt the user to -# fix the problem if automatically fixable every time there is something -# to fix. For instance: -# 1) If there is a node that pretend to receive a slot, or to migrate a -# slot, but has no entries in that slot, fix it. -# 2) If there is a node having keys in slots that are not owned by it -# fix this condition moving the entries in the same node. -# 3) Perform more possibly slow tests about the state of the cluster. -# 4) When aborted slot migration is detected, fix it. - -require 'rubygems' -require 'redis' - -ClusterHashSlots = 16384 -MigrateDefaultTimeout = 60000 -MigrateDefaultPipeline = 10 -RebalanceDefaultThreshold = 2 - -$verbose = false - -def xputs(s) - case s[0..2] - when ">>>" - color="29;1" - when "[ER" - color="31;1" - when "[WA" - color="31;1" - when "[OK" - color="32" - when "[FA","***" - color="33" - else - color=nil - end - - color = nil if ENV['TERM'] != "xterm" - print "\033[#{color}m" if color - print s - print "\033[0m" if color - print "\n" -end - -class ClusterNode - def initialize(addr) - s = addr.split("@")[0].split(":") - if s.length < 2 - puts "Invalid IP or Port (given as #{addr}) - use IP:Port format" - exit 1 - end - port = s.pop # removes port from split array - ip = s.join(":") # if s.length > 1 here, it's IPv6, so restore address - @r = nil - @info = {} - @info[:host] = ip - @info[:port] = port - @info[:slots] = {} - @info[:migrating] = {} - @info[:importing] = {} - @info[:replicate] = false - @dirty = false # True if we need to flush slots info into node. - @friends = [] - end - - def friends - @friends - end - - def slots - @info[:slots] - end - - def has_flag?(flag) - @info[:flags].index(flag) - end - - def to_s - "#{@info[:host]}:#{@info[:port]}" - end - - def connect(o={}) - return if @r - print "Connecting to node #{self}: " if $verbose - STDOUT.flush - begin - @r = Redis.new(:host => @info[:host], :port => @info[:port], :timeout => 60) - @r.ping - rescue - xputs "[ERR] Sorry, can't connect to node #{self}" - exit 1 if o[:abort] - @r = nil - end - xputs "OK" if $verbose - end - - def assert_cluster - info = @r.info - if !info["cluster_enabled"] || info["cluster_enabled"].to_i == 0 - xputs "[ERR] Node #{self} is not configured as a cluster node." - exit 1 - end - end - - def assert_empty - if !(@r.cluster("info").split("\r\n").index("cluster_known_nodes:1")) || - (@r.info['db0']) - xputs "[ERR] Node #{self} is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0." - exit 1 - end - end - - def load_info(o={}) - self.connect - nodes = @r.cluster("nodes").split("\n") - nodes.each{|n| - # name addr flags role ping_sent ping_recv link_status slots - split = n.split - name,addr,flags,master_id,ping_sent,ping_recv,config_epoch,link_status = split[0..6] - slots = split[8..-1] - info = { - :name => name, - :addr => addr, - :flags => flags.split(","), - :replicate => master_id, - :ping_sent => ping_sent.to_i, - :ping_recv => ping_recv.to_i, - :link_status => link_status - } - info[:replicate] = false if master_id == "-" - - if info[:flags].index("myself") - @info = @info.merge(info) - @info[:slots] = {} - slots.each{|s| - if s[0..0] == '[' - if s.index("->-") # Migrating - slot,dst = s[1..-1].split("->-") - @info[:migrating][slot.to_i] = dst - elsif s.index("-<-") # Importing - slot,src = s[1..-1].split("-<-") - @info[:importing][slot.to_i] = src - end - elsif s.index("-") - start,stop = s.split("-") - self.add_slots((start.to_i)..(stop.to_i)) - else - self.add_slots((s.to_i)..(s.to_i)) - end - } if slots - @dirty = false - @r.cluster("info").split("\n").each{|e| - k,v=e.split(":") - k = k.to_sym - v.chop! - if k != :cluster_state - @info[k] = v.to_i - else - @info[k] = v - end - } - elsif o[:getfriends] - @friends << info - end - } - end - - def add_slots(slots) - slots.each{|s| - @info[:slots][s] = :new - } - @dirty = true - end - - def set_as_replica(node_id) - @info[:replicate] = node_id - @dirty = true - end - - def flush_node_config - return if !@dirty - if @info[:replicate] - begin - @r.cluster("replicate",@info[:replicate]) - rescue - # If the cluster did not already joined it is possible that - # the slave does not know the master node yet. So on errors - # we return ASAP leaving the dirty flag set, to flush the - # config later. - return - end - else - new = [] - @info[:slots].each{|s,val| - if val == :new - new << s - @info[:slots][s] = true - end - } - @r.cluster("addslots",*new) - end - @dirty = false - end - - def info_string - # We want to display the hash slots assigned to this node - # as ranges, like in: "1-5,8-9,20-25,30" - # - # Note: this could be easily written without side effects, - # we use 'slots' just to split the computation into steps. - - # First step: we want an increasing array of integers - # for instance: [1,2,3,4,5,8,9,20,21,22,23,24,25,30] - slots = @info[:slots].keys.sort - - # As we want to aggregate adjacent slots we convert all the - # slot integers into ranges (with just one element) - # So we have something like [1..1,2..2, ... and so forth. - slots.map!{|x| x..x} - - # Finally we group ranges with adjacent elements. - slots = slots.reduce([]) {|a,b| - if !a.empty? && b.first == (a[-1].last)+1 - a[0..-2] + [(a[-1].first)..(b.last)] - else - a + [b] - end - } - - # Now our task is easy, we just convert ranges with just one - # element into a number, and a real range into a start-end format. - # Finally we join the array using the comma as separator. - slots = slots.map{|x| - x.count == 1 ? x.first.to_s : "#{x.first}-#{x.last}" - }.join(",") - - role = self.has_flag?("master") ? "M" : "S" - - if self.info[:replicate] and @dirty - is = "S: #{self.info[:name]} #{self.to_s}" - else - is = "#{role}: #{self.info[:name]} #{self.to_s}\n"+ - " slots:#{slots} (#{self.slots.length} slots) "+ - "#{(self.info[:flags]-["myself"]).join(",")}" - end - if self.info[:replicate] - is += "\n replicates #{info[:replicate]}" - elsif self.has_flag?("master") && self.info[:replicas] - is += "\n #{info[:replicas].length} additional replica(s)" - end - is - end - - # Return a single string representing nodes and associated slots. - # TODO: remove slaves from config when slaves will be handled - # by Redis Cluster. - def get_config_signature - config = [] - @r.cluster("nodes").each_line{|l| - s = l.split - slots = s[8..-1].select {|x| x[0..0] != "["} - next if slots.length == 0 - config << s[0]+":"+(slots.sort.join(",")) - } - config.sort.join("|") - end - - def info - @info - end - - def is_dirty? - @dirty - end - - def r - @r - end -end - -class RedisTrib - def initialize - @nodes = [] - @fix = false - @errors = [] - @timeout = MigrateDefaultTimeout - end - - def check_arity(req_args, num_args) - if ((req_args > 0 and num_args != req_args) || - (req_args < 0 and num_args < req_args.abs)) - xputs "[ERR] Wrong number of arguments for specified sub command" - exit 1 - end - end - - def add_node(node) - @nodes << node - end - - def reset_nodes - @nodes = [] - end - - def cluster_error(msg) - @errors << msg - xputs msg - end - - # Return the node with the specified ID or Nil. - def get_node_by_name(name) - @nodes.each{|n| - return n if n.info[:name] == name.downcase - } - return nil - end - - # Like get_node_by_name but the specified name can be just the first - # part of the node ID as long as the prefix in unique across the - # cluster. - def get_node_by_abbreviated_name(name) - l = name.length - candidates = [] - @nodes.each{|n| - if n.info[:name][0...l] == name.downcase - candidates << n - end - } - return nil if candidates.length != 1 - candidates[0] - end - - # This function returns the master that has the least number of replicas - # in the cluster. If there are multiple masters with the same smaller - # number of replicas, one at random is returned. - def get_master_with_least_replicas - masters = @nodes.select{|n| n.has_flag? "master"} - sorted = masters.sort{|a,b| - a.info[:replicas].length <=> b.info[:replicas].length - } - sorted[0] - end - - def check_cluster(opt={}) - xputs ">>> Performing Cluster Check (using node #{@nodes[0]})" - show_nodes if !opt[:quiet] - check_config_consistency - check_open_slots - check_slots_coverage - end - - def show_cluster_info - masters = 0 - keys = 0 - @nodes.each{|n| - if n.has_flag?("master") - puts "#{n} (#{n.info[:name][0...8]}...) -> #{n.r.dbsize} keys | #{n.slots.length} slots | "+ - "#{n.info[:replicas].length} slaves." - masters += 1 - keys += n.r.dbsize - end - } - xputs "[OK] #{keys} keys in #{masters} masters." - keys_per_slot = sprintf("%.2f",keys/16384.0) - puts "#{keys_per_slot} keys per slot on average." - end - - # Merge slots of every known node. If the resulting slots are equal - # to ClusterHashSlots, then all slots are served. - def covered_slots - slots = {} - @nodes.each{|n| - slots = slots.merge(n.slots) - } - slots - end - - def check_slots_coverage - xputs ">>> Check slots coverage..." - slots = covered_slots - if slots.length == ClusterHashSlots - xputs "[OK] All #{ClusterHashSlots} slots covered." - else - cluster_error \ - "[ERR] Not all #{ClusterHashSlots} slots are covered by nodes." - fix_slots_coverage if @fix - end - end - - def check_open_slots - xputs ">>> Check for open slots..." - open_slots = [] - @nodes.each{|n| - if n.info[:migrating].size > 0 - cluster_error \ - "[WARNING] Node #{n} has slots in migrating state (#{n.info[:migrating].keys.join(",")})." - open_slots += n.info[:migrating].keys - end - if n.info[:importing].size > 0 - cluster_error \ - "[WARNING] Node #{n} has slots in importing state (#{n.info[:importing].keys.join(",")})." - open_slots += n.info[:importing].keys - end - } - open_slots.uniq! - if open_slots.length > 0 - xputs "[WARNING] The following slots are open: #{open_slots.join(",")}" - end - if @fix - open_slots.each{|slot| fix_open_slot slot} - end - end - - def nodes_with_keys_in_slot(slot) - nodes = [] - @nodes.each{|n| - next if n.has_flag?("slave") - nodes << n if n.r.cluster("getkeysinslot",slot,1).length > 0 - } - nodes - end - - def fix_slots_coverage - not_covered = (0...ClusterHashSlots).to_a - covered_slots.keys - xputs ">>> Fixing slots coverage..." - xputs "List of not covered slots: " + not_covered.join(",") - - # For every slot, take action depending on the actual condition: - # 1) No node has keys for this slot. - # 2) A single node has keys for this slot. - # 3) Multiple nodes have keys for this slot. - slots = {} - not_covered.each{|slot| - nodes = nodes_with_keys_in_slot(slot) - slots[slot] = nodes - xputs "Slot #{slot} has keys in #{nodes.length} nodes: #{nodes.join(", ")}" - } - - none = slots.select {|k,v| v.length == 0} - single = slots.select {|k,v| v.length == 1} - multi = slots.select {|k,v| v.length > 1} - - # Handle case "1": keys in no node. - if none.length > 0 - xputs "The folowing uncovered slots have no keys across the cluster:" - xputs none.keys.join(",") - yes_or_die "Fix these slots by covering with a random node?" - none.each{|slot,nodes| - node = @nodes.sample - xputs ">>> Covering slot #{slot} with #{node}" - node.r.cluster("addslots",slot) - } - end - - # Handle case "2": keys only in one node. - if single.length > 0 - xputs "The folowing uncovered slots have keys in just one node:" - puts single.keys.join(",") - yes_or_die "Fix these slots by covering with those nodes?" - single.each{|slot,nodes| - xputs ">>> Covering slot #{slot} with #{nodes[0]}" - nodes[0].r.cluster("addslots",slot) - } - end - - # Handle case "3": keys in multiple nodes. - if multi.length > 0 - xputs "The folowing uncovered slots have keys in multiple nodes:" - xputs multi.keys.join(",") - yes_or_die "Fix these slots by moving keys into a single node?" - multi.each{|slot,nodes| - target = get_node_with_most_keys_in_slot(nodes,slot) - xputs ">>> Covering slot #{slot} moving keys to #{target}" - - target.r.cluster('addslots',slot) - target.r.cluster('setslot',slot,'stable') - nodes.each{|src| - next if src == target - # Set the source node in 'importing' state (even if we will - # actually migrate keys away) in order to avoid receiving - # redirections for MIGRATE. - src.r.cluster('setslot',slot,'importing',target.info[:name]) - move_slot(src,target,slot,:dots=>true,:fix=>true,:cold=>true) - src.r.cluster('setslot',slot,'stable') - } - } - end - end - - # Return the owner of the specified slot - def get_slot_owners(slot) - owners = [] - @nodes.each{|n| - next if n.has_flag?("slave") - n.slots.each{|s,_| - owners << n if s == slot - } - } - owners - end - - # Return the node, among 'nodes' with the greatest number of keys - # in the specified slot. - def get_node_with_most_keys_in_slot(nodes,slot) - best = nil - best_numkeys = 0 - @nodes.each{|n| - next if n.has_flag?("slave") - numkeys = n.r.cluster("countkeysinslot",slot) - if numkeys > best_numkeys || best == nil - best = n - best_numkeys = numkeys - end - } - return best - end - - # Slot 'slot' was found to be in importing or migrating state in one or - # more nodes. This function fixes this condition by migrating keys where - # it seems more sensible. - def fix_open_slot(slot) - puts ">>> Fixing open slot #{slot}" - - # Try to obtain the current slot owner, according to the current - # nodes configuration. - owners = get_slot_owners(slot) - owner = owners[0] if owners.length == 1 - - migrating = [] - importing = [] - @nodes.each{|n| - next if n.has_flag? "slave" - if n.info[:migrating][slot] - migrating << n - elsif n.info[:importing][slot] - importing << n - elsif n.r.cluster("countkeysinslot",slot) > 0 && n != owner - xputs "*** Found keys about slot #{slot} in node #{n}!" - importing << n - end - } - puts "Set as migrating in: #{migrating.join(",")}" - puts "Set as importing in: #{importing.join(",")}" - - # If there is no slot owner, set as owner the slot with the biggest - # number of keys, among the set of migrating / importing nodes. - if !owner - xputs ">>> Nobody claims ownership, selecting an owner..." - owner = get_node_with_most_keys_in_slot(@nodes,slot) - - # If we still don't have an owner, we can't fix it. - if !owner - xputs "[ERR] Can't select a slot owner. Impossible to fix." - exit 1 - end - - # Use ADDSLOTS to assign the slot. - puts "*** Configuring #{owner} as the slot owner" - owner.r.cluster("setslot",slot,"stable") - owner.r.cluster("addslots",slot) - # Make sure this information will propagate. Not strictly needed - # since there is no past owner, so all the other nodes will accept - # whatever epoch this node will claim the slot with. - owner.r.cluster("bumpepoch") - - # Remove the owner from the list of migrating/importing - # nodes. - migrating.delete(owner) - importing.delete(owner) - end - - # If there are multiple owners of the slot, we need to fix it - # so that a single node is the owner and all the other nodes - # are in importing state. Later the fix can be handled by one - # of the base cases above. - # - # Note that this case also covers multiple nodes having the slot - # in migrating state, since migrating is a valid state only for - # slot owners. - if owners.length > 1 - owner = get_node_with_most_keys_in_slot(owners,slot) - owners.each{|n| - next if n == owner - n.r.cluster('delslots',slot) - n.r.cluster('setslot',slot,'importing',owner.info[:name]) - importing.delete(n) # Avoid duplciates - importing << n - } - owner.r.cluster('bumpepoch') - end - - # Case 1: The slot is in migrating state in one slot, and in - # importing state in 1 slot. That's trivial to address. - if migrating.length == 1 && importing.length == 1 - move_slot(migrating[0],importing[0],slot,:dots=>true,:fix=>true) - # Case 2: There are multiple nodes that claim the slot as importing, - # they probably got keys about the slot after a restart so opened - # the slot. In this case we just move all the keys to the owner - # according to the configuration. - elsif migrating.length == 0 && importing.length > 0 - xputs ">>> Moving all the #{slot} slot keys to its owner #{owner}" - importing.each {|node| - next if node == owner - move_slot(node,owner,slot,:dots=>true,:fix=>true,:cold=>true) - xputs ">>> Setting #{slot} as STABLE in #{node}" - node.r.cluster("setslot",slot,"stable") - } - # Case 3: There are no slots claiming to be in importing state, but - # there is a migrating node that actually don't have any key. We - # can just close the slot, probably a reshard interrupted in the middle. - elsif importing.length == 0 && migrating.length == 1 && - migrating[0].r.cluster("getkeysinslot",slot,10).length == 0 - migrating[0].r.cluster("setslot",slot,"stable") - else - xputs "[ERR] Sorry, Redis-trib can't fix this slot yet (work in progress). Slot is set as migrating in #{migrating.join(",")}, as importing in #{importing.join(",")}, owner is #{owner}" - end - end - - # Check if all the nodes agree about the cluster configuration - def check_config_consistency - if !is_config_consistent? - cluster_error "[ERR] Nodes don't agree about configuration!" - else - xputs "[OK] All nodes agree about slots configuration." - end - end - - def is_config_consistent? - signatures=[] - @nodes.each{|n| - signatures << n.get_config_signature - } - return signatures.uniq.length == 1 - end - - def wait_cluster_join - print "Waiting for the cluster to join" - while !is_config_consistent? - print "." - STDOUT.flush - sleep 1 - end - print "\n" - end - - def alloc_slots - nodes_count = @nodes.length - masters_count = @nodes.length / (@replicas+1) - masters = [] - - # The first step is to split instances by IP. This is useful as - # we'll try to allocate master nodes in different physical machines - # (as much as possible) and to allocate slaves of a given master in - # different physical machines as well. - # - # This code assumes just that if the IP is different, than it is more - # likely that the instance is running in a different physical host - # or at least a different virtual machine. - ips = {} - @nodes.each{|n| - ips[n.info[:host]] = [] if !ips[n.info[:host]] - ips[n.info[:host]] << n - } - - # Select master instances - puts "Using #{masters_count} masters:" - interleaved = [] - stop = false - while not stop do - # Take one node from each IP until we run out of nodes - # across every IP. - ips.each do |ip,nodes| - if nodes.empty? - # if this IP has no remaining nodes, check for termination - if interleaved.length == nodes_count - # stop when 'interleaved' has accumulated all nodes - stop = true - next - end - else - # else, move one node from this IP to 'interleaved' - interleaved.push nodes.shift - end - end - end - - masters = interleaved.slice!(0, masters_count) - nodes_count -= masters.length - - masters.each{|m| puts m} - - # Alloc slots on masters - slots_per_node = ClusterHashSlots.to_f / masters_count - first = 0 - cursor = 0.0 - masters.each_with_index{|n,masternum| - last = (cursor+slots_per_node-1).round - if last > ClusterHashSlots || masternum == masters.length-1 - last = ClusterHashSlots-1 - end - last = first if last < first # Min step is 1. - n.add_slots first..last - first = last+1 - cursor += slots_per_node - } - - # Select N replicas for every master. - # We try to split the replicas among all the IPs with spare nodes - # trying to avoid the host where the master is running, if possible. - # - # Note we loop two times. The first loop assigns the requested - # number of replicas to each master. The second loop assigns any - # remaining instances as extra replicas to masters. Some masters - # may end up with more than their requested number of replicas, but - # all nodes will be used. - assignment_verbose = false - - [:requested,:unused].each do |assign| - masters.each do |m| - assigned_replicas = 0 - while assigned_replicas < @replicas - break if nodes_count == 0 - if assignment_verbose - if assign == :requested - puts "Requesting total of #{@replicas} replicas " \ - "(#{assigned_replicas} replicas assigned " \ - "so far with #{nodes_count} total remaining)." - elsif assign == :unused - puts "Assigning extra instance to replication " \ - "role too (#{nodes_count} remaining)." - end - end - - # Return the first node not matching our current master - node = interleaved.find{|n| n.info[:host] != m.info[:host]} - - # If we found a node, use it as a best-first match. - # Otherwise, we didn't find a node on a different IP, so we - # go ahead and use a same-IP replica. - if node - slave = node - interleaved.delete node - else - slave = interleaved.shift - end - slave.set_as_replica(m.info[:name]) - nodes_count -= 1 - assigned_replicas += 1 - puts "Adding replica #{slave} to #{m}" - - # If we are in the "assign extra nodes" loop, - # we want to assign one extra replica to each - # master before repeating masters. - # This break lets us assign extra replicas to masters - # in a round-robin way. - break if assign == :unused - end - end - end - end - - def flush_nodes_config - @nodes.each{|n| - n.flush_node_config - } - end - - def show_nodes - @nodes.each{|n| - xputs n.info_string - } - end - - # Redis Cluster config epoch collision resolution code is able to eventually - # set a different epoch to each node after a new cluster is created, but - # it is slow compared to assign a progressive config epoch to each node - # before joining the cluster. However we do just a best-effort try here - # since if we fail is not a problem. - def assign_config_epoch - config_epoch = 1 - @nodes.each{|n| - begin - n.r.cluster("set-config-epoch",config_epoch) - rescue - end - config_epoch += 1 - } - end - - def join_cluster - # We use a brute force approach to make sure the node will meet - # each other, that is, sending CLUSTER MEET messages to all the nodes - # about the very same node. - # Thanks to gossip this information should propagate across all the - # cluster in a matter of seconds. - first = false - @nodes.each{|n| - if !first then first = n.info; next; end # Skip the first node - n.r.cluster("meet",first[:host],first[:port]) - } - end - - def yes_or_die(msg) - print "#{msg} (type 'yes' to accept): " - STDOUT.flush - if !(STDIN.gets.chomp.downcase == "yes") - xputs "*** Aborting..." - exit 1 - end - end - - def load_cluster_info_from_node(nodeaddr) - node = ClusterNode.new(nodeaddr) - node.connect(:abort => true) - node.assert_cluster - node.load_info(:getfriends => true) - add_node(node) - node.friends.each{|f| - next if f[:flags].index("noaddr") || - f[:flags].index("disconnected") || - f[:flags].index("fail") - fnode = ClusterNode.new(f[:addr]) - fnode.connect() - next if !fnode.r - begin - fnode.load_info() - add_node(fnode) - rescue => e - xputs "[ERR] Unable to load info for node #{fnode}" - end - } - populate_nodes_replicas_info - end - - # This function is called by load_cluster_info_from_node in order to - # add additional information to every node as a list of replicas. - def populate_nodes_replicas_info - # Start adding the new field to every node. - @nodes.each{|n| - n.info[:replicas] = [] - } - - # Populate the replicas field using the replicate field of slave - # nodes. - @nodes.each{|n| - if n.info[:replicate] - master = get_node_by_name(n.info[:replicate]) - if !master - xputs "*** WARNING: #{n} claims to be slave of unknown node ID #{n.info[:replicate]}." - else - master.info[:replicas] << n - end - end - } - end - - # Given a list of source nodes return a "resharding plan" - # with what slots to move in order to move "numslots" slots to another - # instance. - def compute_reshard_table(sources,numslots) - moved = [] - # Sort from bigger to smaller instance, for two reasons: - # 1) If we take less slots than instances it is better to start - # getting from the biggest instances. - # 2) We take one slot more from the first instance in the case of not - # perfect divisibility. Like we have 3 nodes and need to get 10 - # slots, we take 4 from the first, and 3 from the rest. So the - # biggest is always the first. - sources = sources.sort{|a,b| b.slots.length <=> a.slots.length} - source_tot_slots = sources.inject(0) {|sum,source| - sum+source.slots.length - } - sources.each_with_index{|s,i| - # Every node will provide a number of slots proportional to the - # slots it has assigned. - n = (numslots.to_f/source_tot_slots*s.slots.length) - if i == 0 - n = n.ceil - else - n = n.floor - end - s.slots.keys.sort[(0...n)].each{|slot| - if moved.length < numslots - moved << {:source => s, :slot => slot} - end - } - } - return moved - end - - def show_reshard_table(table) - table.each{|e| - puts " Moving slot #{e[:slot]} from #{e[:source].info[:name]}" - } - end - - # Move slots between source and target nodes using MIGRATE. - # - # Options: - # :verbose -- Print a dot for every moved key. - # :fix -- We are moving in the context of a fix. Use REPLACE. - # :cold -- Move keys without opening slots / reconfiguring the nodes. - # :update -- Update nodes.info[:slots] for source/target nodes. - # :quiet -- Don't print info messages. - def move_slot(source,target,slot,o={}) - o = {:pipeline => MigrateDefaultPipeline}.merge(o) - - # We start marking the slot as importing in the destination node, - # and the slot as migrating in the target host. Note that the order of - # the operations is important, as otherwise a client may be redirected - # to the target node that does not yet know it is importing this slot. - if !o[:quiet] - print "Moving slot #{slot} from #{source} to #{target}: " - STDOUT.flush - end - - if !o[:cold] - target.r.cluster("setslot",slot,"importing",source.info[:name]) - source.r.cluster("setslot",slot,"migrating",target.info[:name]) - end - # Migrate all the keys from source to target using the MIGRATE command - while true - keys = source.r.cluster("getkeysinslot",slot,o[:pipeline]) - break if keys.length == 0 - begin - source.r.client.call(["migrate",target.info[:host],target.info[:port],"",0,@timeout,:keys,*keys]) - rescue => e - if o[:fix] && e.to_s =~ /BUSYKEY/ - xputs "*** Target key exists. Replacing it for FIX." - source.r.client.call(["migrate",target.info[:host],target.info[:port],"",0,@timeout,:replace,:keys,*keys]) - else - puts "" - xputs "[ERR] Calling MIGRATE: #{e}" - exit 1 - end - end - print "."*keys.length if o[:dots] - STDOUT.flush - end - - puts if !o[:quiet] - # Set the new node as the owner of the slot in all the known nodes. - if !o[:cold] - @nodes.each{|n| - next if n.has_flag?("slave") - n.r.cluster("setslot",slot,"node",target.info[:name]) - } - end - - # Update the node logical config - if o[:update] then - source.info[:slots].delete(slot) - target.info[:slots][slot] = true - end - end - - # redis-trib subcommands implementations. - - def check_cluster_cmd(argv,opt) - load_cluster_info_from_node(argv[0]) - check_cluster - end - - def info_cluster_cmd(argv,opt) - load_cluster_info_from_node(argv[0]) - show_cluster_info - end - - def rebalance_cluster_cmd(argv,opt) - opt = { - 'pipeline' => MigrateDefaultPipeline, - 'threshold' => RebalanceDefaultThreshold - }.merge(opt) - - # Load nodes info before parsing options, otherwise we can't - # handle --weight. - load_cluster_info_from_node(argv[0]) - - # Options parsing - threshold = opt['threshold'].to_i - autoweights = opt['auto-weights'] - weights = {} - opt['weight'].each{|w| - fields = w.split("=") - node = get_node_by_abbreviated_name(fields[0]) - if !node || !node.has_flag?("master") - puts "*** No such master node #{fields[0]}" - exit 1 - end - weights[node.info[:name]] = fields[1].to_f - } if opt['weight'] - useempty = opt['use-empty-masters'] - - # Assign a weight to each node, and compute the total cluster weight. - total_weight = 0 - nodes_involved = 0 - @nodes.each{|n| - if n.has_flag?("master") - next if !useempty && n.slots.length == 0 - n.info[:w] = weights[n.info[:name]] ? weights[n.info[:name]] : 1 - total_weight += n.info[:w] - nodes_involved += 1 - end - } - - # Check cluster, only proceed if it looks sane. - check_cluster(:quiet => true) - if @errors.length != 0 - puts "*** Please fix your cluster problems before rebalancing" - exit 1 - end - - # Calculate the slots balance for each node. It's the number of - # slots the node should lose (if positive) or gain (if negative) - # in order to be balanced. - threshold = opt['threshold'].to_f - threshold_reached = false - @nodes.each{|n| - if n.has_flag?("master") - next if !n.info[:w] - expected = ((ClusterHashSlots.to_f / total_weight) * - n.info[:w]).to_i - n.info[:balance] = n.slots.length - expected - # Compute the percentage of difference between the - # expected number of slots and the real one, to see - # if it's over the threshold specified by the user. - over_threshold = false - if threshold > 0 - if n.slots.length > 0 - err_perc = (100-(100.0*expected/n.slots.length)).abs - over_threshold = true if err_perc > threshold - elsif expected > 0 - over_threshold = true - end - end - threshold_reached = true if over_threshold - end - } - if !threshold_reached - xputs "*** No rebalancing needed! All nodes are within the #{threshold}% threshold." - return - end - - # Only consider nodes we want to change - sn = @nodes.select{|n| - n.has_flag?("master") && n.info[:w] - } - - # Because of rounding, it is possible that the balance of all nodes - # summed does not give 0. Make sure that nodes that have to provide - # slots are always matched by nodes receiving slots. - total_balance = sn.map{|x| x.info[:balance]}.reduce{|a,b| a+b} - while total_balance > 0 - sn.each{|n| - if n.info[:balance] < 0 && total_balance > 0 - n.info[:balance] -= 1 - total_balance -= 1 - end - } - end - - # Sort nodes by their slots balance. - sn = sn.sort{|a,b| - a.info[:balance] <=> b.info[:balance] - } - - xputs ">>> Rebalancing across #{nodes_involved} nodes. Total weight = #{total_weight}" - - if $verbose - sn.each{|n| - puts "#{n} balance is #{n.info[:balance]} slots" - } - end - - # Now we have at the start of the 'sn' array nodes that should get - # slots, at the end nodes that must give slots. - # We take two indexes, one at the start, and one at the end, - # incrementing or decrementing the indexes accordingly til we - # find nodes that need to get/provide slots. - dst_idx = 0 - src_idx = sn.length - 1 - - while dst_idx < src_idx - dst = sn[dst_idx] - src = sn[src_idx] - numslots = [dst.info[:balance],src.info[:balance]].map{|n| - n.abs - }.min - - if numslots > 0 - puts "Moving #{numslots} slots from #{src} to #{dst}" - - # Actaully move the slots. - reshard_table = compute_reshard_table([src],numslots) - if reshard_table.length != numslots - xputs "*** Assertio failed: Reshard table != number of slots" - exit 1 - end - if opt['simulate'] - print "#"*reshard_table.length - else - reshard_table.each{|e| - move_slot(e[:source],dst,e[:slot], - :quiet=>true, - :dots=>false, - :update=>true, - :pipeline=>opt['pipeline']) - print "#" - STDOUT.flush - } - end - puts - end - - # Update nodes balance. - dst.info[:balance] += numslots - src.info[:balance] -= numslots - dst_idx += 1 if dst.info[:balance] == 0 - src_idx -= 1 if src.info[:balance] == 0 - end - end - - def fix_cluster_cmd(argv,opt) - @fix = true - @timeout = opt['timeout'].to_i if opt['timeout'] - - load_cluster_info_from_node(argv[0]) - check_cluster - end - - def reshard_cluster_cmd(argv,opt) - opt = {'pipeline' => MigrateDefaultPipeline}.merge(opt) - - load_cluster_info_from_node(argv[0]) - check_cluster - if @errors.length != 0 - puts "*** Please fix your cluster problems before resharding" - exit 1 - end - - @timeout = opt['timeout'].to_i if opt['timeout'].to_i - - # Get number of slots - if opt['slots'] - numslots = opt['slots'].to_i - else - numslots = 0 - while numslots <= 0 or numslots > ClusterHashSlots - print "How many slots do you want to move (from 1 to #{ClusterHashSlots})? " - numslots = STDIN.gets.to_i - end - end - - # Get the target instance - if opt['to'] - target = get_node_by_name(opt['to']) - if !target || target.has_flag?("slave") - xputs "*** The specified node is not known or not a master, please retry." - exit 1 - end - else - target = nil - while not target - print "What is the receiving node ID? " - target = get_node_by_name(STDIN.gets.chop) - if !target || target.has_flag?("slave") - xputs "*** The specified node is not known or not a master, please retry." - target = nil - end - end - end - - # Get the source instances - sources = [] - if opt['from'] - opt['from'].split(',').each{|node_id| - if node_id == "all" - sources = "all" - break - end - src = get_node_by_name(node_id) - if !src || src.has_flag?("slave") - xputs "*** The specified node is not known or is not a master, please retry." - exit 1 - end - sources << src - } - else - xputs "Please enter all the source node IDs." - xputs " Type 'all' to use all the nodes as source nodes for the hash slots." - xputs " Type 'done' once you entered all the source nodes IDs." - while true - print "Source node ##{sources.length+1}:" - line = STDIN.gets.chop - src = get_node_by_name(line) - if line == "done" - break - elsif line == "all" - sources = "all" - break - elsif !src || src.has_flag?("slave") - xputs "*** The specified node is not known or is not a master, please retry." - elsif src.info[:name] == target.info[:name] - xputs "*** It is not possible to use the target node as source node." - else - sources << src - end - end - end - - if sources.length == 0 - puts "*** No source nodes given, operation aborted" - exit 1 - end - - # Handle soures == all. - if sources == "all" - sources = [] - @nodes.each{|n| - next if n.info[:name] == target.info[:name] - next if n.has_flag?("slave") - sources << n - } - end - - # Check if the destination node is the same of any source nodes. - if sources.index(target) - xputs "*** Target node is also listed among the source nodes!" - exit 1 - end - - puts "\nReady to move #{numslots} slots." - puts " Source nodes:" - sources.each{|s| puts " "+s.info_string} - puts " Destination node:" - puts " #{target.info_string}" - reshard_table = compute_reshard_table(sources,numslots) - puts " Resharding plan:" - show_reshard_table(reshard_table) - if !opt['yes'] - print "Do you want to proceed with the proposed reshard plan (yes/no)? " - yesno = STDIN.gets.chop - exit(1) if (yesno != "yes") - end - reshard_table.each{|e| - move_slot(e[:source],target,e[:slot], - :dots=>true, - :pipeline=>opt['pipeline']) - } - end - - # This is an helper function for create_cluster_cmd that verifies if - # the number of nodes and the specified replicas have a valid configuration - # where there are at least three master nodes and enough replicas per node. - def check_create_parameters - masters = @nodes.length/(@replicas+1) - if masters < 3 - puts "*** ERROR: Invalid configuration for cluster creation." - puts "*** Redis Cluster requires at least 3 master nodes." - puts "*** This is not possible with #{@nodes.length} nodes and #{@replicas} replicas per node." - puts "*** At least #{3*(@replicas+1)} nodes are required." - exit 1 - end - end - - def create_cluster_cmd(argv,opt) - opt = {'replicas' => 0}.merge(opt) - @replicas = opt['replicas'].to_i - - xputs ">>> Creating cluster" - argv[0..-1].each{|n| - node = ClusterNode.new(n) - node.connect(:abort => true) - node.assert_cluster - node.load_info - node.assert_empty - add_node(node) - } - check_create_parameters - xputs ">>> Performing hash slots allocation on #{@nodes.length} nodes..." - alloc_slots - show_nodes - yes_or_die "Can I set the above configuration?" - flush_nodes_config - xputs ">>> Nodes configuration updated" - xputs ">>> Assign a different config epoch to each node" - assign_config_epoch - xputs ">>> Sending CLUSTER MEET messages to join the cluster" - join_cluster - # Give one second for the join to start, in order to avoid that - # wait_cluster_join will find all the nodes agree about the config as - # they are still empty with unassigned slots. - sleep 1 - wait_cluster_join - flush_nodes_config # Useful for the replicas - check_cluster - end - - def addnode_cluster_cmd(argv,opt) - xputs ">>> Adding node #{argv[0]} to cluster #{argv[1]}" - - # Check the existing cluster - load_cluster_info_from_node(argv[1]) - check_cluster - - # If --master-id was specified, try to resolve it now so that we - # abort before starting with the node configuration. - if opt['slave'] - if opt['master-id'] - master = get_node_by_name(opt['master-id']) - if !master - xputs "[ERR] No such master ID #{opt['master-id']}" - end - else - master = get_master_with_least_replicas - xputs "Automatically selected master #{master}" - end - end - - # Add the new node - new = ClusterNode.new(argv[0]) - new.connect(:abort => true) - new.assert_cluster - new.load_info - new.assert_empty - first = @nodes.first.info - add_node(new) - - # Send CLUSTER MEET command to the new node - xputs ">>> Send CLUSTER MEET to node #{new} to make it join the cluster." - new.r.cluster("meet",first[:host],first[:port]) - - # Additional configuration is needed if the node is added as - # a slave. - if opt['slave'] - wait_cluster_join - xputs ">>> Configure node as replica of #{master}." - new.r.cluster("replicate",master.info[:name]) - end - xputs "[OK] New node added correctly." - end - - def delnode_cluster_cmd(argv,opt) - id = argv[1].downcase - xputs ">>> Removing node #{id} from cluster #{argv[0]}" - - # Load cluster information - load_cluster_info_from_node(argv[0]) - - # Check if the node exists and is not empty - node = get_node_by_name(id) - - if !node - xputs "[ERR] No such node ID #{id}" - exit 1 - end - - if node.slots.length != 0 - xputs "[ERR] Node #{node} is not empty! Reshard data away and try again." - exit 1 - end - - # Send CLUSTER FORGET to all the nodes but the node to remove - xputs ">>> Sending CLUSTER FORGET messages to the cluster..." - @nodes.each{|n| - next if n == node - if n.info[:replicate] && n.info[:replicate].downcase == id - # Reconfigure the slave to replicate with some other node - master = get_master_with_least_replicas - xputs ">>> #{n} as replica of #{master}" - n.r.cluster("replicate",master.info[:name]) - end - n.r.cluster("forget",argv[1]) - } - - # Finally shutdown the node - xputs ">>> SHUTDOWN the node." - node.r.shutdown - end - - def set_timeout_cluster_cmd(argv,opt) - timeout = argv[1].to_i - if timeout < 100 - puts "Setting a node timeout of less than 100 milliseconds is a bad idea." - exit 1 - end - - # Load cluster information - load_cluster_info_from_node(argv[0]) - ok_count = 0 - err_count = 0 - - # Send CLUSTER FORGET to all the nodes but the node to remove - xputs ">>> Reconfiguring node timeout in every cluster node..." - @nodes.each{|n| - begin - n.r.config("set","cluster-node-timeout",timeout) - n.r.config("rewrite") - ok_count += 1 - xputs "*** New timeout set for #{n}" - rescue => e - puts "ERR setting node-timeot for #{n}: #{e}" - err_count += 1 - end - } - xputs ">>> New node timeout set. #{ok_count} OK, #{err_count} ERR." - end - - def call_cluster_cmd(argv,opt) - cmd = argv[1..-1] - cmd[0] = cmd[0].upcase - - # Load cluster information - load_cluster_info_from_node(argv[0]) - xputs ">>> Calling #{cmd.join(" ")}" - @nodes.each{|n| - begin - res = n.r.send(*cmd) - puts "#{n}: #{res}" - rescue => e - puts "#{n}: #{e}" - end - } - end - - def import_cluster_cmd(argv,opt) - source_addr = opt['from'] - xputs ">>> Importing data from #{source_addr} to cluster #{argv[1]}" - use_copy = opt['copy'] - use_replace = opt['replace'] - - # Check the existing cluster. - load_cluster_info_from_node(argv[0]) - check_cluster - - # Connect to the source node. - xputs ">>> Connecting to the source Redis instance" - src_host,src_port = source_addr.split(":") - source = Redis.new(:host =>src_host, :port =>src_port) - if source.info['cluster_enabled'].to_i == 1 - xputs "[ERR] The source node should not be a cluster node." - end - xputs "*** Importing #{source.dbsize} keys from DB 0" - - # Build a slot -> node map - slots = {} - @nodes.each{|n| - n.slots.each{|s,_| - slots[s] = n - } - } - - # Use SCAN to iterate over the keys, migrating to the - # right node as needed. - cursor = nil - while cursor != 0 - cursor,keys = source.scan(cursor, :count => 1000) - cursor = cursor.to_i - keys.each{|k| - # Migrate keys using the MIGRATE command. - slot = key_to_slot(k) - target = slots[slot] - print "Migrating #{k} to #{target}: " - STDOUT.flush - begin - cmd = ["migrate",target.info[:host],target.info[:port],k,0,@timeout] - cmd << :copy if use_copy - cmd << :replace if use_replace - source.client.call(cmd) - rescue => e - puts e - else - puts "OK" - end - } - end - end - - def help_cluster_cmd(argv,opt) - show_help - exit 0 - end - - # Parse the options for the specific command "cmd". - # Returns an hash populate with option => value pairs, and the index of - # the first non-option argument in ARGV. - def parse_options(cmd) - idx = 1 ; # Current index into ARGV - options={} - while idx < ARGV.length && ARGV[idx][0..1] == '--' - if ARGV[idx][0..1] == "--" - option = ARGV[idx][2..-1] - idx += 1 - - # --verbose is a global option - if option == "verbose" - $verbose = true - next - end - - if ALLOWED_OPTIONS[cmd] == nil || ALLOWED_OPTIONS[cmd][option] == nil - puts "Unknown option '#{option}' for command '#{cmd}'" - exit 1 - end - if ALLOWED_OPTIONS[cmd][option] != false - value = ARGV[idx] - idx += 1 - else - value = true - end - - # If the option is set to [], it's a multiple arguments - # option. We just queue every new value into an array. - if ALLOWED_OPTIONS[cmd][option] == [] - options[option] = [] if !options[option] - options[option] << value - else - options[option] = value - end - else - # Remaining arguments are not options. - break - end - end - - # Enforce mandatory options - if ALLOWED_OPTIONS[cmd] - ALLOWED_OPTIONS[cmd].each {|option,val| - if !options[option] && val == :required - puts "Option '--#{option}' is required "+ \ - "for subcommand '#{cmd}'" - exit 1 - end - } - end - return options,idx - end -end - -################################################################################# -# Libraries -# -# We try to don't depend on external libs since this is a critical part -# of Redis Cluster. -################################################################################# - -# This is the CRC16 algorithm used by Redis Cluster to hash keys. -# Implementation according to CCITT standards. -# -# This is actually the XMODEM CRC 16 algorithm, using the -# following parameters: -# -# Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" -# Width : 16 bit -# Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) -# Initialization : 0000 -# Reflect Input byte : False -# Reflect Output CRC : False -# Xor constant to output CRC : 0000 -# Output for "123456789" : 31C3 - -module RedisClusterCRC16 - def RedisClusterCRC16.crc16(bytes) - crc = 0 - bytes.each_byte{|b| - crc = ((crc<<8) & 0xffff) ^ XMODEMCRC16Lookup[((crc>>8)^b) & 0xff] - } - crc - end - -private - XMODEMCRC16Lookup = [ - 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, - 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, - 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, - 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, - 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, - 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, - 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, - 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, - 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, - 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, - 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, - 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, - 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, - 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, - 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, - 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, - 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, - 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, - 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, - 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, - 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, - 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, - 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, - 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, - 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, - 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, - 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, - 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, - 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, - 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, - 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, - 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 - ] -end - -# Turn a key name into the corrisponding Redis Cluster slot. -def key_to_slot(key) - # Only hash what is inside {...} if there is such a pattern in the key. - # Note that the specification requires the content that is between - # the first { and the first } after the first {. If we found {} without - # nothing in the middle, the whole key is hashed as usually. - s = key.index "{" - if s - e = key.index "}",s+1 - if e && e != s+1 - key = key[s+1..e-1] - end - end - RedisClusterCRC16.crc16(key) % 16384 -end - -################################################################################# -# Definition of commands -################################################################################# - -COMMANDS={ - "create" => ["create_cluster_cmd", -2, "host1:port1 ... hostN:portN"], - "check" => ["check_cluster_cmd", 2, "host:port"], - "info" => ["info_cluster_cmd", 2, "host:port"], - "fix" => ["fix_cluster_cmd", 2, "host:port"], - "reshard" => ["reshard_cluster_cmd", 2, "host:port"], - "rebalance" => ["rebalance_cluster_cmd", -2, "host:port"], - "add-node" => ["addnode_cluster_cmd", 3, "new_host:new_port existing_host:existing_port"], - "del-node" => ["delnode_cluster_cmd", 3, "host:port node_id"], - "set-timeout" => ["set_timeout_cluster_cmd", 3, "host:port milliseconds"], - "call" => ["call_cluster_cmd", -3, "host:port command arg arg .. arg"], - "import" => ["import_cluster_cmd", 2, "host:port"], - "help" => ["help_cluster_cmd", 1, "(show this help)"] -} - -ALLOWED_OPTIONS={ - "create" => {"replicas" => true}, - "add-node" => {"slave" => false, "master-id" => true}, - "import" => {"from" => :required, "copy" => false, "replace" => false}, - "reshard" => {"from" => true, "to" => true, "slots" => true, "yes" => false, "timeout" => true, "pipeline" => true}, - "rebalance" => {"weight" => [], "auto-weights" => false, "use-empty-masters" => false, "timeout" => true, "simulate" => false, "pipeline" => true, "threshold" => true}, - "fix" => {"timeout" => MigrateDefaultTimeout}, -} - -def show_help - puts "Usage: redis-trib \n\n" - COMMANDS.each{|k,v| - o = "" - puts " #{k.ljust(15)} #{v[2]}" - if ALLOWED_OPTIONS[k] - ALLOWED_OPTIONS[k].each{|optname,has_arg| - puts " --#{optname}" + (has_arg ? " " : "") - } - end - } - puts "\nFor check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.\n" -end - -# Sanity check -if ARGV.length == 0 - show_help - exit 1 -end - -rt = RedisTrib.new -cmd_spec = COMMANDS[ARGV[0].downcase] -if !cmd_spec - puts "Unknown redis-trib subcommand '#{ARGV[0]}'" - exit 1 -end - -# Parse options -cmd_options,first_non_option = rt.parse_options(ARGV[0].downcase) -rt.check_arity(cmd_spec[1],ARGV.length-(first_non_option-1)) - -# Dispatch -rt.send(cmd_spec[0],ARGV[first_non_option..-1],cmd_options) diff --git a/Redis Configs/secure.conf b/Redis Configs/secure.conf deleted file mode 100644 index 766ca883e..000000000 --- a/Redis Configs/secure.conf +++ /dev/null @@ -1,6 +0,0 @@ -port 6381 -requirepass changeme -dbfilename secure.rdb -databases 2000 -maxmemory 512mb -save "" \ No newline at end of file diff --git a/Redis Configs/slave.conf b/Redis Configs/slave.conf deleted file mode 100644 index f3c3096c1..000000000 --- a/Redis Configs/slave.conf +++ /dev/null @@ -1,6 +0,0 @@ -port 6380 -slaveof 127.0.0.1 6379 -dbfilename slave.rdb -databases 2000 -maxmemory 2gb -save "" \ No newline at end of file diff --git a/Shared.ruleset b/Shared.ruleset new file mode 100644 index 000000000..4d85eaba8 --- /dev/null +++ b/Shared.ruleset @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/StackExchange.Redis.Modules/RediSearch/Client.cs b/StackExchange.Redis.Modules/RediSearch/Client.cs deleted file mode 100644 index 5334ae903..000000000 --- a/StackExchange.Redis.Modules/RediSearch/Client.cs +++ /dev/null @@ -1,420 +0,0 @@ -// .NET port of https://github.com/RedisLabs/JRediSearch/ - -using StackExchange.Redis; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace StackExchange.Redis.Modules.RediSearch -{ - public sealed class Client - { - [Flags] - public enum IndexOptions - { - /// - /// All options disabled - /// - None = 0, - /// - /// Set this to tell the index not to save term offset vectors. This reduces memory consumption but does not - /// allow performing exact matches, and reduces overall relevance of multi-term queries - /// - UseTermOffsets = 1, - /// - /// If set (default), we keep flags per index record telling us what fields the term appeared on, - /// and allowing us to filter results by field - /// - KeepFieldFlags = 2, - /// - /// If set, we keep an index of the top entries per term, allowing extremely fast single word queries - /// regardless of index size, at the cost of more memory - /// - UseScoreIndexes = 4, - /// - /// The default indexing options - use term offsets and keep fields flags - /// - Default = UseTermOffsets | KeepFieldFlags - } - private static void SerializeRedisArgs(IndexOptions flags, List args) - { - if ((flags & IndexOptions.UseTermOffsets) == 0) - { - args.Add("NOOFFSETS".Literal()); - } - if ((flags & IndexOptions.KeepFieldFlags) == 0) - { - args.Add("NOFIELDS".Literal()); - } - if ((flags & IndexOptions.UseScoreIndexes) == 0) - { - args.Add("NOSCOREIDX".Literal()); - } - } - private readonly IDatabaseAsync _db; - private IDatabase DbSync - => (_db as IDatabase) ?? throw new InvalidOperationException("Synchronous operations are not available on this database instance"); - - private readonly object _boxedIndexName; - public RedisKey IndexName => (RedisKey)_boxedIndexName; - public Client(RedisKey indexName, IDatabaseAsync db) - { - _db = db ?? throw new ArgumentNullException(nameof(db)); - _boxedIndexName = indexName; // only box once, not per-command - } - public Client(RedisKey indexName, IDatabase db) : this(indexName, (IDatabaseAsync)db) { } - - /// - /// Create the index definition in redis - /// - /// a schema definition - /// index option flags - /// true if successful - public bool CreateIndex(Schema schema, IndexOptions options) - { - var args = new List(); - - args.Add(_boxedIndexName); - SerializeRedisArgs(options, args); - args.Add("SCHEMA".Literal()); - - foreach (var f in schema.Fields) - { - f.SerializeRedisArgs(args); - } - - return (string)DbSync.Execute("FT.CREATE", args) == "OK"; - } - - /// - /// Create the index definition in redis - /// - /// a schema definition - /// index option flags - /// true if successful - public async Task CreateIndexAsync(Schema schema, IndexOptions options) - { - var args = new List(); - - args.Add(_boxedIndexName); - SerializeRedisArgs(options, args); - args.Add("SCHEMA".Literal()); - - foreach (var f in schema.Fields) - { - f.SerializeRedisArgs(args); - } - - return (string)await _db.ExecuteAsync("FT.CREATE", args).ConfigureAwait(false) == "OK"; - } - - /// - /// Search the index - /// - /// a object with the query string and optional parameters - /// a object with the results - public SearchResult Search(Query q) - { - var args = new List(); - args.Add(_boxedIndexName); - q.SerializeRedisArgs(args); - - var resp = (RedisResult[])DbSync.Execute("FT.SEARCH", args); - return new SearchResult(resp, !q.NoContent, q.WithScores, q.WithPayloads); - } - - /// - /// Search the index - /// - /// a object with the query string and optional parameters - /// a object with the results - public async Task SearchAsync(Query q) - { - var args = new List(); - args.Add(_boxedIndexName); - q.SerializeRedisArgs(args); - - var resp = (RedisResult[])await _db.ExecuteAsync("FT.SEARCH", args).ConfigureAwait(false); - return new SearchResult(resp, !q.NoContent, q.WithScores, q.WithPayloads); - } - - /// - /// Add a single document to the query - /// - /// the id of the document. It cannot belong to a document already in the index unless replace is set - /// the document's score, floating point number between 0 and 1 - /// a map of the document's fields - /// if set, we only index the document and do not save its contents. This allows fetching just doc ids - /// if set, and the document already exists, we reindex and update it - /// if set, we can save a payload in the index to be retrieved or evaluated by scoring functions on the server - public bool AddDocument(string docId, Dictionary fields, double score = 1.0, bool noSave = false, bool replace = false, byte[] payload = null) - { - var args = BuildAddDocumentArgs(docId, fields, score, noSave, replace, payload); - return (string)DbSync.Execute("FT.ADD", args) == "OK"; - } - - /// - /// Add a single document to the query - /// - /// the id of the document. It cannot belong to a document already in the index unless replace is set - /// the document's score, floating point number between 0 and 1 - /// a map of the document's fields - /// if set, we only index the document and do not save its contents. This allows fetching just doc ids - /// if set, and the document already exists, we reindex and update it - /// if set, we can save a payload in the index to be retrieved or evaluated by scoring functions on the server - public async Task AddDocumentAsync(string docId, Dictionary fields, double score = 1.0, bool noSave = false, bool replace = false, byte[] payload = null) - { - var args = BuildAddDocumentArgs(docId, fields, score, noSave, replace, payload); - return (string)await _db.ExecuteAsync("FT.ADD", args).ConfigureAwait(false) == "OK"; - } - - private List BuildAddDocumentArgs(string docId, Dictionary fields, double score, bool noSave, bool replace, byte[] payload) - { - var args = new List { _boxedIndexName, docId, score }; - if (noSave) - { - args.Add("NOSAVE".Literal()); - } - if (replace) - { - args.Add("REPLACE".Literal()); - } - if (payload != null) - { - args.Add("PAYLOAD".Literal()); - // TODO: Fix this - args.Add(payload); - } - - args.Add("FIELDS".Literal()); - foreach (var ent in fields) - { - args.Add(ent.Key); - args.Add(ent.Value); - } - return args; - } - - /// - /// replaceDocument is a convenience for calling addDocument with replace=true - /// - public bool ReplaceDocument(string docId, Dictionary fields, double score = 1.0, byte[] payload = null) - => AddDocument(docId, fields, score, false, true, payload); - - /// - /// replaceDocument is a convenience for calling addDocument with replace=true - /// - public Task ReplaceDocumentAsync(string docId, Dictionary fields, double score = 1.0, byte[] payload = null) - => AddDocumentAsync(docId, fields, score, false, true, payload); - - /// - /// Index a document already in redis as a HASH key. - /// - /// the id of the document in redis. This must match an existing, unindexed HASH key - /// the document's index score, between 0 and 1 - /// if set, and the document already exists, we reindex and update it - /// true on success - public bool AddHash(string docId, double score, bool replace) - { - var args = new List { _boxedIndexName, docId, score }; - if (replace) - { - args.Add("REPLACE".Literal()); - } - return (string)DbSync.Execute("FT.ADDHASH", args) == "OK"; - } - /// - /// Index a document already in redis as a HASH key. - /// - /// the id of the document in redis. This must match an existing, unindexed HASH key - /// the document's index score, between 0 and 1 - /// if set, and the document already exists, we reindex and update it - /// true on success - public async Task AddHashAsync(string docId, double score, bool replace) - { - var args = new List { _boxedIndexName, docId, score }; - if (replace) - { - args.Add("REPLACE".Literal()); - } - return (string)await _db.ExecuteAsync("FT.ADDHASH", args).ConfigureAwait(false) == "OK"; - } - - /// - /// Get the index info, including memory consumption and other statistics. - /// - /// TODO: Make a class for easier access to the index properties - /// a map of key/value pairs - public Dictionary GetInfo() - { - return ParseGetInfo(DbSync.Execute("FT.INFO", _boxedIndexName)); - } - /// - /// Get the index info, including memory consumption and other statistics. - /// - /// TODO: Make a class for easier access to the index properties - /// a map of key/value pairs - public async Task> GetInfoAsync() - { - return ParseGetInfo(await _db.ExecuteAsync("FT.INFO", _boxedIndexName).ConfigureAwait(false)); - } - static Dictionary ParseGetInfo(RedisResult value) - { - var res = (RedisValue[])value; - var info = new Dictionary(); - for (int i = 0; i < res.Length; i += 2) - { - var key = (string)res[i]; - var val = res[i + 1]; - info.Add(key, val); - } - return info; - } - - /// - /// Delete a document from the index. - /// - /// the document's id - /// true if it has been deleted, false if it did not exist - public bool DeleteDocument(string docId) - { - return (long)DbSync.Execute("FT.DEL", _boxedIndexName, docId) == 1; - } - - /// - /// Delete a document from the index. - /// - /// the document's id - /// true if it has been deleted, false if it did not exist - public async Task DeleteDocumentAsync(string docId) - { - return (long)await _db.ExecuteAsync("FT.DEL", _boxedIndexName, docId).ConfigureAwait(false) == 1; - } - - /// - /// Drop the index and all associated keys, including documents - /// - /// true on success - public bool DropIndex() - { - return (string)DbSync.Execute("FT.DROP", _boxedIndexName) == "OK"; - } - /// - /// Drop the index and all associated keys, including documents - /// - /// true on success - public async Task DropIndexAsync() - { - return (string) await _db.ExecuteAsync("FT.DROP", _boxedIndexName).ConfigureAwait(false) == "OK"; - } - - /// - /// Optimize memory consumption of the index by removing extra saved capacity. This does not affect speed - /// - public long OptimizeIndex() - { - return (long)DbSync.Execute("FT.OPTIMIZE", _boxedIndexName); - } - - /// - /// Optimize memory consumption of the index by removing extra saved capacity. This does not affect speed - /// - public async Task OptimizeIndexAsync() - { - return (long) await _db.ExecuteAsync("FT.OPTIMIZE", _boxedIndexName).ConfigureAwait(false); - } - - /// - /// Get the size of an autoc-complete suggestion dictionary - /// - public long CountSuggestions() - => (long)DbSync.Execute("FT.SUGLEN", _boxedIndexName); - - /// - /// Get the size of an autoc-complete suggestion dictionary - /// - public async Task CountSuggestionsAsync() - => (long)await _db.ExecuteAsync("FT.SUGLEN", _boxedIndexName).ConfigureAwait(false); - - /// - /// Add a suggestion string to an auto-complete suggestion dictionary. This is disconnected from the index definitions, and leaves creating and updating suggestino dictionaries to the user. - /// - /// the suggestion string we index - /// a floating point number of the suggestion string's weight - /// if set, we increment the existing entry of the suggestion by the given score, instead of replacing the score. This is useful for updating the dictionary based on user queries in real time - /// the current size of the suggestion dictionary. - public long AddSuggestion(string value, double score, bool increment = false) - { - object args = increment - ? new object[] { _boxedIndexName, value, score, "INCR".Literal() } - : new object[] { _boxedIndexName, value, score }; - return (long)DbSync.Execute("FT.SUGADD", args); - } - - /// - /// Add a suggestion string to an auto-complete suggestion dictionary. This is disconnected from the index definitions, and leaves creating and updating suggestino dictionaries to the user. - /// - /// the suggestion string we index - /// a floating point number of the suggestion string's weight - /// if set, we increment the existing entry of the suggestion by the given score, instead of replacing the score. This is useful for updating the dictionary based on user queries in real time - /// the current size of the suggestion dictionary. - public async Task AddSuggestionAsync(string value, double score, bool increment = false) - { - object args = increment - ? new object[] { _boxedIndexName, value, score, "INCR".Literal() } - : new object[] { _boxedIndexName, value, score }; - return (long)await _db.ExecuteAsync("FT.SUGADD", args).ConfigureAwait(false); - } - - /// - /// Delete a string from a suggestion index. - /// - /// the string to delete - public bool DeleteSuggestion(string value) - => (long)DbSync.Execute("FT.SUGDEL", _boxedIndexName, value) == 1; - - /// - /// Delete a string from a suggestion index. - /// - /// the string to delete - public async Task DeleteSuggestionAsync(string value) - => (long)await _db.ExecuteAsync("FT.SUGDEL", _boxedIndexName, value).ConfigureAwait(false) == 1; - - /// - /// Get completion suggestions for a prefix - /// - /// the prefix to complete on - /// if set,we do a fuzzy prefix search, including prefixes at levenshtein distance of 1 from the prefix sent - /// If set, we limit the results to a maximum of num. (Note: The default is 5, and the number cannot be greater than 10). - /// a list of the top suggestions matching the prefix - public string[] GetSuggestions(string prefix, bool fuzzy = false, int max = 5) - { - var args = new List { _boxedIndexName, prefix}; - if (fuzzy) args.Add("FUZZY".Literal()); - if (max != 5) - { - args.Add("MAX".Literal()); - args.Add(max); - } - return (string[])DbSync.Execute("FT.SUGGET", args); - } - /// - /// Get completion suggestions for a prefix - /// - /// the prefix to complete on - /// if set,we do a fuzzy prefix search, including prefixes at levenshtein distance of 1 from the prefix sent - /// If set, we limit the results to a maximum of num. (Note: The default is 5, and the number cannot be greater than 10). - /// a list of the top suggestions matching the prefix - public async Task GetSuggestionsAsync(string prefix, bool fuzzy = false, int max = 5) - { - var args = new List { _boxedIndexName, prefix }; - if (fuzzy) args.Add("FUZZY".Literal()); - if (max != 5) - { - args.Add("MAX".Literal()); - args.Add(max); - } - return (string[])await _db.ExecuteAsync("FT.SUGGET", args).ConfigureAwait(false); - } - } -} diff --git a/StackExchange.Redis.Modules/RediSearch/Document.cs b/StackExchange.Redis.Modules/RediSearch/Document.cs deleted file mode 100644 index 6f241aa9b..000000000 --- a/StackExchange.Redis.Modules/RediSearch/Document.cs +++ /dev/null @@ -1,51 +0,0 @@ -// .NET port of https://github.com/RedisLabs/JRediSearch/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using StackExchange.Redis; - -namespace StackExchange.Redis.Modules.RediSearch -{ - /// - /// Document represents a single indexed document or entity in the engine - /// - public class Document - { - - public string Id { get; } - public double Score { get; } - public byte[] Payload { get; } - private Dictionary properties = new Dictionary(); - - public Document(string id, double score, byte[] payload) - { - Id = id; - Score = score; - Payload = payload; - } - - public static Document Load(string id, double score, byte[] payload, RedisValue[] fields) - { - Document ret = new Document(id, score, payload); - if (fields != null) - { - for (int i = 0; i < fields.Length; i += 2) - { - ret[(string)fields[i]] = fields[i + 1]; - } - } - return ret; - } - - public RedisValue this[string key] - { - get { return properties.TryGetValue(key, out var val) ? val : default(RedisValue); } - internal set { properties[key] = value; } - } - - public bool HasProperty(string key) => properties.ContainsKey(key); - } -} diff --git a/StackExchange.Redis.Modules/RediSearch/Literals.cs b/StackExchange.Redis.Modules/RediSearch/Literals.cs deleted file mode 100644 index fb39761c6..000000000 --- a/StackExchange.Redis.Modules/RediSearch/Literals.cs +++ /dev/null @@ -1,36 +0,0 @@ -using StackExchange.Redis; -using System.Collections; -namespace StackExchange.Redis.Modules.RediSearch -{ - /// - /// Cache to ensure we encode and box literals once only - /// - internal static class Literals - { - private static Hashtable _boxed = new Hashtable(); - private static object _null = RedisValue.Null; - /// - /// Obtain a lazily-cached pre-encoded and boxed representation of a string - /// - /// This shoul donly be used for fixed values, not user data (the cache is never reclaimed, so it will be a memory leak) - public static object Literal(this string value) - { - if (value == null) return _null; - - object boxed = _boxed[value]; - if (boxed == null) - { - lock (_boxed) - { - boxed = _boxed[value]; - if (boxed == null) - { - boxed = (RedisValue)value; - _boxed.Add(value, boxed); - } - } - } - return boxed; - } - } -} diff --git a/StackExchange.Redis.Modules/RediSearch/Query.cs b/StackExchange.Redis.Modules/RediSearch/Query.cs deleted file mode 100644 index 0cd854ec1..000000000 --- a/StackExchange.Redis.Modules/RediSearch/Query.cs +++ /dev/null @@ -1,264 +0,0 @@ -// .NET port of https://github.com/RedisLabs/JRediSearch/ - -using StackExchange.Redis; -using System; -using System.Collections.Generic; -using System.Globalization; - -namespace StackExchange.Redis.Modules.RediSearch -{ - /// - /// Query represents query parameters and filters to load results from the engine - /// - public class Query - { - /// - /// Filter represents a filtering rules in a query - /// - public abstract class Filter - { - - public string Property { get; } - - internal abstract void SerializeRedisArgs(List args); - - internal Filter(string property) - { - Property = property; - } - - } - - /// - /// NumericFilter wraps a range filter on a numeric field. It can be inclusive or exclusive - /// - public class NumericFilter : Filter - { - - private readonly double min, max; - private readonly bool exclusiveMin, exclusiveMax; - - public NumericFilter(string property, double min, bool exclusiveMin, double max, bool exclusiveMax) : base(property) - { - this.min = min; - this.max = max; - this.exclusiveMax = exclusiveMax; - this.exclusiveMin = exclusiveMin; - } - - public NumericFilter(string property, double min, double max) : this(property, min, false, max, false) { } - - - internal override void SerializeRedisArgs(List args) - { - RedisValue FormatNum(double num, bool exclude) - { - if (!exclude || double.IsInfinity(num)) - { - return (RedisValue)num; // can use directly - } - // need to add leading bracket - return "(" + num.ToString("G17", NumberFormatInfo.InvariantInfo); - } - args.Add("FILTER".Literal()); - args.Add(Property); - args.Add(FormatNum(min, exclusiveMin)); - args.Add(FormatNum(max, exclusiveMax)); - } - } - - /// - /// GeoFilter encapsulates a radius filter on a geographical indexed fields - /// - public class GeoFilter : Filter - { - - private readonly double lon, lat, radius; - private GeoUnit unit; - - public GeoFilter(string property, double lon, double lat, double radius, GeoUnit unit) : base(property) - { - this.lon = lon; - this.lat = lat; - this.radius = radius; - this.unit = unit; - } - - internal override void SerializeRedisArgs(List args) - { - args.Add("GEOFILTER".Literal()); - args.Add(Property); - args.Add(lon); - args.Add(lat); - args.Add(radius); - - switch (unit) - { - case GeoUnit.Feet: args.Add("ft".Literal()); break; - case GeoUnit.Kilometers: args.Add("km".Literal()); break; - case GeoUnit.Meters: args.Add("m".Literal()); break; - case GeoUnit.Miles: args.Add("mi".Literal()); break; - default: throw new InvalidOperationException($"Unknown unit: {unit}"); - } - } - } - - private struct Paging - { - public int Offset { get; } - public int Count { get; } - - public Paging(int offset, int count) - { - Offset = offset; - Count = count; - } - } - - /// - /// The query's filter list. We only support AND operation on all those filters - /// - List _filters = new List(); - - /// - /// The textual part of the query - /// - public string QueryString { get; } - - /// - /// The sorting parameters - /// - Paging _paging = new Paging(0, 10); - - /// - /// Set the query to verbatim mode, disabling stemming and query expansion - /// - public bool Verbatim { get; set; } - /// - /// Set the query not to return the contents of documents, and rather just return the ids - /// - public bool NoContent { get; set; } - /// - /// Set the query not to filter for stopwords. In general this should not be used - /// - public bool NoStopwords { get; set; } - /// - /// Set the query to return a factored score for each results. This is useful to merge results from multiple queries. - /// - public bool WithScores { get; set; } - /// - /// Set the query to return object payloads, if any were given - /// - public bool WithPayloads { get; set; } - - /// - /// Set the query language, for stemming purposes; see http://redisearch.io for documentation on languages and stemming - /// - public string Language { get; set; } - protected String[] _fields = null; - /// - /// Set the query payload to be evaluated by the scoring function - /// - public byte[] Payload { get; set; } - - /// - /// Create a new index - /// - public Query(String queryString) - { - QueryString = queryString; - } - - internal void SerializeRedisArgs(List args) - { - args.Add(QueryString); - - if (Verbatim) - { - args.Add("VERBATIM".Literal()); - } - if (NoContent) - { - args.Add("NOCONTENT".Literal()); - } - if (NoStopwords) - { - args.Add("NOSTOPWORDS".Literal()); - } - if (WithScores) - { - args.Add("WITHSCORES".Literal()); - } - if (WithPayloads) - { - args.Add("WITHPAYLOADS".Literal()); - } - if (Language != null) - { - args.Add("LANGUAGE".Literal()); - args.Add(Language); - } - if (_fields != null && _fields.Length > 0) - { - args.Add("INFIELDS".Literal()); - args.Add(_fields.Length); - args.AddRange(_fields); - } - - if (Payload != null) - { - args.Add("PAYLOAD".Literal()); - args.Add(Payload); - } - - if (_paging.Offset != 0 || _paging.Count != 10) - { - args.Add("LIMIT".Literal()); - args.Add(_paging.Offset); - args.Add(_paging.Count); - } - - if (_filters != null && _filters.Count > 0) - { - foreach (var f in _filters) - { - f.SerializeRedisArgs(args); - } - } - } - - /// - /// Limit the results to a certain offset and limit - /// - /// the first result to show, zero based indexing - /// how many results we want to show - /// the query itself, for builder-style syntax - public Query Limit(int offset, int count) - { - _paging = new Paging(offset, count); - return this; - } - - /// - /// Add a filter to the query's filter list - /// - /// either a numeric or geo filter object - /// the query itself - public Query AddFilter(Filter f) - { - _filters.Add(f); - return this; - } - - /// - /// Limit the query to results that are limited to a specific set of fields - /// - /// a list of TEXT fields in the schemas - /// the query object itself - public Query LimitFields(params string[] fields) - { - this._fields = fields; - return this; - } - } -} diff --git a/StackExchange.Redis.Modules/RediSearch/Schema.cs b/StackExchange.Redis.Modules/RediSearch/Schema.cs deleted file mode 100644 index de8f6de32..000000000 --- a/StackExchange.Redis.Modules/RediSearch/Schema.cs +++ /dev/null @@ -1,103 +0,0 @@ -// .NET port of https://github.com/RedisLabs/JRediSearch/ - -using System; -using System.Collections.Generic; - -namespace StackExchange.Redis.Modules.RediSearch -{ - /// - /// Schema abstracts the schema definition when creating an index. - /// Documents can contain fields not mentioned in the schema, but the index will only index pre-defined fields - /// - public sealed class Schema - { - public enum FieldType - { - FullText, - Geo, - Numeric - } - - public class Field - { - public String Name { get; } - public FieldType Type { get; } - - internal Field(string name, FieldType type) - { - Name = name; - Type = type; - } - - internal virtual void SerializeRedisArgs(List args) - { - object GetForRedis(FieldType type) - { - switch (type) - { - case FieldType.FullText: return "TEXT".Literal(); - case FieldType.Geo: return "GEO".Literal(); - case FieldType.Numeric: return "NUMERIC".Literal(); - default: throw new ArgumentOutOfRangeException(nameof(type)); - } - } - args.Add(Name); - args.Add(GetForRedis(Type)); - } - } - public class TextField : Field - { - public double Weight { get; } - internal TextField(string name, double weight = 1.0) : base(name, FieldType.FullText) - { - Weight = weight; - } - internal override void SerializeRedisArgs(List args) - { - base.SerializeRedisArgs(args); - if (Weight != 1.0) - { - args.Add("WEIGHT".Literal()); - args.Add(Weight); - } - } - } - - public List Fields { get; } = new List(); - - /// - /// Add a text field to the schema with a given weight - /// - /// the field's name - /// its weight, a positive floating point number - /// the schema object - public Schema AddTextField(string name, double weight = 1.0) - { - Fields.Add(new TextField(name, weight)); - return this; - } - - /// - /// Add a numeric field to the schema - /// - /// the field's name - /// the schema object - public Schema AddGeoField(string name) - { - Fields.Add(new Field(name, FieldType.Geo)); - return this; - } - - /// - /// Add a numeric field to the schema - /// - /// the field's name - /// the schema object - public Schema AddNumericField(string name) - { - Fields.Add(new Field(name, FieldType.Numeric)); - return this; - } - - } -} diff --git a/StackExchange.Redis.Modules/RediSearch/SearchResult.cs b/StackExchange.Redis.Modules/RediSearch/SearchResult.cs deleted file mode 100644 index fbbf667e0..000000000 --- a/StackExchange.Redis.Modules/RediSearch/SearchResult.cs +++ /dev/null @@ -1,75 +0,0 @@ -// .NET port of https://github.com/RedisLabs/JRediSearch/ - -using StackExchange.Redis; -using System.Collections.Generic; - -namespace StackExchange.Redis.Modules.RediSearch -{ - /// - /// SearchResult encapsulates the returned result from a search query. - /// It contains publically accessible fields for the total number of results, and an array of - /// objects conatining the actual returned documents. - /// - public class SearchResult - { - public long TotalResults { get; } - public List Documents { get; } - - - internal SearchResult(RedisResult[] resp, bool hasContent, bool hasScores, bool hasPayloads) - { - // Calculate the step distance to walk over the results. - // The order of results is id, score (if withScore), payLoad (if hasPayloads), fields - int step = 1; - int scoreOffset = 0; - int contentOffset = 1; - int payloadOffset = 0; - if (hasScores) - { - step += 1; - scoreOffset = 1; - contentOffset += 1; - } - if (hasContent) - { - step += 1; - if (hasPayloads) - { - payloadOffset = scoreOffset + 1; - step += 1; - contentOffset += 1; - } - } - - // the first element is always the number of results - TotalResults = (long)resp[0]; - var docs = new List((resp.Length - 1) / step); - Documents = docs; - for (int i = 1; i < resp.Length; i += step) - { - - var id = (string)resp[i]; - double score = 1.0; - byte[] payload = null; - RedisValue[] fields = null; - if (hasScores) - { - score = (double)resp[i + scoreOffset]; - } - if (hasPayloads) - { - payload = (byte[])resp[i + payloadOffset]; - } - - if (hasContent) - { - fields = (RedisValue[])resp[i + contentOffset]; - } - - docs.Add(Document.Load(id, score, payload, fields)); - } - - - } - } -} diff --git a/StackExchange.Redis.Modules/StackExchange.Redis.Modules.csproj b/StackExchange.Redis.Modules/StackExchange.Redis.Modules.csproj deleted file mode 100644 index 673bb28fd..000000000 --- a/StackExchange.Redis.Modules/StackExchange.Redis.Modules.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - $(LibraryTargetFrameworks) - 0.1 - false - Redis;Search;Modules;RediSearch - - - - - \ No newline at end of file diff --git a/StackExchange.Redis.Modules/Throttling.cs b/StackExchange.Redis.Modules/Throttling.cs deleted file mode 100644 index 7770cb297..000000000 --- a/StackExchange.Redis.Modules/Throttling.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace StackExchange.Redis.Modules.Throttling -{ - public static class ThrottlingExtensions - { - public static ThrottleResult Throttle( - this IDatabase db, RedisKey key, int maxBurst, - int maxPerInterval, - int intervalSeconds = 60, int count = 1) - { - return new ThrottleResult(db.Execute("CL.THROTTLE", - key, maxBurst.Boxed(), maxPerInterval.Boxed(), intervalSeconds.Boxed(), count.Boxed())); - } - public async static Task ThrottleAsync( - this IDatabaseAsync db, RedisKey key, int maxBurst, - int maxPerInterval, - int intervalSeconds = 60, int count = 1) - { - return new ThrottleResult(await db.ExecuteAsync("CL.THROTTLE", - key, maxBurst.Boxed(), maxPerInterval.Boxed(), intervalSeconds.Boxed(), count.Boxed())); - } - - static readonly object[] _boxedInt32 = Enumerable.Range(-1, 128).Select(i => (object)i).ToArray(); - internal static object Boxed(this int value) - => value >= -1 && value <= 126 ? _boxedInt32[value + 1] : (object)value; - } - public struct ThrottleResult - { - internal ThrottleResult(RedisResult result) - { - var arr = (int[])result; - Permitted = arr[0] == 0; - TotalLimit = arr[1]; - RemainingLimit = arr[2]; - RetryAfterSeconds = arr[3]; - ResetAfterSeconds = arr[4]; - } - /// Whether the action was limited - public bool Permitted {get;} - /// The total limit of the key (max_burst + 1). This is equivalent to the common `X-RateLimit-Limit` HTTP header. - public int TotalLimit {get;} - /// The remaining limit of the key. Equivalent to `X-RateLimit-Remaining`. - public int RemainingLimit {get;} - /// The number of seconds until the user should retry, and always -1 if the action was allowed. Equivalent to `Retry-After`. - public int RetryAfterSeconds {get;} - /// The number of seconds until the limit will reset to its maximum capacity. Equivalent to `X-RateLimit-Reset`. - public int ResetAfterSeconds {get;} - } -} \ No newline at end of file diff --git a/StackExchange.Redis.StrongName/StackExchange.Redis.StrongName.csproj b/StackExchange.Redis.StrongName/StackExchange.Redis.StrongName.csproj deleted file mode 100644 index 6b54c378a..000000000 --- a/StackExchange.Redis.StrongName/StackExchange.Redis.StrongName.csproj +++ /dev/null @@ -1,72 +0,0 @@ - - - - $(LibraryTargetFrameworks) - High performance Redis client, incorporating both synchronous and asynchronous usage. - StackExchange.Redis.StrongName - $(DefineConstants);STRONG_NAME - true - true - StackExchange.Redis.StrongName - Async;Redis;Cache;PubSub;Messaging - Library - - - - - - - - - - - - - - - - - - - - - - - - - - - - $(DefineConstants);FEATURE_SERIALIZATION;FEATURE_SOCKET_MODE_POLL - - - - $(DefineConstants);CORE_CLR - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/StackExchange.Redis.StrongName/build.cmd b/StackExchange.Redis.StrongName/build.cmd deleted file mode 100644 index c2ab76303..000000000 --- a/StackExchange.Redis.StrongName/build.cmd +++ /dev/null @@ -1 +0,0 @@ -dotnet msbuild "/t:Restore;Build;Pack" "/p:NuGetBuildTasksPackTargets='000'" "/p:PackageOutputPath=nupkgs" "/p:Configuration=Release" diff --git a/StackExchange.Redis.Tests/AdhocTests.cs b/StackExchange.Redis.Tests/AdhocTests.cs deleted file mode 100644 index 274cb0875..000000000 --- a/StackExchange.Redis.Tests/AdhocTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Text; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class AdhocTests : TestBase - { - [Test] - public void TestAdhocCommandsAPI() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(); - - // needs explicit RedisKey type for key-based - // sharding to work; will still work with strings, - // but no key-based sharding support - RedisKey key = "some_key"; - - // note: if command renames are configured in - // the API, they will still work automatically - db.Execute("del", key); - db.Execute("set", key, "12"); - db.Execute("incrby", key, 4); - int i = (int) db.Execute("get", key); - - Assert.AreEqual(16, i); - - } - } - } -} diff --git a/StackExchange.Redis.Tests/AsyncTests.cs b/StackExchange.Redis.Tests/AsyncTests.cs deleted file mode 100644 index 63d9265d2..000000000 --- a/StackExchange.Redis.Tests/AsyncTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Linq; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class AsyncTests : TestBase - { - protected override string GetConfiguration() - { - return PrimaryServer + ":" + PrimaryPortString; - } - -#if DEBUG // IRedisServerDebug and AllowConnect are only available if DEBUG is defined - [Test] - public void AsyncTasksReportFailureIfServerUnavailable() - { - SetExpectedAmbientFailureCount(-1); // this will get messy - - using(var conn = Create(allowAdmin: true)) - { - var server = conn.GetServer(PrimaryServer, PrimaryPort); - - RedisKey key = Me(); - var db = conn.GetDatabase(); - db.KeyDelete(key); - var a = db.SetAddAsync(key, "a"); - var b = db.SetAddAsync(key, "b"); - - Assert.AreEqual(true, conn.Wait(a)); - Assert.AreEqual(true, conn.Wait(b)); - - conn.AllowConnect = false; - server.SimulateConnectionFailure(); - var c = db.SetAddAsync(key, "c"); - - Assert.IsTrue(c.IsFaulted, "faulted"); - var ex = c.Exception.InnerExceptions.Single(); - Assert.IsInstanceOf(ex); - Assert.AreEqual("No connection is available to service this operation: SADD AsyncTasksReportFailureIfServerUnavailable", ex.Message); - } - } -#endif - } -} diff --git a/StackExchange.Redis.Tests/BasicOps.cs b/StackExchange.Redis.Tests/BasicOps.cs deleted file mode 100644 index 48ea5d6a2..000000000 --- a/StackExchange.Redis.Tests/BasicOps.cs +++ /dev/null @@ -1,709 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -#if FEATURE_BOOKSLEEVE -using BookSleeve; -#endif -using NUnit.Framework; -using StackExchange.Redis.KeyspaceIsolation; -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class BasicOpsTests : TestBase - { - [Test] - [TestCase(true)] - [TestCase(false)] - public void PingOnce(bool preserveOrder) - { - using (var muxer = Create()) - { - muxer.PreserveAsyncOrder = preserveOrder; - var conn = muxer.GetDatabase(); - - var task = conn.PingAsync(); - var duration = muxer.Wait(task); - Console.WriteLine("Ping took: " + duration); - Assert.IsTrue(duration.TotalMilliseconds > 0); - } - } - - [Test] - [TestCase(true)] - [TestCase(false)] - public void RapidDispose(bool preserverOrder) - { - RedisKey key = Me(); - using (var primary = Create()) - { - var conn = primary.GetDatabase(); - conn.KeyDelete(key); - - for (int i = 0; i < 10; i++) - { - using (var secondary = Create(fail: true)) - { - secondary.GetDatabase().StringIncrement(key, flags: CommandFlags.FireAndForget); - } - } - - Assert.AreEqual(10, (int)conn.StringGet(key)); - } - } - - [Test] - [TestCase(true)] - [TestCase(false)] - public void PingMany(bool preserveOrder) - { - using (var muxer = Create()) - { - muxer.PreserveAsyncOrder = preserveOrder; - var conn = muxer.GetDatabase(); - var tasks = new Task[10000]; - - for (int i = 0; i < tasks.Length; i++) - { - tasks[i] = conn.PingAsync(); - } - muxer.WaitAll(tasks); - Assert.IsTrue(tasks[0].Result.TotalMilliseconds > 0); - Assert.IsTrue(tasks[tasks.Length - 1].Result.TotalMilliseconds > 0); - } - } - - [Test] - public void GetWithNullKey() - { - using (var muxer = Create()) - { - var db = muxer.GetDatabase(); - string key = null; - Assert.Throws( - () => db.StringGet(key), - "A null key is not valid in this context"); - } - } - - [Test] - public void SetWithNullKey() - { - using (var muxer = Create()) - { - var db = muxer.GetDatabase(); - string key = null, value = "abc"; - Assert.Throws( - () => db.StringSet(key, value), - "A null key is not valid in this context"); - } - } - - [Test] - public void SetWithNullValue() - { - using (var muxer = Create()) - { - var db = muxer.GetDatabase(); - string key = Me(), value = null; - db.KeyDelete(key, CommandFlags.FireAndForget); - - db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); - Assert.IsTrue(db.KeyExists(key)); - db.StringSet(key, value); - - var actual = (string)db.StringGet(key); - Assert.IsNull(actual); - Assert.IsFalse(db.KeyExists(key)); - } - } - - [Test] - public void SetWithDefaultValue() - { - using (var muxer = Create()) - { - var db = muxer.GetDatabase(); - string key = Me(); - var value = default(RedisValue); // this is kinda 0... ish - db.KeyDelete(key, CommandFlags.FireAndForget); - - db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); - Assert.IsTrue(db.KeyExists(key)); - db.StringSet(key, value); - - var actual = (string)db.StringGet(key); - Assert.IsNull(actual); - Assert.IsFalse(db.KeyExists(key)); - } - } - - [Test] - public void SetWithZeroValue() - { - using (var muxer = Create()) - { - var db = muxer.GetDatabase(); - string key = Me(); - long value = 0; - db.KeyDelete(key, CommandFlags.FireAndForget); - - db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); - Assert.IsTrue(db.KeyExists(key)); - db.StringSet(key, value); - - var actual = (string)db.StringGet(key); - Assert.AreEqual("0", actual); - Assert.IsTrue(db.KeyExists(key)); - } - } - - [Test] - [TestCase(true)] - [TestCase(false)] - public void GetSetAsync(bool preserveOrder) - { - using (var muxer = Create()) - { - muxer.PreserveAsyncOrder = preserveOrder; - var conn = muxer.GetDatabase(); - - RedisKey key = Me(); - var d0 = conn.KeyDeleteAsync(key); - var d1 = conn.KeyDeleteAsync(key); - var g1 = conn.StringGetAsync(key); - var s1 = conn.StringSetAsync(key, "123"); - var g2 = conn.StringGetAsync(key); - var d2 = conn.KeyDeleteAsync(key); - - muxer.Wait(d0); - Assert.IsFalse(muxer.Wait(d1)); - Assert.IsNull((string)muxer.Wait(g1)); - Assert.IsTrue(muxer.Wait(g1).IsNull); - muxer.Wait(s1); - Assert.AreEqual("123", (string)muxer.Wait(g2)); - Assert.AreEqual(123, (int)muxer.Wait(g2)); - Assert.IsFalse(muxer.Wait(g2).IsNull); - Assert.IsTrue(muxer.Wait(d2)); - } - } - - [Test] - [TestCase(true)] - [TestCase(false)] - public void GetSetSync(bool preserveOrder) - { - using (var muxer = Create()) - { - muxer.PreserveAsyncOrder = preserveOrder; - var conn = muxer.GetDatabase(); - - RedisKey key = Me(); - conn.KeyDelete(key); - var d1 = conn.KeyDelete(key); - var g1 = conn.StringGet(key); - conn.StringSet(key, "123"); - var g2 = conn.StringGet(key); - var d2 = conn.KeyDelete(key); - - Assert.IsFalse(d1); - Assert.IsNull((string)g1); - Assert.IsTrue(g1.IsNull); - - Assert.AreEqual("123", (string)g2); - Assert.AreEqual(123, (int)g2); - Assert.IsFalse(g2.IsNull); - Assert.IsTrue(d2); - } - } - - [Test] - [TestCase(true, true)] - [TestCase(true, false)] - [TestCase(false, true)] - [TestCase(false, false)] - public void MassiveBulkOpsAsync(bool preserveOrder, bool withContinuation) - { -#if DEBUG - var oldAsyncCompletionCount = ConnectionMultiplexer.GetAsyncCompletionWorkerCount(); -#endif - using (var muxer = Create()) - { - muxer.PreserveAsyncOrder = preserveOrder; - RedisKey key = "MBOA"; - var conn = muxer.GetDatabase(); - muxer.Wait(conn.PingAsync()); - -#if CORE_CLR - int number = 0; -#endif - Action nonTrivial = delegate - { -#if !CORE_CLR - Thread.SpinWait(5); -#else - for (int i = 0; i < 50; i++) - { - number++; - } -#endif - }; - var watch = Stopwatch.StartNew(); - for (int i = 0; i <= AsyncOpsQty; i++) - { - var t = conn.StringSetAsync(key, i); - if (withContinuation) t.ContinueWith(nonTrivial); - } - int val = (int)muxer.Wait(conn.StringGetAsync(key)); - Assert.AreEqual(AsyncOpsQty, val); - watch.Stop(); - Console.WriteLine("{2}: Time for {0} ops: {1}ms ({3}, {4}); ops/s: {5}", AsyncOpsQty, watch.ElapsedMilliseconds, Me(), - withContinuation ? "with continuation" : "no continuation", preserveOrder ? "preserve order" : "any order", - AsyncOpsQty / watch.Elapsed.TotalSeconds); -#if DEBUG - Console.WriteLine("Async completion workers: " + (ConnectionMultiplexer.GetAsyncCompletionWorkerCount() - oldAsyncCompletionCount)); -#endif - } - } - - [Test] - [TestCase(false, false)] - [TestCase(true, true)] - [TestCase(true, false)] - public void GetWithExpiry(bool exists, bool hasExpiry) - { - using(var conn = Create()) - { - var db = conn.GetDatabase(); - RedisKey key = Me(); - db.KeyDelete(key); - if (exists) - { - if (hasExpiry) - db.StringSet(key, "val", TimeSpan.FromMinutes(5)); - else - db.StringSet(key, "val"); - } - var async = db.StringGetWithExpiryAsync(key); - var syncResult = db.StringGetWithExpiry(key); - var asyncResult = db.Wait(async); - - if(exists) - { - Assert.AreEqual("val", (string)asyncResult.Value); - Assert.AreEqual(hasExpiry, asyncResult.Expiry.HasValue); - if (hasExpiry) Assert.IsTrue(asyncResult.Expiry.Value.TotalMinutes >= 4.9 && asyncResult.Expiry.Value.TotalMinutes <= 5); - Assert.AreEqual("val", (string)syncResult.Value); - Assert.AreEqual(hasExpiry, syncResult.Expiry.HasValue); - if (hasExpiry) Assert.IsTrue(syncResult.Expiry.Value.TotalMinutes >= 4.9 && syncResult.Expiry.Value.TotalMinutes <= 5); - } - else - { - Assert.IsTrue(asyncResult.Value.IsNull); - Assert.IsFalse(asyncResult.Expiry.HasValue); - Assert.IsTrue(syncResult.Value.IsNull); - Assert.IsFalse(syncResult.Expiry.HasValue); - } - } - } - [Test] - public void GetWithExpiryWrongTypeAsync() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(); - RedisKey key = Me(); - db.KeyDelete(key); - db.SetAdd(key, "abc"); - Assert.Throws(() => - { - try - { - var async = db.Wait(db.StringGetWithExpiryAsync(key)); - } - catch (AggregateException ex) - { - throw ex.InnerExceptions[0]; - } - Assert.Fail(); - }, - "A null key is not valid in this context"); - } - } - - [Test] - public void GetWithExpiryWrongTypeSync() - { - Assert.Throws(() => - { - using (var conn = Create()) - { - var db = conn.GetDatabase(); - RedisKey key = Me(); - db.KeyDelete(key); - db.SetAdd(key, "abc"); - db.StringGetWithExpiry(key); - Assert.Fail(); - } - }, - "WRONGTYPE Operation against a key holding the wrong kind of value"); - } - -#if FEATURE_BOOKSLEEVE - [Test] - [TestCase(true, true, ResultCompletionMode.ConcurrentIfContinuation)] - [TestCase(true, false, ResultCompletionMode.ConcurrentIfContinuation)] - [TestCase(false, true, ResultCompletionMode.ConcurrentIfContinuation)] - [TestCase(false, false, ResultCompletionMode.ConcurrentIfContinuation)] - [TestCase(true, true, ResultCompletionMode.Concurrent)] - [TestCase(true, false, ResultCompletionMode.Concurrent)] - [TestCase(false, true, ResultCompletionMode.Concurrent)] - [TestCase(false, false, ResultCompletionMode.Concurrent)] - [TestCase(true, true, ResultCompletionMode.PreserveOrder)] - [TestCase(true, false, ResultCompletionMode.PreserveOrder)] - [TestCase(false, true, ResultCompletionMode.PreserveOrder)] - [TestCase(false, false, ResultCompletionMode.PreserveOrder)] - public void MassiveBulkOpsAsyncOldStyle(bool withContinuation, bool suspendFlush, ResultCompletionMode completionMode) - { - using (var conn = GetOldStyleConnection()) - { - const int db = 0; - string key = "MBOQ"; - conn.CompletionMode = completionMode; - conn.Wait(conn.Server.Ping()); - Action nonTrivial = delegate - { - Thread.SpinWait(5); - }; - var watch = Stopwatch.StartNew(); - - if (suspendFlush) conn.SuspendFlush(); - try - { - - for (int i = 0; i <= AsyncOpsQty; i++) - { - var t = conn.Strings.Set(db, key, i); - if (withContinuation) t.ContinueWith(nonTrivial); - } - } finally - { - if (suspendFlush) conn.ResumeFlush(); - } - int val = (int)conn.Wait(conn.Strings.GetInt64(db, key)); - Assert.AreEqual(AsyncOpsQty, val); - watch.Stop(); - Console.WriteLine("{2}: Time for {0} ops: {1}ms ({3}, {4}, {5}); ops/s: {6}", AsyncOpsQty, watch.ElapsedMilliseconds, Me(), - withContinuation ? "with continuation" : "no continuation", - suspendFlush ? "suspend flush" : "flush at whim", - completionMode, AsyncOpsQty / watch.Elapsed.TotalSeconds); - } - } -#endif - - [Test] - [TestCase(true, 1)] - [TestCase(false, 1)] - [TestCase(true, 5)] - [TestCase(false, 5)] - [TestCase(true, 10)] - [TestCase(false, 10)] - [TestCase(true, 50)] - [TestCase(false, 50)] - public void MassiveBulkOpsSync(bool preserveOrder, int threads) - { - int workPerThread = SyncOpsQty / threads; - using (var muxer = Create()) - { - muxer.PreserveAsyncOrder = preserveOrder; - RedisKey key = "MBOS"; - var conn = muxer.GetDatabase(); - conn.KeyDelete(key); -#if DEBUG - long oldAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount(); - long oldWorkerCount = ConnectionMultiplexer.GetAsyncCompletionWorkerCount(); -#endif - var timeTaken = RunConcurrent(delegate - { - for (int i = 0; i < workPerThread; i++) - { - conn.StringIncrement(key); - } - }, threads); - - int val = (int)conn.StringGet(key); - Assert.AreEqual(workPerThread * threads, val); - Console.WriteLine("{2}: Time for {0} ops on {4} threads: {1}ms ({3}); ops/s: {5}", - threads * workPerThread, timeTaken.TotalMilliseconds, Me() - , preserveOrder ? "preserve order" : "any order", threads, (workPerThread * threads) / timeTaken.TotalSeconds); -#if DEBUG - long newAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount(); - long newWorkerCount = ConnectionMultiplexer.GetAsyncCompletionWorkerCount(); - Console.WriteLine("ResultBox allocations: {0}; workers {1}", newAlloc - oldAlloc, newWorkerCount - oldWorkerCount); - Assert.IsTrue(newAlloc - oldAlloc <= 2 * threads, "number of box allocations"); -#endif - } - } - -#if FEATURE_BOOKSLEEVE - [Test] - [TestCase(ResultCompletionMode.Concurrent, 1)] - [TestCase(ResultCompletionMode.ConcurrentIfContinuation, 1)] - [TestCase(ResultCompletionMode.PreserveOrder, 1)] - [TestCase(ResultCompletionMode.Concurrent, 5)] - [TestCase(ResultCompletionMode.ConcurrentIfContinuation, 5)] - [TestCase(ResultCompletionMode.PreserveOrder, 5)] - [TestCase(ResultCompletionMode.Concurrent, 10)] - [TestCase(ResultCompletionMode.ConcurrentIfContinuation, 10)] - [TestCase(ResultCompletionMode.PreserveOrder, 10)] - [TestCase(ResultCompletionMode.Concurrent, 50)] - [TestCase(ResultCompletionMode.ConcurrentIfContinuation, 50)] - [TestCase(ResultCompletionMode.PreserveOrder, 50)] - public void MassiveBulkOpsSyncOldStyle(ResultCompletionMode completionMode, int threads) - { - int workPerThread = SyncOpsQty / threads; - - using (var conn = GetOldStyleConnection()) - { - const int db = 0; - string key = "MBOQ"; - conn.CompletionMode = completionMode; - conn.Wait(conn.Keys.Remove(db, key)); - - var timeTaken = RunConcurrent(delegate - { - for (int i = 0; i < workPerThread; i++) - { - conn.Wait(conn.Strings.Increment(db, key)); - } - }, threads); - - int val = (int)conn.Wait(conn.Strings.GetInt64(db, key)); - Assert.AreEqual(workPerThread * threads, val); - - Console.WriteLine("{2}: Time for {0} ops on {4} threads: {1}ms ({3}); ops/s: {5}", workPerThread * threads, timeTaken.TotalMilliseconds, Me(), - completionMode, threads, (workPerThread * threads) / timeTaken.TotalSeconds); - } - } -#endif - - [Test] - [TestCase(true, 1)] - [TestCase(false, 1)] - [TestCase(true, 5)] - [TestCase(false, 5)] - public void MassiveBulkOpsFireAndForget(bool preserveOrder, int threads) - { - using (var muxer = Create()) - { - muxer.PreserveAsyncOrder = preserveOrder; -#if DEBUG - long oldAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount(); -#endif - RedisKey key = "MBOF"; - var conn = muxer.GetDatabase(); - conn.Ping(); - - conn.KeyDelete(key, CommandFlags.FireAndForget); - int perThread = AsyncOpsQty / threads; - var elapsed = RunConcurrent(delegate - { - for (int i = 0; i < perThread; i++) - { - conn.StringIncrement(key, flags: CommandFlags.FireAndForget); - } - conn.Ping(); - }, threads); - var val = (long)conn.StringGet(key); - Assert.AreEqual(perThread * threads, val); - - Console.WriteLine("{2}: Time for {0} ops over {5} threads: {1:###,###}ms ({3}); ops/s: {4:###,###,##0}", - val, elapsed.TotalMilliseconds, Me(), - preserveOrder ? "preserve order" : "any order", - val / elapsed.TotalSeconds, threads); -#if DEBUG - long newAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount(); - Console.WriteLine("ResultBox allocations: {0}", - newAlloc - oldAlloc); - Assert.IsTrue(newAlloc - oldAlloc <= 4); -#endif - } - } - - -#if DEBUG - [Test] - [TestCase(true)] - [TestCase(false)] - public void TestQuit(bool preserveOrder) - { - SetExpectedAmbientFailureCount(1); - using (var muxer = Create(allowAdmin: true)) - { - muxer.PreserveAsyncOrder = preserveOrder; - var db = muxer.GetDatabase(); - string key = Guid.NewGuid().ToString(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.StringSet(key, key, flags: CommandFlags.FireAndForget); - GetServer(muxer).Quit(CommandFlags.FireAndForget); - var watch = Stopwatch.StartNew(); - try - { - db.Ping(); - Assert.Fail(); - } - catch (RedisConnectionException) { } - watch.Stop(); - Console.WriteLine("Time to notice quit: {0}ms ({1})", watch.ElapsedMilliseconds, - preserveOrder ? "preserve order" : "any order"); - Thread.Sleep(20); - Debug.WriteLine("Pinging..."); - Assert.AreEqual(key, (string)db.StringGet(key)); - } - } - [Test] - [TestCase(true)] - [TestCase(false)] - public void TestSevered(bool preserveOrder) - { - SetExpectedAmbientFailureCount(2); - using (var muxer = Create(allowAdmin: true)) - { - muxer.PreserveAsyncOrder = preserveOrder; - var db = muxer.GetDatabase(); - string key = Guid.NewGuid().ToString(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.StringSet(key, key, flags: CommandFlags.FireAndForget); - GetServer(muxer).SimulateConnectionFailure(); - var watch = Stopwatch.StartNew(); - db.Ping(); - watch.Stop(); - Console.WriteLine("Time to re-establish: {0}ms ({1})", watch.ElapsedMilliseconds, - preserveOrder ? "preserve order" : "any order"); - Thread.Sleep(20); - Debug.WriteLine("Pinging..."); - Assert.AreEqual(key, (string)db.StringGet(key)); - } - } -#endif - - - - [Test] - [TestCase(true)] - [TestCase(false)] - public void IncrAsync(bool preserveOrder) - { - using (var muxer = Create()) - { - muxer.PreserveAsyncOrder = preserveOrder; - var conn = muxer.GetDatabase(); - RedisKey key = Me(); - conn.KeyDelete(key, CommandFlags.FireAndForget); - var nix = conn.KeyExistsAsync(key); - var a = conn.StringGetAsync(key); - var b = conn.StringIncrementAsync(key); - var c = conn.StringGetAsync(key); - var d = conn.StringIncrementAsync(key, 10); - var e = conn.StringGetAsync(key); - var f = conn.StringDecrementAsync(key, 11); - var g = conn.StringGetAsync(key); - var h = conn.KeyExistsAsync(key); - Assert.IsFalse(muxer.Wait(nix)); - Assert.IsTrue(muxer.Wait(a).IsNull); - Assert.AreEqual(0, (long)muxer.Wait(a)); - Assert.AreEqual(1, muxer.Wait(b)); - Assert.AreEqual(1, (long)muxer.Wait(c)); - Assert.AreEqual(11, muxer.Wait(d)); - Assert.AreEqual(11, (long)muxer.Wait(e)); - Assert.AreEqual(0, muxer.Wait(f)); - Assert.AreEqual(0, (long)muxer.Wait(g)); - Assert.IsTrue(muxer.Wait(h)); - } - } - [Test] - [TestCase(true)] - [TestCase(false)] - public void IncrSync(bool preserveOrder) - { - using (var muxer = Create()) - { - muxer.PreserveAsyncOrder = preserveOrder; - var conn = muxer.GetDatabase(); - RedisKey key = Me(); - conn.KeyDelete(key, CommandFlags.FireAndForget); - var nix = conn.KeyExists(key); - var a = conn.StringGet(key); - var b = conn.StringIncrement(key); - var c = conn.StringGet(key); - var d = conn.StringIncrement(key, 10); - var e = conn.StringGet(key); - var f = conn.StringDecrement(key, 11); - var g = conn.StringGet(key); - var h = conn.KeyExists(key); - Assert.IsFalse(nix); - Assert.IsTrue(a.IsNull); - Assert.AreEqual(0, (long)a); - Assert.AreEqual(1, b); - Assert.AreEqual(1, (long)c); - Assert.AreEqual(11, d); - Assert.AreEqual(11, (long)e); - Assert.AreEqual(0, f); - Assert.AreEqual(0, (long)g); - Assert.IsTrue(h); - } - } - - [Test] - public void IncrDifferentSizes() - { - using (var muxer = Create()) - { - var db = muxer.GetDatabase(); - RedisKey key = Me(); - db.KeyDelete(key, CommandFlags.FireAndForget); - int expected = 0; - Incr(db, key, -129019, ref expected); - Incr(db, key, -10023, ref expected); - Incr(db, key, -9933, ref expected); - Incr(db, key, -23, ref expected); - Incr(db, key, -7, ref expected); - Incr(db, key, -1, ref expected); - Incr(db, key, 0, ref expected); - Incr(db, key, 1, ref expected); - Incr(db, key, 9, ref expected); - Incr(db, key, 11, ref expected); - Incr(db, key, 345, ref expected); - Incr(db, key, 4982, ref expected); - Incr(db, key, 13091, ref expected); - Incr(db, key, 324092, ref expected); - Assert.AreNotEqual(0, expected); - var sum = (long)db.StringGet(key); - Assert.AreEqual(expected, sum); - } - } - - private void Incr(IDatabase database, RedisKey key, int delta, ref int total) - { - database.StringIncrement(key, delta, CommandFlags.FireAndForget); - total += delta; - } - - [Test] - public void WrappedDatabasePrefixIntegration() - { - using (var conn = Create()) - { - var db = conn.GetDatabase().WithKeyPrefix("abc"); - db.KeyDelete("count"); - db.StringIncrement("count"); - db.StringIncrement("count"); - db.StringIncrement("count"); - - int count = (int)conn.GetDatabase().StringGet("abccount"); - Assert.AreEqual(3, count); - } - } - } - -} diff --git a/StackExchange.Redis.Tests/BatchWrapperTests.cs b/StackExchange.Redis.Tests/BatchWrapperTests.cs deleted file mode 100644 index 1e0167ee4..000000000 --- a/StackExchange.Redis.Tests/BatchWrapperTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -#if FEATURE_MOQ -using Moq; -using NUnit.Framework; -using StackExchange.Redis.KeyspaceIsolation; -using System.Text; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public sealed class BatchWrapperTests - { - private Mock mock; - private BatchWrapper wrapper; - - //[TestFixtureSetUp] - [OneTimeSetUpAttribute] - public void Initialize() - { - mock = new Mock(); - wrapper = new BatchWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:")); - } - - [Test] - public void Execute() - { - wrapper.Execute(); - mock.Verify(_ => _.Execute(), Times.Once()); - } - } -} -#endif \ No newline at end of file diff --git a/StackExchange.Redis.Tests/Bits.cs b/StackExchange.Redis.Tests/Bits.cs deleted file mode 100644 index 91a470a40..000000000 --- a/StackExchange.Redis.Tests/Bits.cs +++ /dev/null @@ -1,23 +0,0 @@ -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Bits : TestBase - { - [Test] - public void BasicOps() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(); - RedisKey key = Me(); - - db.KeyDelete(key, CommandFlags.FireAndForget); - db.StringSetBit(key, 10, true); - Assert.True(db.StringGetBit(key, 10)); - Assert.False(db.StringGetBit(key, 11)); - } - } - } -} \ No newline at end of file diff --git a/StackExchange.Redis.Tests/Cluster.cs b/StackExchange.Redis.Tests/Cluster.cs deleted file mode 100644 index b08e396a6..000000000 --- a/StackExchange.Redis.Tests/Cluster.cs +++ /dev/null @@ -1,673 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Cluster : TestBase - { - //private const string ClusterIp = "192.168.0.15"; // marc - //private const string ClusterIp = "10.110.11.102"; // kmontrose - private const string ClusterIp = "127.0.0.1"; - private const int ServerCount = 6, FirstPort = 7000; - - protected override string GetConfiguration() - { - var server = ClusterIp; - if (string.Equals(Environment.MachineName, "MARC-LAPTOP", StringComparison.OrdinalIgnoreCase)) - { - server = "192.168.56.101"; - } - return string.Join(",", - from port in Enumerable.Range(FirstPort, ServerCount) - select server + ":" + port) + ",connectTimeout=10000"; - } - - [Test] - public void ExportConfiguration() - { - if (File.Exists("cluster.zip")) File.Delete("cluster.zip"); - Assert.IsFalse(File.Exists("cluster.zip")); - using (var muxer = Create(allowAdmin: true)) - using(var file = File.Create("cluster.zip")) - { - muxer.ExportConfiguration(file); - } - Assert.IsTrue(File.Exists("cluster.zip")); - } - - [Test] - public void ConnectUsesSingleSocket() - { - for(int i = 0; i<10;i++) - { - using (var muxer = Create(failMessage: i + ": ")) - { - var eps = muxer.GetEndPoints(); - foreach (var ep in eps) - { - var srv = muxer.GetServer(ep); - var counters = srv.GetCounters(); - Assert.AreEqual(1, counters.Interactive.SocketCount, i + "; interactive, " + ep.ToString()); - Assert.AreEqual(1, counters.Subscription.SocketCount, i + "; subscription, " + ep.ToString()); - } - } - } - } - - - [Test] - public void CanGetTotalStats() - { - using(var muxer = Create()) - { - var counters = muxer.GetCounters(); - Console.WriteLine(counters); - } - } - - [Test] - public void Connect() - { - using (var muxer = Create()) - { - var endpoints = muxer.GetEndPoints(); - - Assert.AreEqual(ServerCount, endpoints.Length); - var expectedPorts = new HashSet(Enumerable.Range(FirstPort, ServerCount)); - int masters = 0, slaves = 0; - var failed = new List(); - foreach (var endpoint in endpoints) - { - var server = muxer.GetServer(endpoint); - if (!server.IsConnected) - { - failed.Add(endpoint); - } - Assert.AreEqual(endpoint, server.EndPoint, "endpoint:" + endpoint); - Assert.IsInstanceOf(endpoint, "endpoint-type:" + endpoint); - Assert.IsTrue(expectedPorts.Remove(((IPEndPoint)endpoint).Port), "port:" + endpoint); - Assert.AreEqual(ServerType.Cluster, server.ServerType, "server-type:" + endpoint); - if (server.IsSlave) slaves++; - else masters++; - } - if (failed.Count != 0) - { - Console.WriteLine("{0} failues", failed.Count); - foreach (var fail in failed) - { - Console.WriteLine(fail); - } - Assert.Fail("not all servers connected"); - } - - Assert.AreEqual(ServerCount / 2, slaves, "slaves"); - Assert.AreEqual(ServerCount / 2, masters, "masters"); - - } - } - - [Test] - public void TestIdentity() - { - using(var conn = Create()) - { - RedisKey key = Guid.NewGuid().ToByteArray(); - var ep = conn.GetDatabase().IdentifyEndpoint(key); - Assert.AreEqual(ep, conn.GetServer(ep).ClusterConfiguration.GetBySlot(key).EndPoint); - } - } - - [Test] - public void IntentionalWrongServer() - { - using (var conn = Create()) - { - var endpoints = conn.GetEndPoints(); - var servers = endpoints.Select(e => conn.GetServer(e)); - - var key = Me(); - const string value = "abc"; - var db = conn.GetDatabase(); - db.KeyDelete(key); - db.StringSet(key, value); - servers.First().Ping(); - var config = servers.First().ClusterConfiguration; - Assert.IsNotNull(config); - int slot = conn.HashSlot(key); - var rightMasterNode = config.GetBySlot(key); - Assert.IsNotNull(rightMasterNode); - - - -#if DEBUG - string a = conn.GetServer(rightMasterNode.EndPoint).StringGet(db.Database, key); - Assert.AreEqual(value, a, "right master"); - - var node = config.Nodes.FirstOrDefault(x => !x.IsSlave && x.NodeId != rightMasterNode.NodeId); - Assert.IsNotNull(node); - if (node != null) - { - string b = conn.GetServer(node.EndPoint).StringGet(db.Database, key); - Assert.AreEqual(value, b, "wrong master, allow redirect"); - - try - { - string c = conn.GetServer(node.EndPoint).StringGet(db.Database, key, CommandFlags.NoRedirect); - Assert.Fail("wrong master, no redirect"); - } catch (RedisServerException ex) - { - Assert.AreEqual("MOVED " + slot + " " + rightMasterNode.EndPoint.ToString(), ex.Message, "wrong master, no redirect"); - } - } - - node = config.Nodes.FirstOrDefault(x => x.IsSlave && x.ParentNodeId == rightMasterNode.NodeId); - Assert.IsNotNull(node); - if (node != null) - { - string d = conn.GetServer(node.EndPoint).StringGet(db.Database, key); - Assert.AreEqual(value, d, "right slave"); - } - - node = config.Nodes.FirstOrDefault(x => x.IsSlave && x.ParentNodeId != rightMasterNode.NodeId); - Assert.IsNotNull(node); - if (node != null) - { - string e = conn.GetServer(node.EndPoint).StringGet(db.Database, key); - Assert.AreEqual(value, e, "wrong slave, allow redirect"); - - try - { - string f = conn.GetServer(node.EndPoint).StringGet(db.Database, key, CommandFlags.NoRedirect); - Assert.Fail("wrong slave, no redirect"); - } - catch (RedisServerException ex) - { - Assert.AreEqual("MOVED " + slot + " " + rightMasterNode.EndPoint.ToString(), ex.Message, "wrong slave, no redirect"); - } - } -#endif - - } - } - - [Test] - public void TransactionWithMultiServerKeys() - { - Assert.Throws(() => - { - using (var muxer = Create()) - { - // connect - var cluster = muxer.GetDatabase(); - var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]); - anyServer.Ping(); - Assert.AreEqual(ServerType.Cluster, anyServer.ServerType); - var config = anyServer.ClusterConfiguration; - Assert.IsNotNull(config); - - // invent 2 keys that we believe are served by different nodes - string x = Guid.NewGuid().ToString(), y; - var xNode = config.GetBySlot(x); - int abort = 1000; - do - { - y = Guid.NewGuid().ToString(); - } while (--abort > 0 && config.GetBySlot(y) == xNode); - if (abort == 0) Assert.Inconclusive("failed to find a different node to use"); - var yNode = config.GetBySlot(y); - Console.WriteLine("x={0}, served by {1}", x, xNode.NodeId); - Console.WriteLine("y={0}, served by {1}", y, yNode.NodeId); - Assert.AreNotEqual(xNode.NodeId, yNode.NodeId, "same node"); - - // wipe those keys - cluster.KeyDelete(x, CommandFlags.FireAndForget); - cluster.KeyDelete(y, CommandFlags.FireAndForget); - - // create a transaction that attempts to assign both keys - var tran = cluster.CreateTransaction(); - tran.AddCondition(Condition.KeyNotExists(x)); - tran.AddCondition(Condition.KeyNotExists(y)); - var setX = tran.StringSetAsync(x, "x-val"); - var setY = tran.StringSetAsync(y, "y-val"); - bool success = tran.Execute(); - - Assert.Fail("Expected single-slot rules to apply"); - // the rest no longer applies while we are following single-slot rules - - //// check that everything was aborted - //Assert.IsFalse(success, "tran aborted"); - //Assert.IsTrue(setX.IsCanceled, "set x cancelled"); - //Assert.IsTrue(setY.IsCanceled, "set y cancelled"); - //var existsX = cluster.KeyExistsAsync(x); - //var existsY = cluster.KeyExistsAsync(y); - //Assert.IsFalse(cluster.Wait(existsX), "x exists"); - //Assert.IsFalse(cluster.Wait(existsY), "y exists"); - } - }, - "Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot"); - } - - [Test] - public void TransactionWithSameServerKeys() - { - Assert.Throws(() => - { - using (var muxer = Create()) - { - // connect - var cluster = muxer.GetDatabase(); - var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]); - anyServer.Ping(); - var config = anyServer.ClusterConfiguration; - Assert.IsNotNull(config); - - // invent 2 keys that we believe are served by different nodes - string x = Guid.NewGuid().ToString(), y; - var xNode = config.GetBySlot(x); - int abort = 1000; - do - { - y = Guid.NewGuid().ToString(); - } while (--abort > 0 && config.GetBySlot(y) != xNode); - if (abort == 0) Assert.Inconclusive("failed to find a key with the same node to use"); - var yNode = config.GetBySlot(y); - Console.WriteLine("x={0}, served by {1}", x, xNode.NodeId); - Console.WriteLine("y={0}, served by {1}", y, yNode.NodeId); - Assert.AreEqual(xNode.NodeId, yNode.NodeId, "same node"); - - // wipe those keys - cluster.KeyDelete(x, CommandFlags.FireAndForget); - cluster.KeyDelete(y, CommandFlags.FireAndForget); - - // create a transaction that attempts to assign both keys - var tran = cluster.CreateTransaction(); - tran.AddCondition(Condition.KeyNotExists(x)); - tran.AddCondition(Condition.KeyNotExists(y)); - var setX = tran.StringSetAsync(x, "x-val"); - var setY = tran.StringSetAsync(y, "y-val"); - bool success = tran.Execute(); - - Assert.Fail("Expected single-slot rules to apply"); - // the rest no longer applies while we are following single-slot rules - - //// check that everything was aborted - //Assert.IsTrue(success, "tran aborted"); - //Assert.IsFalse(setX.IsCanceled, "set x cancelled"); - //Assert.IsFalse(setY.IsCanceled, "set y cancelled"); - //var existsX = cluster.KeyExistsAsync(x); - //var existsY = cluster.KeyExistsAsync(y); - //Assert.IsTrue(cluster.Wait(existsX), "x exists"); - //Assert.IsTrue(cluster.Wait(existsY), "y exists"); - } - }, - "Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot"); - } - - [Test] - public void TransactionWithSameSlotKeys() - { - using (var muxer = Create()) - { - // connect - var cluster = muxer.GetDatabase(); - var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]); - anyServer.Ping(); - var config = anyServer.ClusterConfiguration; - Assert.IsNotNull(config); - - // invent 2 keys that we believe are in the same slot - var guid = Guid.NewGuid().ToString(); - string x = "/{" + guid + "}/foo", y = "/{" + guid + "}/bar"; - - Assert.AreEqual(muxer.HashSlot(x), muxer.HashSlot(y)); - var xNode = config.GetBySlot(x); - var yNode = config.GetBySlot(y); - Console.WriteLine("x={0}, served by {1}", x, xNode.NodeId); - Console.WriteLine("y={0}, served by {1}", y, yNode.NodeId); - Assert.AreEqual(xNode.NodeId, yNode.NodeId, "same node"); - - // wipe those keys - cluster.KeyDelete(x, CommandFlags.FireAndForget); - cluster.KeyDelete(y, CommandFlags.FireAndForget); - - // create a transaction that attempts to assign both keys - var tran = cluster.CreateTransaction(); - tran.AddCondition(Condition.KeyNotExists(x)); - tran.AddCondition(Condition.KeyNotExists(y)); - var setX = tran.StringSetAsync(x, "x-val"); - var setY = tran.StringSetAsync(y, "y-val"); - bool success = tran.Execute(); - - // check that everything was aborted - Assert.IsTrue(success, "tran aborted"); - Assert.IsFalse(setX.IsCanceled, "set x cancelled"); - Assert.IsFalse(setY.IsCanceled, "set y cancelled"); - var existsX = cluster.KeyExistsAsync(x); - var existsY = cluster.KeyExistsAsync(y); - Assert.IsTrue(cluster.Wait(existsX), "x exists"); - Assert.IsTrue(cluster.Wait(existsY), "y exists"); - } - } - - [Test] - [TestCase(null, 10)] - [TestCase(null, 100)] - [TestCase("abc", 10)] - [TestCase("abc", 100)] - - public void Keys(string pattern, int pageSize) - { - using (var conn = Create(allowAdmin: true)) - { - var cluster = conn.GetDatabase(); - var server = conn.GetEndPoints().Select(x => conn.GetServer(x)).First(x => !x.IsSlave); - server.FlushAllDatabases(); - try - { - Assert.IsFalse(server.Keys(pattern: pattern, pageSize: pageSize).Any()); - Console.WriteLine("Complete: '{0}' / {1}", pattern, pageSize); - } catch - { - Console.WriteLine("Failed: '{0}' / {1}", pattern, pageSize); - throw; - } - } - } - - [Test] - [TestCase("", 0)] - [TestCase("abc", 7638)] - [TestCase("{abc}", 7638)] - [TestCase("abcdef", 15101)] - [TestCase("abc{abc}def", 7638)] - [TestCase("c", 7365)] - [TestCase("g", 7233)] - [TestCase("d", 11298)] - - [TestCase("user1000", 3443)] - [TestCase("{user1000}", 3443)] - [TestCase("abc{user1000}", 3443)] - [TestCase("abc{user1000}def", 3443)] - [TestCase("{user1000}.following", 3443)] - [TestCase("{user1000}.followers", 3443)] - - [TestCase("foo{}{bar}", 8363)] - - [TestCase("foo{{bar}}zap", 4015)] - [TestCase("{bar", 4015)] - - [TestCase("foo{bar}{zap}", 5061)] - [TestCase("bar", 5061)] - - public void HashSlots(string key, int slot) - { - using(var muxer = Create(connectTimeout: 500, pause: false)) - { - Assert.AreEqual(slot, muxer.HashSlot(key)); - } - } - - - [Test] - public void SScan() - { - using (var conn = Create()) - { - RedisKey key = "a"; - var db = conn.GetDatabase(); - db.KeyDelete(key); - - int totalUnfiltered = 0, totalFiltered = 0; - for (int i = 0; i < 1000; i++) - { - db.SetAdd(key, i); - totalUnfiltered += i; - if (i.ToString().Contains("3")) totalFiltered += i; - } - var unfilteredActual = db.SetScan(key).Select(x => (int)x).Sum(); - var filteredActual = db.SetScan(key, "*3*").Select(x => (int)x).Sum(); - Assert.AreEqual(totalUnfiltered, unfilteredActual); - Assert.AreEqual(totalFiltered, filteredActual); - } - } - - [Test] - public void GetConfig() - { - using(var muxer = Create(allowAdmin: true)) - { - var endpoints = muxer.GetEndPoints(); - var server = muxer.GetServer(endpoints.First()); - var nodes = server.ClusterNodes(); - - Assert.AreEqual(endpoints.Length, nodes.Nodes.Count); - foreach(var node in nodes.Nodes.OrderBy(x => x)) - { - Console.WriteLine(node); - } - } - } - - [Test] - public void AccessRandomKeys() - { - using(var conn = Create(allowAdmin: true)) - { - - var cluster = conn.GetDatabase(); - int slotMovedCount = 0; - conn.HashSlotMoved += (s, a) => - { - Console.WriteLine("{0} moved from {1} to {2}", a.HashSlot, Describe(a.OldEndPoint), Describe(a.NewEndPoint)); - Interlocked.Increment(ref slotMovedCount); - }; - var pairs = new Dictionary(); - const int COUNT = 500; - Task[] send = new Task[COUNT]; - int index = 0; - - var servers = conn.GetEndPoints().Select(x => conn.GetServer(x)); - foreach (var server in servers) - { - if (!server.IsSlave) - { - server.Ping(); - server.FlushAllDatabases(); - } - } - - for(int i = 0; i < COUNT; i++) - { - var key = Guid.NewGuid().ToString(); - var value = Guid.NewGuid().ToString(); - pairs.Add(key, value); - send[index++] = cluster.StringSetAsync(key, value); - } - conn.WaitAll(send); - - var expected = new string[COUNT]; - var actual = new Task[COUNT]; - index = 0; - foreach (var pair in pairs) - { - expected[index] = pair.Value; - actual[index] = cluster.StringGetAsync(pair.Key); - index++; - } - cluster.WaitAll(actual); - for(int i = 0; i < COUNT; i++) - { - Assert.AreEqual(expected[i], (string)actual[i].Result); - } - - int total = 0; - Parallel.ForEach(servers, server => - { - if (!server.IsSlave) - { - int count = server.Keys(pageSize: 100).Count(); - Console.WriteLine("{0} has {1} keys", server.EndPoint, count); - Interlocked.Add(ref total, count); - } - }); - - foreach (var server in servers) - { - var counters = server.GetCounters(); - Console.WriteLine(counters); - } - int final = Interlocked.CompareExchange(ref total, 0, 0); - Assert.AreEqual(COUNT, final); - Assert.AreEqual(0, Interlocked.CompareExchange(ref slotMovedCount, 0, 0), "slot moved count"); - } - } - - [Test] - [TestCase(CommandFlags.DemandMaster, false)] - [TestCase(CommandFlags.DemandSlave, true)] - [TestCase(CommandFlags.PreferMaster, false)] - [TestCase(CommandFlags.PreferSlave, true)] - public void GetFromRightNodeBasedOnFlags(CommandFlags flags, bool isSlave) - { - using(var muxer = Create(allowAdmin: true)) - { - var db = muxer.GetDatabase(); - for(int i = 0; i < 1000; i++) - { - var key = Guid.NewGuid().ToString(); - var endpoint = db.IdentifyEndpoint(key, flags); - var server = muxer.GetServer(endpoint); - Assert.AreEqual(isSlave, server.IsSlave, key); - } - } - } - - private static string Describe(EndPoint endpoint) - { - return endpoint?.ToString() ?? "(unknown)"; - } - - class TestProfiler : IProfiler - { - public object MyContext = new object(); - - public object GetContext() - { - return MyContext; - } - } - - [Test] - public void SimpleProfiling() - { - using (var conn = Create()) - { - var profiler = new TestProfiler(); - - conn.RegisterProfiler(profiler); - conn.BeginProfiling(profiler.MyContext); - var db = conn.GetDatabase(); - db.StringSet("hello", "world"); - var val = db.StringGet("hello"); - Assert.AreEqual("world", (string)val); - - var msgs = conn.FinishProfiling(profiler.MyContext); - Assert.AreEqual(2, msgs.Count()); - Assert.IsTrue(msgs.Any(m => m.Command == "GET")); - Assert.IsTrue(msgs.Any(m => m.Command == "SET")); - } - } - -#if DEBUG - [Test] - public void MovedProfiling() - { - const string Key = "redirected-key"; - const string Value = "redirected-value"; - - var profiler = new TestProfiler(); - - using (var conn = Create()) - { - conn.RegisterProfiler(profiler); - - var endpoints = conn.GetEndPoints(); - var servers = endpoints.Select(e => conn.GetServer(e)); - - conn.BeginProfiling(profiler.MyContext); - var db = conn.GetDatabase(); - db.KeyDelete(Key); - db.StringSet(Key, Value); - var config = servers.First().ClusterConfiguration; - Assert.IsNotNull(config); - - int slot = conn.HashSlot(Key); - var rightMasterNode = config.GetBySlot(Key); - Assert.IsNotNull(rightMasterNode); - - string a = conn.GetServer(rightMasterNode.EndPoint).StringGet(db.Database, Key); - Assert.AreEqual(Value, a, "right master"); - - var wrongMasterNode = config.Nodes.FirstOrDefault(x => !x.IsSlave && x.NodeId != rightMasterNode.NodeId); - Assert.IsNotNull(wrongMasterNode); - - string b = conn.GetServer(wrongMasterNode.EndPoint).StringGet(db.Database, Key); - Assert.AreEqual(Value, b, "wrong master, allow redirect"); - - var msgs = conn.FinishProfiling(profiler.MyContext).ToList(); - - // verify that things actually got recorded properly, and the retransmission profilings are connected as expected - { - // expect 1 DEL, 1 SET, 1 GET (to right master), 1 GET (to wrong master) that was responded to by an ASK, and 1 GET (to right master or a slave of it) - Assert.AreEqual(5, msgs.Count); - Assert.AreEqual(1, msgs.Count(c => c.Command == "DEL")); - Assert.AreEqual(1, msgs.Count(c => c.Command == "SET")); - Assert.AreEqual(3, msgs.Count(c => c.Command == "GET")); - - var toRightMasterNotRetransmission = msgs.Where(m => m.Command == "GET" && m.EndPoint.Equals(rightMasterNode.EndPoint) && m.RetransmissionOf == null); - Assert.AreEqual(1, toRightMasterNotRetransmission.Count()); - - var toWrongMasterWithoutRetransmission = msgs.Where(m => m.Command == "GET" && m.EndPoint.Equals(wrongMasterNode.EndPoint) && m.RetransmissionOf == null); - Assert.AreEqual(1, toWrongMasterWithoutRetransmission.Count()); - - var toRightMasterOrSlaveAsRetransmission = msgs.Where(m => m.Command == "GET" && (m.EndPoint.Equals(rightMasterNode.EndPoint) || rightMasterNode.Children.Any(c => m.EndPoint.Equals(c.EndPoint))) && m.RetransmissionOf != null); - Assert.AreEqual(1, toRightMasterOrSlaveAsRetransmission.Count()); - - var originalWrongMaster = toWrongMasterWithoutRetransmission.Single(); - var retransmissionToRight = toRightMasterOrSlaveAsRetransmission.Single(); - - Assert.IsTrue(object.ReferenceEquals(originalWrongMaster, retransmissionToRight.RetransmissionOf)); - } - - foreach(var msg in msgs) - { - Assert.IsTrue(msg.CommandCreated != default(DateTime)); - Assert.IsTrue(msg.CreationToEnqueued > TimeSpan.Zero); - Assert.IsTrue(msg.EnqueuedToSending > TimeSpan.Zero); - Assert.IsTrue(msg.SentToResponse > TimeSpan.Zero); - Assert.IsTrue(msg.ResponseToCompletion > TimeSpan.Zero); - Assert.IsTrue(msg.ElapsedTime > TimeSpan.Zero); - - if (msg.RetransmissionOf != null) - { - // imprecision of DateTime.UtcNow makes this pretty approximate - Assert.IsTrue(msg.RetransmissionOf.CommandCreated <= msg.CommandCreated); - Assert.AreEqual(RetransmissionReasonType.Moved, msg.RetransmissionReason.Value); - } - else - { - Assert.IsFalse(msg.RetransmissionReason.HasValue); - } - } - } - } -#endif - } -} diff --git a/StackExchange.Redis.Tests/Commands.cs b/StackExchange.Redis.Tests/Commands.cs deleted file mode 100644 index a255ac982..000000000 --- a/StackExchange.Redis.Tests/Commands.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Net; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Commands - { - [Test] - public void Basic() - { - var config = ConfigurationOptions.Parse(".,$PING=p"); - Assert.AreEqual(1, config.EndPoints.Count); - config.SetDefaultPorts(); - Assert.Contains(new DnsEndPoint(".",6379), config.EndPoints); - var map = config.CommandMap; - Assert.AreEqual("$PING=p", map.ToString()); - Assert.AreEqual(".:6379,$PING=p", config.ToString()); - } - } -} diff --git a/StackExchange.Redis.Tests/Config.cs b/StackExchange.Redis.Tests/Config.cs deleted file mode 100644 index 812c0cb0f..000000000 --- a/StackExchange.Redis.Tests/Config.cs +++ /dev/null @@ -1,338 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Config : TestBase - { - - [Test] - public void VerifyReceiveConfigChangeBroadcast() - { - - var config = this.GetConfiguration(); - using (var sender = Create(allowAdmin: true)) - using (var receiver = Create(syncTimeout: 2000)) - { - int total = 0; - receiver.ConfigurationChangedBroadcast += (s, a) => - { - Console.WriteLine("Config changed: " + (a.EndPoint == null ? "(none)" : a.EndPoint.ToString())); - Interlocked.Increment(ref total); - }; - Thread.Sleep(500); - // send a reconfigure/reconnect message - long count = sender.PublishReconfigure(); - GetServer(receiver).Ping(); - GetServer(receiver).Ping(); - Assert.IsTrue(count == -1 || count >= 2, "subscribers"); - Assert.IsTrue(Interlocked.CompareExchange(ref total, 0, 0) >= 1, "total (1st)"); - - Interlocked.Exchange(ref total, 0); - - // and send a second time via a re-master operation - var server = GetServer(sender); - if (server.IsSlave) Assert.Inconclusive("didn't expect a slave"); - server.MakeMaster(ReplicationChangeOptions.Broadcast); - Thread.Sleep(100); - GetServer(receiver).Ping(); - GetServer(receiver).Ping(); - Assert.IsTrue(Interlocked.CompareExchange(ref total, 0, 0) >= 1, "total (2nd)"); - } - } - - [Test] - public void TalkToNonsenseServer() - { - var config = new ConfigurationOptions - { - AbortOnConnectFail = false, - EndPoints = - { - { "127.0.0.1:1234" } - }, - ConnectTimeout = 200 - }; - var log = new StringWriter(); - using(var conn = ConnectionMultiplexer.Connect(config, log)) - { - Console.WriteLine(log); - Assert.IsFalse(conn.IsConnected); - } - } - - [Test] - public void TestManaulHeartbeat() - { - using (var muxer = Create(keepAlive: 2)) - { - var conn = muxer.GetDatabase(); - conn.Ping(); - - var before = muxer.OperationCount; - - Console.WriteLine("sleeping to test heartbeat..."); - Thread.Sleep(TimeSpan.FromSeconds(5)); - - var after = muxer.OperationCount; - - Assert.IsTrue(after >= before + 4); - - } - } - - [Test] - [TestCase(0)] - [TestCase(10)] - [TestCase(100)] - [TestCase(200)] - public void GetSlowlog(int count) - { - using(var muxer = Create(allowAdmin: true)) - { - var rows = GetServer(muxer).SlowlogGet(count); - Assert.IsNotNull(rows); - } - } - [Test] - public void ClearSlowlog() - { - using (var muxer = Create(allowAdmin: true)) - { - GetServer(muxer).SlowlogReset(); - } - } - - [Test] - public void ClientName() - { - using (var muxer = Create(clientName: "Test Rig", allowAdmin: true)) - { - Assert.AreEqual("Test Rig", muxer.ClientName); - - var conn = muxer.GetDatabase(); - conn.Ping(); -#if DEBUG - var name = GetServer(muxer).ClientGetName(); - Assert.AreEqual("TestRig", name); -#endif - } - } - - [Test] - public void DefaultClientName() - { - using (var muxer = Create(allowAdmin: true)) - { - Assert.AreEqual(Environment.MachineName, muxer.ClientName); - var conn = muxer.GetDatabase(); - conn.Ping(); -#if DEBUG - var name = GetServer(muxer).ClientGetName(); - Assert.AreEqual(Environment.MachineName, name); -#endif - } - } - - [Test] - public void ReadConfigWithConfigDisabled() - { - using (var muxer = Create(allowAdmin: true, disabledCommands: new[] { "config", "info" })) - { - var conn = GetServer(muxer); - Assert.Throws(() => - { - var all = conn.ConfigGet(); - }, - "This operation has been disabled in the command-map and cannot be used: CONFIG"); - } - } - [Test] - public void ReadConfig() - { - using (var muxer = Create(allowAdmin: true)) - { - Console.WriteLine("about to get config"); - var conn = GetServer(muxer); - var all = conn.ConfigGet(); - Assert.IsTrue(all.Length > 0, "any"); - -#if !CORE_CLR - var pairs = all.ToDictionary(x => (string)x.Key, x => (string)x.Value, StringComparer.InvariantCultureIgnoreCase); -#else - var pairs = all.ToDictionary(x => (string)x.Key, x => (string)x.Value, StringComparer.OrdinalIgnoreCase); -#endif - - Assert.AreEqual(all.Length, pairs.Count); - Assert.IsTrue(pairs.ContainsKey("timeout"), "timeout"); - var val = int.Parse(pairs["timeout"]); - - Assert.IsTrue(pairs.ContainsKey("port"), "port"); - val = int.Parse(pairs["port"]); - Assert.AreEqual(PrimaryPort, val); - } - } - - [Test] - public async System.Threading.Tasks.Task TestConfigureAsync() - { - using(var muxer = Create()) - { - Thread.Sleep(1000); - Debug.WriteLine("About to reconfigure....."); - await muxer.ConfigureAsync().ConfigureAwait(false); - Debug.WriteLine("Reconfigured"); - } - } - [Test] - public void TestConfigureSync() - { - using (var muxer = Create()) - { - Thread.Sleep(1000); - Debug.WriteLine("About to reconfigure....."); - muxer.Configure(); - Debug.WriteLine("Reconfigured"); - } - } - - [Test] - public void GetTime() - { - using (var muxer = Create()) - { - var server = GetServer(muxer); - var serverTime = server.Time(); - Console.WriteLine(serverTime); - var delta = Math.Abs((DateTime.UtcNow - serverTime).TotalSeconds); - - Assert.IsTrue(delta < 5); - } - } - - [Test] - public void DebugObject() - { - using (var muxer = Create(allowAdmin: true)) - { - var db = muxer.GetDatabase(); - RedisKey key = Me(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.StringIncrement(key, flags: CommandFlags.FireAndForget); - var debug = (string)db.DebugObject(key); - Assert.IsNotNull(debug); - Assert.IsTrue(debug.Contains("encoding:int serializedlength:2")); - } - } - - [Test] - public void GetInfo() - { - using(var muxer = Create(allowAdmin: true)) - { - var server = GetServer(muxer); - var info1 = server.Info(); - Assert.IsTrue(info1.Length > 5); - Console.WriteLine("All sections"); - foreach(var group in info1) - { - Console.WriteLine(group.Key); - } - var first = info1.First(); - Console.WriteLine("Full info for: " + first.Key); - foreach (var setting in first) - { - Console.WriteLine("{0} ==> {1}", setting.Key, setting.Value); - } - - var info2 = server.Info("cpu"); - Assert.AreEqual(1, info2.Length); - var cpu = info2.Single(); - Assert.IsTrue(cpu.Count() > 2); - Assert.AreEqual("CPU", cpu.Key); - Assert.IsTrue(cpu.Any(x => x.Key == "used_cpu_sys")); - Assert.IsTrue(cpu.Any(x => x.Key == "used_cpu_user")); - } - } - - [Test] - public void GetInfoRaw() - { - using (var muxer = Create(allowAdmin: true)) - { - var server = GetServer(muxer); - var info = server.InfoRaw(); - Assert.IsTrue(info.Contains("used_cpu_sys")); - Assert.IsTrue(info.Contains("used_cpu_user")); - } - } - - [Test] - public void GetClients() - { - var name = Guid.NewGuid().ToString(); - using (var muxer = Create(clientName: name, allowAdmin: true)) - { - var server = GetServer(muxer); - var clients = server.ClientList(); - Assert.IsTrue(clients.Length > 0, "no clients"); // ourselves! - Assert.IsTrue(clients.Any(x => x.Name == name), "expected: " + name); - } - } - - [Test] - public void SlowLog() - { - using (var muxer = Create(allowAdmin: true)) - { - var server = GetServer(muxer); - var slowlog = server.SlowlogGet(); - server.SlowlogReset(); - } - } - - [Test] - public void TestAutomaticHeartbeat() - { - RedisValue oldTimeout = RedisValue.Null; - using (var configMuxer = Create(allowAdmin: true)) - { - try - { - var conn = configMuxer.GetDatabase(); - var srv = GetServer(configMuxer); - oldTimeout = srv.ConfigGet("timeout")[0].Value; - srv.ConfigSet("timeout", 5); - - using(var innerMuxer = Create()) - { - var innerConn = innerMuxer.GetDatabase(); - innerConn.Ping(); // need to wait to pick up configuration etc - - var before = innerMuxer.OperationCount; - - Console.WriteLine("sleeping to test heartbeat..."); - Thread.Sleep(TimeSpan.FromSeconds(8)); - - var after = innerMuxer.OperationCount; - Assert.IsTrue(after >= before + 4); - - } - } - finally - { - if(!oldTimeout.IsNull) - { - var srv = GetServer(configMuxer); - srv.ConfigSet("timeout", oldTimeout); - } - } - } - } - } -} diff --git a/StackExchange.Redis.Tests/ConnectFailTimeout.cs b/StackExchange.Redis.Tests/ConnectFailTimeout.cs deleted file mode 100644 index afe63b99f..000000000 --- a/StackExchange.Redis.Tests/ConnectFailTimeout.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Threading; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class ConnectFailTimeout : TestBase - { -#if DEBUG - [TestCase] - public void NoticesConnectFail() - { - SetExpectedAmbientFailureCount(-1); - using (var conn = Create(allowAdmin: true)) - { - var server = conn.GetServer(conn.GetEndPoints()[0]); - conn.IgnoreConnect = true; - conn.ConnectionFailed += (s,a) => { - System.Console.WriteLine("Disconnected: " + EndPointCollection.ToString(a.EndPoint)); - }; - conn.ConnectionRestored += (s,a) => { - System.Console.WriteLine("Reconnected: " + EndPointCollection.ToString(a.EndPoint)); - }; - server.SimulateConnectionFailure(); - Thread.Sleep(2000); - try - { - server.Ping(); - Assert.Fail("Did not expect PING to succeed"); - } catch(RedisConnectionException) { /* expected */ } - - conn.IgnoreConnect = false; - Thread.Sleep(2000); - var time = server.Ping(); - System.Console.WriteLine(time); - } - } -#endif - } -} diff --git a/StackExchange.Redis.Tests/ConnectToUnexistingHost.cs b/StackExchange.Redis.Tests/ConnectToUnexistingHost.cs deleted file mode 100644 index 798176736..000000000 --- a/StackExchange.Redis.Tests/ConnectToUnexistingHost.cs +++ /dev/null @@ -1,51 +0,0 @@ -using NUnit.Framework; -using System.Diagnostics; -using System.Threading; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class ConnectToUnexistingHost : TestBase - { -#if DEBUG - [Test] - [TestCase(CompletionType.Any)] - [TestCase(CompletionType.Sync)] - [TestCase(CompletionType.Async)] - public void ConnectToUnexistingHostFailsWithinTimeout(CompletionType completionType) - { - var sw = Stopwatch.StartNew(); - - try - { - var config = new ConfigurationOptions - { - EndPoints = { { "invalid", 1234 } }, - ConnectTimeout = 1000 - }; - - SocketManager.ConnectCompletionType = completionType; - - using (var muxer = ConnectionMultiplexer.Connect(config)) - { - Thread.Sleep(10000); - } - - Assert.Fail("Connect should fail with RedisConnectionException exception"); - } - catch (RedisConnectionException) - { - var elapsed = sw.ElapsedMilliseconds; - if (elapsed > 9000) - { - Assert.Fail("Connect should fail within ConnectTimeout"); - } - } - finally - { - SocketManager.ConnectCompletionType = CompletionType.Any; - } - } -#endif - } -} \ No newline at end of file diff --git a/StackExchange.Redis.Tests/ConnectingFailDetection.cs b/StackExchange.Redis.Tests/ConnectingFailDetection.cs deleted file mode 100644 index e3e218f50..000000000 --- a/StackExchange.Redis.Tests/ConnectingFailDetection.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Threading; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class ConnectingFailDetection : TestBase - { -#if DEBUG - [Test] - public void FastNoticesFailOnConnectingSync() - { - try - { - using (var muxer = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true)) - { - var conn = muxer.GetDatabase(); - conn.Ping(); - - var server = muxer.GetServer(muxer.GetEndPoints()[0]); - - muxer.AllowConnect = false; - SocketManager.ConnectCompletionType = CompletionType.Sync; - - server.SimulateConnectionFailure(); - - Assert.IsFalse(muxer.IsConnected); - - // should reconnect within 1 keepalive interval - muxer.AllowConnect = true; - Console.WriteLine("Waiting for reconnect"); - Thread.Sleep(2000); - - Assert.IsTrue(muxer.IsConnected); - } - } - finally - { - SocketManager.ConnectCompletionType = CompletionType.Any; - ClearAmbientFailures(); - } - } - - [Test] - public void ConnectsWhenBeginConnectCompletesSynchronously() - { - try - { - SocketManager.ConnectCompletionType = CompletionType.Sync; - - using (var muxer = Create(keepAlive: 1, connectTimeout: 3000)) - { - var conn = muxer.GetDatabase(); - conn.Ping(); - - Assert.IsTrue(muxer.IsConnected); - } - } - finally - { - SocketManager.ConnectCompletionType = CompletionType.Any; - ClearAmbientFailures(); - } - } - - [Test] - public void FastNoticesFailOnConnectingAsync() - { - try - { - - using (var muxer = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true)) - { - var conn = muxer.GetDatabase(); - conn.Ping(); - - var server = muxer.GetServer(muxer.GetEndPoints()[0]); - - muxer.AllowConnect = false; - SocketManager.ConnectCompletionType = CompletionType.Async; - - server.SimulateConnectionFailure(); - - Assert.IsFalse(muxer.IsConnected); - - // should reconnect within 1 keepalive interval - muxer.AllowConnect = true; - Console.WriteLine("Waiting for reconnect"); - Thread.Sleep(2000); - - Assert.IsTrue(muxer.IsConnected); - } - } - finally - { - SocketManager.ConnectCompletionType = CompletionType.Any; - ClearAmbientFailures(); - } - } - - [Test] - public void ReconnectsOnStaleConnection() - { - try - { - using (var muxer = Create(keepAlive: 1, connectTimeout: 3000)) - { - var conn = muxer.GetDatabase(); - conn.Ping(); - - Assert.IsTrue(muxer.IsConnected); - - PhysicalConnection.EmulateStaleConnection = true; - Thread.Sleep(500); - Assert.IsFalse(muxer.IsConnected); - - PhysicalConnection.EmulateStaleConnection = false; - Thread.Sleep(1000); - Assert.IsTrue(muxer.IsConnected); - } - } - finally - { - PhysicalConnection.EmulateStaleConnection = false; - ClearAmbientFailures(); - } - } - -#endif - } -} diff --git a/StackExchange.Redis.Tests/ConnectionFailedErrors.cs b/StackExchange.Redis.Tests/ConnectionFailedErrors.cs deleted file mode 100644 index 3fbee2eeb..000000000 --- a/StackExchange.Redis.Tests/ConnectionFailedErrors.cs +++ /dev/null @@ -1,146 +0,0 @@ -using NUnit.Framework; -using System.Threading; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class ConnectionFailedErrors : TestBase - { - [Test] - [TestCase(true)] - [TestCase(false)] - public void SSLCertificateValidationError(bool isCertValidationSucceeded) - { - string name, password; - GetAzureCredentials(out name, out password); - var options = new ConfigurationOptions(); - options.EndPoints.Add(name + ".redis.cache.windows.net"); - options.Ssl = true; - options.Password = password; - options.CertificateValidation += (sender, cert, chain, errors) => { return isCertValidationSucceeded; }; - options.AbortOnConnectFail = false; - - using (var connection = ConnectionMultiplexer.Connect(options)) - { - connection.ConnectionFailed += (object sender, ConnectionFailedEventArgs e) => - { - Assert.That(e.FailureType, Is.EqualTo(ConnectionFailureType.AuthenticationFailure)); - }; - if (!isCertValidationSucceeded) - { - //validate that in this case it throws an certificatevalidation exception - var ex = Assert.Throws(() => connection.GetDatabase().Ping()); - var rde = (RedisConnectionException)ex.InnerException; - Assert.That(rde.FailureType, Is.EqualTo(ConnectionFailureType.AuthenticationFailure)); - Assert.That(rde.InnerException.Message, Is.EqualTo("The remote certificate is invalid according to the validation procedure.")); - } - else - { - Assert.DoesNotThrow(() => connection.GetDatabase().Ping()); - } - - //wait for a second for connectionfailed event to fire - Thread.Sleep(1000); - } - - - } - - [Test] - public void AuthenticationFailureError() - { - string name, password; - GetAzureCredentials(out name, out password); - var options = new ConfigurationOptions(); - options.EndPoints.Add(name + ".redis.cache.windows.net"); - options.Ssl = true; - options.Password = ""; - options.AbortOnConnectFail = false; - using (var muxer = ConnectionMultiplexer.Connect(options)) - { - muxer.ConnectionFailed += (object sender, ConnectionFailedEventArgs e) => - { - Assert.That(e.FailureType, Is.EqualTo(ConnectionFailureType.AuthenticationFailure)); - }; - var ex = Assert.Throws(() => muxer.GetDatabase().Ping()); - var rde = (RedisConnectionException)ex.InnerException; - Assert.That(ex.CommandStatus, Is.EqualTo(CommandStatus.WaitingToBeSent)); - Assert.That(rde.FailureType, Is.EqualTo(ConnectionFailureType.AuthenticationFailure)); - Assert.That(rde.InnerException.Message, Is.EqualTo("Error: NOAUTH Authentication required. Verify if the Redis password provided is correct.")); - //wait for a second for connectionfailed event to fire - Thread.Sleep(1000); - } - } - - [Test] - public void SocketFailureError() - { - var options = new ConfigurationOptions(); - options.EndPoints.Add(".redis.cache.windows.net"); - options.Ssl = true; - options.Password = ""; - options.AbortOnConnectFail = false; - using (var muxer = ConnectionMultiplexer.Connect(options)) - { - var ex = Assert.Throws(() => muxer.GetDatabase().Ping()); - var rde = (RedisConnectionException)ex.InnerException; - Assert.That(rde.FailureType, Is.EqualTo(ConnectionFailureType.SocketFailure)); - } - } -#if DEBUG // needs AllowConnect, which is DEBUG only - [Test] - public void AbortOnConnectFailFalseConnectTimeoutError() - { - string name, password; - GetAzureCredentials(out name, out password); - var options = new ConfigurationOptions(); - options.EndPoints.Add(name + ".redis.cache.windows.net"); - options.Ssl = true; - options.ConnectTimeout = 0; - options.Password = password; - using (var muxer = ConnectionMultiplexer.Connect(options)) - { - var ex = Assert.Throws(() => muxer.GetDatabase().Ping()); - Assert.That(ex.Message, Does.Contain("ConnectTimeout")); - } - } - - [Test] - public void CheckFailureRecovered() - { - try - { - using (var muxer = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true)) - { - var conn = muxer.GetDatabase(); - var server = muxer.GetServer(muxer.GetEndPoints()[0]); - - muxer.AllowConnect = false; - SocketManager.ConnectCompletionType = CompletionType.Async; - - server.SimulateConnectionFailure(); - - Assert.AreEqual(ConnectionFailureType.SocketFailure, ((RedisConnectionException)muxer.GetServerSnapshot()[0].LastException).FailureType); - - // should reconnect within 1 keepalive interval - muxer.AllowConnect = true; - Thread.Sleep(2000); - - Assert.Null(muxer.GetServerSnapshot()[0].LastException); - } - } - finally - { - SocketManager.ConnectCompletionType = CompletionType.Any; - ClearAmbientFailures(); - } - } - -#endif - [Test] - public void TryGetAzureRoleInstanceIdNoThrow() - { - Assert.IsNull(ConnectionMultiplexer.TryGetAzureRoleInstanceIdNoThrow()); - } - } -} diff --git a/StackExchange.Redis.Tests/ConnectionReconnectRetryPolicyTests.cs b/StackExchange.Redis.Tests/ConnectionReconnectRetryPolicyTests.cs deleted file mode 100644 index bf9edb1b3..000000000 --- a/StackExchange.Redis.Tests/ConnectionReconnectRetryPolicyTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class TransientErrorTests : TestBase - { - - [TestCase] - public void TestExponentialRetry() - { - IReconnectRetryPolicy exponentialRetry = new ExponentialRetry(5000); - Assert.False(exponentialRetry.ShouldRetry(0, 0)); - Assert.True(exponentialRetry.ShouldRetry(1, 5600)); - Assert.True(exponentialRetry.ShouldRetry(2, 6050)); - Assert.False(exponentialRetry.ShouldRetry(2, 4050)); - } - - [TestCase] - public void TestExponentialMaxRetry() - { - IReconnectRetryPolicy exponentialRetry = new ExponentialRetry(5000); - Assert.True(exponentialRetry.ShouldRetry(long.MaxValue, (int)TimeSpan.FromSeconds(30).TotalMilliseconds)); - } - - [TestCase] - public void TestLinearRetry() - { - IReconnectRetryPolicy linearRetry = new LinearRetry(5000); - Assert.False(linearRetry.ShouldRetry(0, 0)); - Assert.False(linearRetry.ShouldRetry(2, 4999)); - Assert.True(linearRetry.ShouldRetry(1, 5000)); - } - } -} diff --git a/StackExchange.Redis.Tests/ConnectionShutdown.cs b/StackExchange.Redis.Tests/ConnectionShutdown.cs deleted file mode 100644 index 30f26c8c9..000000000 --- a/StackExchange.Redis.Tests/ConnectionShutdown.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class ConnectionShutdown : TestBase - { - protected override string GetConfiguration() - { - return PrimaryServer + ":" + PrimaryPortString; - } - - [Test] - public void ShutdownRaisesConnectionFailedAndRestore() - { - using(var conn = Create(allowAdmin: true)) - { - int failed = 0, restored = 0; - Stopwatch watch = Stopwatch.StartNew(); - conn.ConnectionFailed += (sender,args)=> - { - Console.WriteLine(watch.Elapsed + ": failed: " + EndPointCollection.ToString(args.EndPoint) + "/" + args.ConnectionType); - Interlocked.Increment(ref failed); - }; - conn.ConnectionRestored += (sender, args) => - { - Console.WriteLine(watch.Elapsed + ": restored: " + EndPointCollection.ToString(args.EndPoint) + "/" + args.ConnectionType); - Interlocked.Increment(ref restored); - }; - var db = conn.GetDatabase(); - db.Ping(); - Assert.AreEqual(0, Interlocked.CompareExchange(ref failed, 0, 0)); - Assert.AreEqual(0, Interlocked.CompareExchange(ref restored, 0, 0)); - -#if DEBUG - conn.AllowConnect = false; - var server = conn.GetServer(PrimaryServer, PrimaryPort); - - SetExpectedAmbientFailureCount(2); - server.SimulateConnectionFailure(); - - db.Ping(CommandFlags.FireAndForget); - Thread.Sleep(250); - Assert.AreEqual(2, Interlocked.CompareExchange(ref failed, 0, 0), "failed"); - Assert.AreEqual(0, Interlocked.CompareExchange(ref restored, 0, 0), "restored"); - conn.AllowConnect = true; - db.Ping(CommandFlags.FireAndForget); - Thread.Sleep(1500); - Assert.AreEqual(2, Interlocked.CompareExchange(ref failed, 0, 0), "failed"); - Assert.AreEqual(2, Interlocked.CompareExchange(ref restored, 0, 0), "restored"); -#endif - watch.Stop(); - } - - } - } -} diff --git a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs b/StackExchange.Redis.Tests/DatabaseWrapperTests.cs deleted file mode 100644 index 1c1b185de..000000000 --- a/StackExchange.Redis.Tests/DatabaseWrapperTests.cs +++ /dev/null @@ -1,937 +0,0 @@ -#if FEATURE_MOQ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Net; -using System.Text; -using Moq; -using NUnit.Framework; -using StackExchange.Redis.KeyspaceIsolation; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public sealed class DatabaseWrapperTests - { - private Mock mock; - private DatabaseWrapper wrapper; - - //[TestFixtureSetUp] - [OneTimeSetUp] - public void Initialize() - { - mock = new Mock(); - wrapper = new DatabaseWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:")); - } - - [Test] - public void CreateBatch() - { - object asyncState = new object(); - IBatch innerBatch = new Mock().Object; - mock.Setup(_ => _.CreateBatch(asyncState)).Returns(innerBatch); - IBatch wrappedBatch = wrapper.CreateBatch(asyncState); - mock.Verify(_ => _.CreateBatch(asyncState)); - Assert.IsInstanceOf(wrappedBatch); - Assert.AreSame(innerBatch, ((BatchWrapper)wrappedBatch).Inner); - } - - [Test] - public void CreateTransaction() - { - object asyncState = new object(); - ITransaction innerTransaction = new Mock().Object; - mock.Setup(_ => _.CreateTransaction(asyncState)).Returns(innerTransaction); - ITransaction wrappedTransaction = wrapper.CreateTransaction(asyncState); - mock.Verify(_ => _.CreateTransaction(asyncState)); - Assert.IsInstanceOf(wrappedTransaction); - Assert.AreSame(innerTransaction, ((TransactionWrapper)wrappedTransaction).Inner); - } - - [Test] - public void DebugObject() - { - wrapper.DebugObject("key", CommandFlags.HighPriority); - mock.Verify(_ => _.DebugObject("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void get_Database() - { - mock.SetupGet(_ => _.Database).Returns(123); - Assert.AreEqual(123, wrapper.Database); - } - - [Test] - public void HashDecrement_1() - { - wrapper.HashDecrement("key", "hashField", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.HashDecrement("prefix:key", "hashField", 123, CommandFlags.HighPriority)); - } - - [Test] - public void HashDecrement_2() - { - wrapper.HashDecrement("key", "hashField", 1.23, CommandFlags.HighPriority); - mock.Verify(_ => _.HashDecrement("prefix:key", "hashField", 1.23, CommandFlags.HighPriority)); - } - - [Test] - public void HashDelete_1() - { - wrapper.HashDelete("key", "hashField", CommandFlags.HighPriority); - mock.Verify(_ => _.HashDelete("prefix:key", "hashField", CommandFlags.HighPriority)); - } - - [Test] - public void HashDelete_2() - { - RedisValue[] hashFields = new RedisValue[0]; - wrapper.HashDelete("key", hashFields, CommandFlags.HighPriority); - mock.Verify(_ => _.HashDelete("prefix:key", hashFields, CommandFlags.HighPriority)); - } - - [Test] - public void HashExists() - { - wrapper.HashExists("key", "hashField", CommandFlags.HighPriority); - mock.Verify(_ => _.HashExists("prefix:key", "hashField", CommandFlags.HighPriority)); - } - - [Test] - public void HashGet_1() - { - wrapper.HashGet("key", "hashField", CommandFlags.HighPriority); - mock.Verify(_ => _.HashGet("prefix:key", "hashField", CommandFlags.HighPriority)); - } - - [Test] - public void HashGet_2() - { - RedisValue[] hashFields = new RedisValue[0]; - wrapper.HashGet("key", hashFields, CommandFlags.HighPriority); - mock.Verify(_ => _.HashGet("prefix:key", hashFields, CommandFlags.HighPriority)); - } - - [Test] - public void HashGetAll() - { - wrapper.HashGetAll("key", CommandFlags.HighPriority); - mock.Verify(_ => _.HashGetAll("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void HashIncrement_1() - { - wrapper.HashIncrement("key", "hashField", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.HashIncrement("prefix:key", "hashField", 123, CommandFlags.HighPriority)); - } - - [Test] - public void HashIncrement_2() - { - wrapper.HashIncrement("key", "hashField", 1.23, CommandFlags.HighPriority); - mock.Verify(_ => _.HashIncrement("prefix:key", "hashField", 1.23, CommandFlags.HighPriority)); - } - - [Test] - public void HashKeys() - { - wrapper.HashKeys("key", CommandFlags.HighPriority); - mock.Verify(_ => _.HashKeys("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void HashLength() - { - wrapper.HashLength("key", CommandFlags.HighPriority); - mock.Verify(_ => _.HashLength("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void HashScan() - { - wrapper.HashScan("key", "pattern", 123, flags: CommandFlags.HighPriority); - mock.Verify(_ => _.HashScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority)); - } - - [Test] - public void HashSet_1() - { - HashEntry[] hashFields = new HashEntry[0]; - wrapper.HashSet("key", hashFields, CommandFlags.HighPriority); - mock.Verify(_ => _.HashSet("prefix:key", hashFields, CommandFlags.HighPriority)); - } - - [Test] - public void HashSet_2() - { - wrapper.HashSet("key", "hashField", "value", When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.HashSet("prefix:key", "hashField", "value", When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void HashValues() - { - wrapper.HashValues("key", CommandFlags.HighPriority); - mock.Verify(_ => _.HashValues("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void HyperLogLogAdd_1() - { - wrapper.HyperLogLogAdd("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.HyperLogLogAdd("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void HyperLogLogAdd_2() - { - RedisValue[] values = new RedisValue[0]; - wrapper.HyperLogLogAdd("key", values, CommandFlags.HighPriority); - mock.Verify(_ => _.HyperLogLogAdd("prefix:key", values, CommandFlags.HighPriority)); - } - - [Test] - public void HyperLogLogLength() - { - wrapper.HyperLogLogLength("key", CommandFlags.HighPriority); - mock.Verify(_ => _.HyperLogLogLength("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void HyperLogLogMerge_1() - { - wrapper.HyperLogLogMerge("destination", "first", "second", CommandFlags.HighPriority); - mock.Verify(_ => _.HyperLogLogMerge("prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority)); - } - - [Test] - public void HyperLogLogMerge_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.HyperLogLogMerge("destination", keys, CommandFlags.HighPriority); - mock.Verify(_ => _.HyperLogLogMerge("prefix:destination", It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void IdentifyEndpoint() - { - wrapper.IdentifyEndpoint("key", CommandFlags.HighPriority); - mock.Verify(_ => _.IdentifyEndpoint("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void KeyDelete_1() - { - wrapper.KeyDelete("key", CommandFlags.HighPriority); - mock.Verify(_ => _.KeyDelete("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void KeyDelete_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.KeyDelete(keys, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyDelete(It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void KeyDump() - { - wrapper.KeyDump("key", CommandFlags.HighPriority); - mock.Verify(_ => _.KeyDump("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void KeyExists() - { - wrapper.KeyExists("key", CommandFlags.HighPriority); - mock.Verify(_ => _.KeyExists("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void KeyExpire_1() - { - TimeSpan expiry = TimeSpan.FromSeconds(123); - wrapper.KeyExpire("key", expiry, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyExpire("prefix:key", expiry, CommandFlags.HighPriority)); - } - - [Test] - public void KeyExpire_2() - { - DateTime expiry = DateTime.Now; - wrapper.KeyExpire("key", expiry, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyExpire("prefix:key", expiry, CommandFlags.HighPriority)); - } - - [Test] - public void KeyMigrate() - { - EndPoint toServer = new IPEndPoint(IPAddress.Loopback, 123); - wrapper.KeyMigrate("key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyMigrate("prefix:key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.HighPriority)); - } - - [Test] - public void KeyMove() - { - wrapper.KeyMove("key", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyMove("prefix:key", 123, CommandFlags.HighPriority)); - } - - [Test] - public void KeyPersist() - { - wrapper.KeyPersist("key", CommandFlags.HighPriority); - mock.Verify(_ => _.KeyPersist("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void KeyRandom() - { - Assert.Throws(() => wrapper.KeyRandom()); - } - - [Test] - public void KeyRename() - { - wrapper.KeyRename("key", "newKey", When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyRename("prefix:key", "prefix:newKey", When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void KeyRestore() - { - Byte[] value = new Byte[0]; - TimeSpan expiry = TimeSpan.FromSeconds(123); - wrapper.KeyRestore("key", value, expiry, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyRestore("prefix:key", value, expiry, CommandFlags.HighPriority)); - } - - [Test] - public void KeyTimeToLive() - { - wrapper.KeyTimeToLive("key", CommandFlags.HighPriority); - mock.Verify(_ => _.KeyTimeToLive("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void KeyType() - { - wrapper.KeyType("key", CommandFlags.HighPriority); - mock.Verify(_ => _.KeyType("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void ListGetByIndex() - { - wrapper.ListGetByIndex("key", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.ListGetByIndex("prefix:key", 123, CommandFlags.HighPriority)); - } - - [Test] - public void ListInsertAfter() - { - wrapper.ListInsertAfter("key", "pivot", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.ListInsertAfter("prefix:key", "pivot", "value", CommandFlags.HighPriority)); - } - - [Test] - public void ListInsertBefore() - { - wrapper.ListInsertBefore("key", "pivot", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.ListInsertBefore("prefix:key", "pivot", "value", CommandFlags.HighPriority)); - } - - [Test] - public void ListLeftPop() - { - wrapper.ListLeftPop("key", CommandFlags.HighPriority); - mock.Verify(_ => _.ListLeftPop("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void ListLeftPush_1() - { - wrapper.ListLeftPush("key", "value", When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.ListLeftPush("prefix:key", "value", When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void ListLeftPush_2() - { - RedisValue[] values = new RedisValue[0]; - wrapper.ListLeftPush("key", values, CommandFlags.HighPriority); - mock.Verify(_ => _.ListLeftPush("prefix:key", values, CommandFlags.HighPriority)); - } - - [Test] - public void ListLength() - { - wrapper.ListLength("key", CommandFlags.HighPriority); - mock.Verify(_ => _.ListLength("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void ListRange() - { - wrapper.ListRange("key", 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.ListRange("prefix:key", 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void ListRemove() - { - wrapper.ListRemove("key", "value", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.ListRemove("prefix:key", "value", 123, CommandFlags.HighPriority)); - } - - [Test] - public void ListRightPop() - { - wrapper.ListRightPop("key", CommandFlags.HighPriority); - mock.Verify(_ => _.ListRightPop("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void ListRightPopLeftPush() - { - wrapper.ListRightPopLeftPush("source", "destination", CommandFlags.HighPriority); - mock.Verify(_ => _.ListRightPopLeftPush("prefix:source", "prefix:destination", CommandFlags.HighPriority)); - } - - [Test] - public void ListRightPush_1() - { - wrapper.ListRightPush("key", "value", When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.ListRightPush("prefix:key", "value", When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void ListRightPush_2() - { - RedisValue[] values = new RedisValue[0]; - wrapper.ListRightPush("key", values, CommandFlags.HighPriority); - mock.Verify(_ => _.ListRightPush("prefix:key", values, CommandFlags.HighPriority)); - } - - [Test] - public void ListSetByIndex() - { - wrapper.ListSetByIndex("key", 123, "value", CommandFlags.HighPriority); - mock.Verify(_ => _.ListSetByIndex("prefix:key", 123, "value", CommandFlags.HighPriority)); - } - - [Test] - public void ListTrim() - { - wrapper.ListTrim("key", 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.ListTrim("prefix:key", 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void LockExtend() - { - TimeSpan expiry = TimeSpan.FromSeconds(123); - wrapper.LockExtend("key", "value", expiry, CommandFlags.HighPriority); - mock.Verify(_ => _.LockExtend("prefix:key", "value", expiry, CommandFlags.HighPriority)); - } - - [Test] - public void LockQuery() - { - wrapper.LockQuery("key", CommandFlags.HighPriority); - mock.Verify(_ => _.LockQuery("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void LockRelease() - { - wrapper.LockRelease("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.LockRelease("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void LockTake() - { - TimeSpan expiry = TimeSpan.FromSeconds(123); - wrapper.LockTake("key", "value", expiry, CommandFlags.HighPriority); - mock.Verify(_ => _.LockTake("prefix:key", "value", expiry, CommandFlags.HighPriority)); - } - - [Test] - public void Publish() - { - wrapper.Publish("channel", "message", CommandFlags.HighPriority); - mock.Verify(_ => _.Publish("prefix:channel", "message", CommandFlags.HighPriority)); - } - - [Test] - public void ScriptEvaluate_1() - { - byte[] hash = new byte[0]; - RedisValue[] values = new RedisValue[0]; - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.ScriptEvaluate(hash, keys, values, CommandFlags.HighPriority); - mock.Verify(_ => _.ScriptEvaluate(hash, It.Is(valid), values, CommandFlags.HighPriority)); - } - - [Test] - public void ScriptEvaluate_2() - { - RedisValue[] values = new RedisValue[0]; - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.ScriptEvaluate("script", keys, values, CommandFlags.HighPriority); - mock.Verify(_ => _.ScriptEvaluate("script", It.Is(valid), values, CommandFlags.HighPriority)); - } - - [Test] - public void SetAdd_1() - { - wrapper.SetAdd("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.SetAdd("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void SetAdd_2() - { - RedisValue[] values = new RedisValue[0]; - wrapper.SetAdd("key", values, CommandFlags.HighPriority); - mock.Verify(_ => _.SetAdd("prefix:key", values, CommandFlags.HighPriority)); - } - - [Test] - public void SetCombine_1() - { - wrapper.SetCombine(SetOperation.Intersect, "first", "second", CommandFlags.HighPriority); - mock.Verify(_ => _.SetCombine(SetOperation.Intersect, "prefix:first", "prefix:second", CommandFlags.HighPriority)); - } - - [Test] - public void SetCombine_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.SetCombine(SetOperation.Intersect, keys, CommandFlags.HighPriority); - mock.Verify(_ => _.SetCombine(SetOperation.Intersect, It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void SetCombineAndStore_1() - { - wrapper.SetCombineAndStore(SetOperation.Intersect, "destination", "first", "second", CommandFlags.HighPriority); - mock.Verify(_ => _.SetCombineAndStore(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority)); - } - - [Test] - public void SetCombineAndStore_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.SetCombineAndStore(SetOperation.Intersect, "destination", keys, CommandFlags.HighPriority); - mock.Verify(_ => _.SetCombineAndStore(SetOperation.Intersect, "prefix:destination", It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void SetContains() - { - wrapper.SetContains("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.SetContains("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void SetLength() - { - wrapper.SetLength("key", CommandFlags.HighPriority); - mock.Verify(_ => _.SetLength("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void SetMembers() - { - wrapper.SetMembers("key", CommandFlags.HighPriority); - mock.Verify(_ => _.SetMembers("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void SetMove() - { - wrapper.SetMove("source", "destination", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.SetMove("prefix:source", "prefix:destination", "value", CommandFlags.HighPriority)); - } - - [Test] - public void SetPop() - { - wrapper.SetPop("key", CommandFlags.HighPriority); - mock.Verify(_ => _.SetPop("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void SetRandomMember() - { - wrapper.SetRandomMember("key", CommandFlags.HighPriority); - mock.Verify(_ => _.SetRandomMember("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void SetRandomMembers() - { - wrapper.SetRandomMembers("key", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.SetRandomMembers("prefix:key", 123, CommandFlags.HighPriority)); - } - - [Test] - public void SetRemove_1() - { - wrapper.SetRemove("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.SetRemove("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void SetRemove_2() - { - RedisValue[] values = new RedisValue[0]; - wrapper.SetRemove("key", values, CommandFlags.HighPriority); - mock.Verify(_ => _.SetRemove("prefix:key", values, CommandFlags.HighPriority)); - } - - [Test] - public void SetScan() - { - wrapper.SetScan("key", "pattern", 123, flags: CommandFlags.HighPriority); - mock.Verify(_ => _.SetScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority)); - } - - [Test] - public void Sort() - { - RedisValue[] get = new RedisValue[] { "a", "#" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#"; - - wrapper.Sort("key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.HighPriority); - wrapper.Sort("key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.HighPriority); - - mock.Verify(_ => _.Sort("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", It.Is(valid), CommandFlags.HighPriority)); - mock.Verify(_ => _.Sort("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void SortAndStore() - { - RedisValue[] get = new RedisValue[] { "a", "#" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#"; - - wrapper.SortAndStore("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.HighPriority); - wrapper.SortAndStore("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.HighPriority); - - mock.Verify(_ => _.SortAndStore("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", It.Is(valid), CommandFlags.HighPriority)); - mock.Verify(_ => _.SortAndStore("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetAdd_1() - { - wrapper.SortedSetAdd("key", "member", 1.23, When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetAdd("prefix:key", "member", 1.23, When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetAdd_2() - { - SortedSetEntry[] values = new SortedSetEntry[0]; - wrapper.SortedSetAdd("key", values, When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetAdd("prefix:key", values, When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetCombineAndStore_1() - { - wrapper.SortedSetCombineAndStore(SetOperation.Intersect, "destination", "first", "second", Aggregate.Max, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetCombineAndStore(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", Aggregate.Max, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetCombineAndStore_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.SetCombineAndStore(SetOperation.Intersect, "destination", keys, CommandFlags.HighPriority); - mock.Verify(_ => _.SetCombineAndStore(SetOperation.Intersect, "prefix:destination", It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetDecrement() - { - wrapper.SortedSetDecrement("key", "member", 1.23, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetDecrement("prefix:key", "member", 1.23, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetIncrement() - { - wrapper.SortedSetIncrement("key", "member", 1.23, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetIncrement("prefix:key", "member", 1.23, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetLength() - { - wrapper.SortedSetLength("key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetLength("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetLengthByValue() - { - wrapper.SortedSetLengthByValue("key", "min", "max", Exclude.Start, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetLengthByValue("prefix:key", "min", "max", Exclude.Start, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRangeByRank() - { - wrapper.SortedSetRangeByRank("key", 123, 456, Order.Descending, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRangeByRank("prefix:key", 123, 456, Order.Descending, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRangeByRankWithScores() - { - wrapper.SortedSetRangeByRankWithScores("key", 123, 456, Order.Descending, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRangeByRankWithScores("prefix:key", 123, 456, Order.Descending, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRangeByScore() - { - wrapper.SortedSetRangeByScore("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRangeByScore("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRangeByScoreWithScores() - { - wrapper.SortedSetRangeByScoreWithScores("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRangeByScoreWithScores("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRangeByValue() - { - wrapper.SortedSetRangeByValue("key", "min", "max", Exclude.Start, 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRangeByValue("prefix:key", "min", "max", Exclude.Start, 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRank() - { - wrapper.SortedSetRank("key", "member", Order.Descending, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRank("prefix:key", "member", Order.Descending, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRemove_1() - { - wrapper.SortedSetRemove("key", "member", CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRemove("prefix:key", "member", CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRemove_2() - { - RedisValue[] members = new RedisValue[0]; - wrapper.SortedSetRemove("key", members, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRemove("prefix:key", members, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRemoveRangeByRank() - { - wrapper.SortedSetRemoveRangeByRank("key", 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRemoveRangeByRank("prefix:key", 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRemoveRangeByScore() - { - wrapper.SortedSetRemoveRangeByScore("key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRemoveRangeByScore("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRemoveRangeByValue() - { - wrapper.SortedSetRemoveRangeByValue("key", "min", "max", Exclude.Start, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRemoveRangeByValue("prefix:key", "min", "max", Exclude.Start, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetScan() - { - wrapper.SortedSetScan("key", "pattern", 123, flags: CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetScore() - { - wrapper.SortedSetScore("key", "member", CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetScore("prefix:key", "member", CommandFlags.HighPriority)); - } - - [Test] - public void StringAppend() - { - wrapper.StringAppend("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.StringAppend("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void StringBitCount() - { - wrapper.StringBitCount("key", 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.StringBitCount("prefix:key", 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void StringBitOperation_1() - { - wrapper.StringBitOperation(Bitwise.Xor, "destination", "first", "second", CommandFlags.HighPriority); - mock.Verify(_ => _.StringBitOperation(Bitwise.Xor, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority)); - } - - [Test] - public void StringBitOperation_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.StringBitOperation(Bitwise.Xor, "destination", keys, CommandFlags.HighPriority); - mock.Verify(_ => _.StringBitOperation(Bitwise.Xor, "prefix:destination", It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void StringBitPosition() - { - wrapper.StringBitPosition("key", true, 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.StringBitPosition("prefix:key", true, 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void StringDecrement_1() - { - wrapper.StringDecrement("key", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.StringDecrement("prefix:key", 123, CommandFlags.HighPriority)); - } - - [Test] - public void StringDecrement_2() - { - wrapper.StringDecrement("key", 1.23, CommandFlags.HighPriority); - mock.Verify(_ => _.StringDecrement("prefix:key", 1.23, CommandFlags.HighPriority)); - } - - [Test] - public void StringGet_1() - { - wrapper.StringGet("key", CommandFlags.HighPriority); - mock.Verify(_ => _.StringGet("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void StringGet_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.StringGet(keys, CommandFlags.HighPriority); - mock.Verify(_ => _.StringGet(It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void StringGetBit() - { - wrapper.StringGetBit("key", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.StringGetBit("prefix:key", 123, CommandFlags.HighPriority)); - } - - [Test] - public void StringGetRange() - { - wrapper.StringGetRange("key", 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.StringGetRange("prefix:key", 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void StringGetSet() - { - wrapper.StringGetSet("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.StringGetSet("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void StringGetWithExpiry() - { - wrapper.StringGetWithExpiry("key", CommandFlags.HighPriority); - mock.Verify(_ => _.StringGetWithExpiry("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void StringIncrement_1() - { - wrapper.StringIncrement("key", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.StringIncrement("prefix:key", 123, CommandFlags.HighPriority)); - } - - [Test] - public void StringIncrement_2() - { - wrapper.StringIncrement("key", 1.23, CommandFlags.HighPriority); - mock.Verify(_ => _.StringIncrement("prefix:key", 1.23, CommandFlags.HighPriority)); - } - - [Test] - public void StringLength() - { - wrapper.StringLength("key", CommandFlags.HighPriority); - mock.Verify(_ => _.StringLength("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void StringSet_1() - { - TimeSpan expiry = TimeSpan.FromSeconds(123); - wrapper.StringSet("key", "value", expiry, When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.StringSet("prefix:key", "value", expiry, When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void StringSet_2() - { - KeyValuePair[] values = new KeyValuePair[] { new KeyValuePair("a", "x"), new KeyValuePair("b", "y") }; - Expression[], bool>> valid = _ => _.Length == 2 && _[0].Key == "prefix:a" && _[0].Value == "x" && _[1].Key == "prefix:b" && _[1].Value == "y"; - wrapper.StringSet(values, When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.StringSet(It.Is(valid), When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void StringSetBit() - { - wrapper.StringSetBit("key", 123, true, CommandFlags.HighPriority); - mock.Verify(_ => _.StringSetBit("prefix:key", 123, true, CommandFlags.HighPriority)); - } - - [Test] - public void StringSetRange() - { - wrapper.StringSetRange("key", 123, "value", CommandFlags.HighPriority); - mock.Verify(_ => _.StringSetRange("prefix:key", 123, "value", CommandFlags.HighPriority)); - } - } -} -#endif \ No newline at end of file diff --git a/StackExchange.Redis.Tests/Databases.cs b/StackExchange.Redis.Tests/Databases.cs deleted file mode 100644 index b444dc8f6..000000000 --- a/StackExchange.Redis.Tests/Databases.cs +++ /dev/null @@ -1,70 +0,0 @@ -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Databases : TestBase - { - - [Test] - public void CountKeys() - { - using (var muxer = Create(allowAdmin:true)) - { - var server = GetServer(muxer); - server.FlushDatabase(0, CommandFlags.FireAndForget); - server.FlushDatabase(1, CommandFlags.FireAndForget); - } - using (var muxer = Create()) - { - RedisKey key = Me(); - var db0 = muxer.GetDatabase(0); - var db1 = muxer.GetDatabase(1); - db0.StringSet("abc", "def", flags: CommandFlags.FireAndForget); - db0.StringIncrement(key, flags: CommandFlags.FireAndForget); - db1.StringIncrement(key, flags: CommandFlags.FireAndForget); - - var server = GetServer(muxer); - var c0 = server.DatabaseSizeAsync(0); - var c1 = server.DatabaseSizeAsync(1); - - - Assert.AreEqual(2, muxer.Wait(c0)); - Assert.AreEqual(1, muxer.Wait(c1)); - - } - } - [Test] - public void MultiDatabases() - { - using (var muxer = Create()) - { - RedisKey key = Me(); - var db0 = muxer.GetDatabase(0); - var db1 = muxer.GetDatabase(1); - var db2 = muxer.GetDatabase(2); - db0.Ping(); - - db0.KeyDelete(key, CommandFlags.FireAndForget); - db1.KeyDelete(key, CommandFlags.FireAndForget); - db2.KeyDelete(key, CommandFlags.FireAndForget); - - muxer.WaitAll( - db0.StringSetAsync(key, "a"), - db1.StringSetAsync(key, "b"), - db2.StringSetAsync(key, "c") - ); - - var a = db0.StringGetAsync(key); - var b = db1.StringGetAsync(key); - var c = db2.StringGetAsync(key); - muxer.WaitAll(a, b, c); - - Assert.AreEqual("a", (string)muxer.Wait(a), "db:0"); - Assert.AreEqual("b", (string)muxer.Wait(b), "db:1"); - Assert.AreEqual("c", (string)muxer.Wait(c), "db:2"); - - } - } - } -} diff --git a/StackExchange.Redis.Tests/DefaultPorts.cs b/StackExchange.Redis.Tests/DefaultPorts.cs deleted file mode 100644 index dbfded6f1..000000000 --- a/StackExchange.Redis.Tests/DefaultPorts.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Linq; -using System.Net; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class DefaultPorts - { - [Test] - [TestCase("foo", 6379)] - [TestCase("foo:6379", 6379)] - [TestCase("foo:6380", 6380)] - [TestCase("foo,ssl=false", 6379)] - [TestCase("foo:6379,ssl=false", 6379)] - [TestCase("foo:6380,ssl=false", 6380)] - - [TestCase("foo,ssl=true", 6380)] - [TestCase("foo:6379,ssl=true", 6379)] - [TestCase("foo:6380,ssl=true", 6380)] - [TestCase("foo:6381,ssl=true", 6381)] - public void ConfigStringRoundTripWithDefaultPorts(string config, int expectedPort) - { - var options = ConfigurationOptions.Parse(config); - string backAgain = options.ToString(); - Assert.AreEqual(config, backAgain.Replace("=True","=true").Replace("=False", "=false")); - - options.SetDefaultPorts(); // normally it is the multiplexer that calls this, not us - Assert.AreEqual(expectedPort, ((DnsEndPoint)options.EndPoints.Single()).Port); - } - - [Test] - [TestCase("foo", 0, false, 6379)] - [TestCase("foo", 6379, false, 6379)] - [TestCase("foo", 6380, false, 6380)] - - [TestCase("foo", 0, true, 6380)] - [TestCase("foo", 6379, true, 6379)] - [TestCase("foo", 6380, true, 6380)] - [TestCase("foo", 6381, true, 6381)] - - public void ConfigManualWithDefaultPorts(string host, int port, bool useSsl, int expectedPort) - { - var options = new ConfigurationOptions(); - if(port == 0) - { - options.EndPoints.Add(host); - } else - { - options.EndPoints.Add(host, port); - } - if (useSsl) options.Ssl = true; - - options.SetDefaultPorts(); // normally it is the multiplexer that calls this, not us - Assert.AreEqual(expectedPort, ((DnsEndPoint)options.EndPoints.Single()).Port); - } - } -} diff --git a/StackExchange.Redis.Tests/ExceptionFactoryTests.cs b/StackExchange.Redis.Tests/ExceptionFactoryTests.cs deleted file mode 100644 index 92c5c1e0e..000000000 --- a/StackExchange.Redis.Tests/ExceptionFactoryTests.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class ExceptionFactoryTests : TestBase - { - [Test] - public void NullLastException() - { - using (var muxer = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true)) - { - var conn = muxer.GetDatabase(); - Assert.Null(muxer.GetServerSnapshot()[0].LastException); - var ex = ExceptionFactory.NoConnectionAvailable(true, true, new RedisCommand(), null, null, muxer.GetServerSnapshot()); - Assert.Null(ex.InnerException); - } - - } - - [Test] - public void NullSnapshot() - { - var ex = ExceptionFactory.NoConnectionAvailable(true, true, new RedisCommand(), null, null, null); - Assert.Null(ex.InnerException); - } -#if DEBUG // needs debug connection features - [Test] - public void MultipleEndpointsThrowAggregateException() - { - try - { - using (var muxer = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true)) - { - var conn = muxer.GetDatabase(); - muxer.AllowConnect = false; - SocketManager.ConnectCompletionType = CompletionType.Async; - - foreach (var endpoint in muxer.GetEndPoints()) - { - muxer.GetServer(endpoint).SimulateConnectionFailure(); - } - - var ex = ExceptionFactory.NoConnectionAvailable(true, true, new RedisCommand(), null, null, muxer.GetServerSnapshot()); - Assert.IsInstanceOf(ex); - Assert.IsInstanceOf(ex.InnerException); - var aggException = (AggregateException)ex.InnerException; - Assert.That(aggException.InnerExceptions.Count, Is.EqualTo(2)); - for (int i = 0; i < aggException.InnerExceptions.Count; i++) - { - Assert.That(((RedisConnectionException)aggException.InnerExceptions[i]).FailureType, Is.EqualTo(ConnectionFailureType.SocketFailure)); - } - } - } - finally - { - SocketManager.ConnectCompletionType = CompletionType.Any; - ClearAmbientFailures(); - } - } - - [Test] - public void NullInnerExceptionForMultipleEndpointsWithNoLastException() - { - try - { - using (var muxer = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true)) - { - var conn = muxer.GetDatabase(); - muxer.AllowConnect = false; - SocketManager.ConnectCompletionType = CompletionType.Async; - var ex = ExceptionFactory.NoConnectionAvailable(true, true, new RedisCommand(), null, null, muxer.GetServerSnapshot()); - Assert.IsInstanceOf(ex); - Assert.Null(ex.InnerException); - } - } - finally - { - SocketManager.ConnectCompletionType = CompletionType.Any; - ClearAmbientFailures(); - } - } - - [Test] - public void ServerTakesPrecendenceOverSnapshot() - { - try - { - using (var muxer = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true)) - { - var conn = muxer.GetDatabase(); - muxer.AllowConnect = false; - SocketManager.ConnectCompletionType = CompletionType.Async; - - muxer.GetServer(muxer.GetEndPoints()[0]).SimulateConnectionFailure(); - - var ex = ExceptionFactory.NoConnectionAvailable(true, true, new RedisCommand(), null,muxer.GetServerSnapshot()[0], muxer.GetServerSnapshot()); - Assert.IsInstanceOf(ex); - Assert.IsInstanceOf(ex.InnerException); - Assert.That(muxer.GetServerSnapshot()[0].LastException, Is.EqualTo(ex.InnerException)); - } - } - finally - { - SocketManager.ConnectCompletionType = CompletionType.Any; - ClearAmbientFailures(); - } - - } -#endif - } -} diff --git a/StackExchange.Redis.Tests/Expiry.cs b/StackExchange.Redis.Tests/Expiry.cs deleted file mode 100644 index 7e82f3b9b..000000000 --- a/StackExchange.Redis.Tests/Expiry.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Expiry : TestBase - { - static string[] GetMap(bool disablePTimes) - { - if(disablePTimes) - { - return new[] { "pexpire", "pexpireat", "pttl" }; - } - return null; - } - [Test] - [TestCase(true)] - [TestCase(false)] - public void TestBasicExpiryTimeSpan(bool disablePTimes) - { - using(var muxer = Create(disabledCommands: GetMap(disablePTimes))) - { - RedisKey key = Me(); - var conn = muxer.GetDatabase(); - conn.KeyDelete(key, CommandFlags.FireAndForget); - - conn.StringSet(key, "new value", flags: CommandFlags.FireAndForget); - var a = conn.KeyTimeToLiveAsync(key); - conn.KeyExpire(key, TimeSpan.FromHours(1), CommandFlags.FireAndForget); - var b = conn.KeyTimeToLiveAsync(key); - conn.KeyExpire(key, (TimeSpan?)null, CommandFlags.FireAndForget); - var c = conn.KeyTimeToLiveAsync(key); - conn.KeyExpire(key, TimeSpan.FromHours(1.5), CommandFlags.FireAndForget); - var d = conn.KeyTimeToLiveAsync(key); - conn.KeyExpire(key, TimeSpan.MaxValue, CommandFlags.FireAndForget); - var e = conn.KeyTimeToLiveAsync(key); - - Assert.IsNull(muxer.Wait(a)); - var time = muxer.Wait(b); - Assert.IsNotNull(time); - Assert.IsTrue(time > TimeSpan.FromMinutes(59.9) && time <= TimeSpan.FromMinutes(60)); - Assert.IsNull(muxer.Wait(c)); - time = muxer.Wait(d); - Assert.IsNotNull(time); - Assert.IsTrue(time > TimeSpan.FromMinutes(89.9) && time <= TimeSpan.FromMinutes(90)); - Assert.IsNull(muxer.Wait(e)); - } - } - - [Test] - [TestCase(true, true)] - [TestCase(false, true)] - [TestCase(true, false)] - [TestCase(false, false)] - public void TestBasicExpiryDateTime(bool disablePTimes, bool utc) - { - using (var muxer = Create(disabledCommands: GetMap(disablePTimes))) - { - RedisKey key = Me(); - var conn = muxer.GetDatabase(); - conn.KeyDelete(key, CommandFlags.FireAndForget); - - var now = utc ? DateTime.UtcNow : new DateTime(DateTime.UtcNow.Ticks + TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time").BaseUtcOffset.Ticks, DateTimeKind.Local); - conn.StringSet(key, "new value", flags: CommandFlags.FireAndForget); - var a = conn.KeyTimeToLiveAsync(key); - conn.KeyExpire(key, now.AddHours(1), CommandFlags.FireAndForget); - var b = conn.KeyTimeToLiveAsync(key); - conn.KeyExpire(key, (DateTime?)null, CommandFlags.FireAndForget); - var c = conn.KeyTimeToLiveAsync(key); - conn.KeyExpire(key, now.AddHours(1.5), CommandFlags.FireAndForget); - var d = conn.KeyTimeToLiveAsync(key); - conn.KeyExpire(key, DateTime.MaxValue, CommandFlags.FireAndForget); - var e = conn.KeyTimeToLiveAsync(key); - - Assert.IsNull(muxer.Wait(a)); - var time = muxer.Wait(b); - Assert.IsNotNull(time); - Console.WriteLine(time); - Assert.IsTrue(time > TimeSpan.FromMinutes(59.9) && time <= TimeSpan.FromMinutes(60)); - Assert.IsNull(muxer.Wait(c)); - time = muxer.Wait(d); - Assert.IsNotNull(time); - Assert.IsTrue(time > TimeSpan.FromMinutes(89.9) && time <= TimeSpan.FromMinutes(90)); - Assert.IsNull(muxer.Wait(e)); - } - } - } -} diff --git a/StackExchange.Redis.Tests/FloatingPoint.cs b/StackExchange.Redis.Tests/FloatingPoint.cs deleted file mode 100644 index c4d9bb90f..000000000 --- a/StackExchange.Redis.Tests/FloatingPoint.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class FloatingPoint : TestBase - { - static bool Within(double x, double y, double delta) - { - return Math.Abs(x - y) <= delta; - } - [Test] - public void IncrDecrFloatingPoint() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(); - RedisKey key = Me(); - db.KeyDelete(key, CommandFlags.FireAndForget); - double[] incr = - { - 12.134, - -14561.0000002, - 125.3421, - -2.49892498 - }, decr = - { - 99.312, - 12, - -35 - }; - double sum = 0; - foreach (var value in incr) - { - db.StringIncrement(key, value, CommandFlags.FireAndForget); - sum += value; - } - foreach (var value in decr) - { - db.StringDecrement(key, value, CommandFlags.FireAndForget); - sum -= value; - } - var val = (double)db.StringGet(key); - - Assert.IsTrue(Within(sum, val, 0.0001)); - } - } - - [Test] - public async void IncrDecrFloatingPointAsync() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(); - RedisKey key = Me(); - db.KeyDelete(key, CommandFlags.FireAndForget); - double[] incr = - { - 12.134, - -14561.0000002, - 125.3421, - -2.49892498 - }, decr = - { - 99.312, - 12, - -35 - }; - double sum = 0; - foreach (var value in incr) - { - await db.StringIncrementAsync(key, value).ConfigureAwait(false); - sum += value; - } - foreach (var value in decr) - { - await db.StringDecrementAsync(key, value).ConfigureAwait(false); - sum -= value; - } - var val = (double)await db.StringGetAsync(key).ConfigureAwait(false); - - Assert.IsTrue(Within(sum, val, 0.0001)); - } - } - - [Test] - public void HashIncrDecrFloatingPoint() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(); - RedisKey key = Me(); - RedisValue field = "foo"; - db.KeyDelete(key, CommandFlags.FireAndForget); - double[] incr = - { - 12.134, - -14561.0000002, - 125.3421, - -2.49892498 - }, decr = - { - 99.312, - 12, - -35 - }; - double sum = 0; - foreach (var value in incr) - { - db.HashIncrement(key, field, value, CommandFlags.FireAndForget); - sum += value; - } - foreach (var value in decr) - { - db.HashDecrement(key, field, value, CommandFlags.FireAndForget); - sum -= value; - } - var val = (double)db.HashGet(key, field); - - Assert.IsTrue(Within(sum, val, 0.0001)); - } - } - - [Test] - public async void HashIncrDecrFloatingPointAsync() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(); - RedisKey key = Me(); - RedisValue field = "bar"; - db.KeyDelete(key, CommandFlags.FireAndForget); - double[] incr = - { - 12.134, - -14561.0000002, - 125.3421, - -2.49892498 - }, decr = - { - 99.312, - 12, - -35 - }; - double sum = 0; - foreach (var value in incr) - { - await db.HashIncrementAsync(key, field, value).ConfigureAwait(false); - sum += value; - } - foreach (var value in decr) - { - await db.HashDecrementAsync(key, field, value).ConfigureAwait(false); - sum -= value; - } - var val = (double)await db.HashGetAsync(key, field).ConfigureAwait(false); - - Assert.IsTrue(Within(sum, val, 0.0001)); - } - } - } -} diff --git a/StackExchange.Redis.Tests/GeoTests.cs b/StackExchange.Redis.Tests/GeoTests.cs deleted file mode 100644 index 5f6429907..000000000 --- a/StackExchange.Redis.Tests/GeoTests.cs +++ /dev/null @@ -1,187 +0,0 @@ -using NUnit.Framework; -using System; -using System.IO; -using System.Linq; - -namespace StackExchange.Redis.Tests -{ - public class AzureTestAttribute : TestAttribute - { - - } - [TestFixture] - public class GeoTests : TestBase - { - private ConnectionMultiplexer Create() - { - string name, password; - GetAzureCredentials(out name, out password); - var options = new ConfigurationOptions(); - options.EndPoints.Add(name + ".redis.cache.windows.net"); - options.Ssl = true; - options.ConnectTimeout = 5000; - options.Password = password; - options.TieBreaker = ""; - var log = new StringWriter(); - var conn = ConnectionMultiplexer.Connect(options, log); - var s = log.ToString(); - Console.WriteLine(s); - return conn; - } - public const int Db = 0; - - public static GeoEntry - palermo = new GeoEntry(13.361389, 38.115556, "Palermo"), - catania = new GeoEntry(15.087269, 37.502669, "Catania"), - agrigento = new GeoEntry(13.5765, 37.311, "Agrigento"), - cefal� = new GeoEntry(14.0188, 38.0084, "Cefal�"); - public static GeoEntry[] all = { palermo, catania, agrigento , cefal� }; - [AzureTest] - public void GeoAdd() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(Db); - RedisKey key = Me(); - db.KeyDelete(key); - - // add while not there - Assert.IsTrue(db.GeoAdd(key, cefal�.Longitude, cefal�.Latitude, cefal�.Member)); - Assert.AreEqual(2, db.GeoAdd(key, new GeoEntry[] { palermo, catania })); - Assert.IsTrue(db.GeoAdd(key, agrigento)); - - // now add again - Assert.IsFalse(db.GeoAdd(key, cefal�.Longitude, cefal�.Latitude, cefal�.Member)); - Assert.AreEqual(0, db.GeoAdd(key, new GeoEntry[] { palermo, catania })); - Assert.IsFalse(db.GeoAdd(key, agrigento)); - - - } - } - - [AzureTest] - public void GetDistance() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(Db); - RedisKey key = Me(); - db.KeyDelete(key); - db.GeoAdd(key, all); - var val = db.GeoDistance(key, "Palermo", "Catania", GeoUnit.Meters); - Assert.IsTrue(val.HasValue); - var rounded = Math.Round(val.Value, 10); - Assert.AreEqual(166274.1516, val); - - - val = db.GeoDistance(key, "Palermo", "Nowhere", GeoUnit.Meters); - Assert.IsFalse(val.HasValue); - } - } - - [AzureTest] - public void GeoHash() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(Db); - RedisKey key = Me(); - db.KeyDelete(key); - db.GeoAdd(key, all); - - var hashes = db.GeoHash(key, new RedisValue[] { palermo.Member, "Nowhere", agrigento.Member }); - Assert.AreEqual(3, hashes.Length); - Assert.AreEqual("sqc8b49rny0", hashes[0]); - Assert.IsNull(hashes[1]); - Assert.AreEqual("sq9skbq0760", hashes[2]); - - var hash = db.GeoHash(key, "Palermo"); - Assert.AreEqual("sqc8b49rny0", hash); - - hash = db.GeoHash(key, "Nowhere"); - Assert.IsNull(hash); - } - } - - [AzureTest] - public void GeoGetPosition() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(Db); - RedisKey key = Me(); - db.KeyDelete(key); - db.GeoAdd(key, all); - - var pos = db.GeoPosition(key, palermo.Member); - Assert.IsTrue(pos.HasValue); - Assert.AreEqual(Math.Round(palermo.Longitude, 6), Math.Round(pos.Value.Longitude, 6)); - Assert.AreEqual(Math.Round(palermo.Latitude, 6), Math.Round(pos.Value.Latitude, 6)); - - pos = db.GeoPosition(key, "Nowhere"); - Assert.IsFalse(pos.HasValue); - } - } - - [AzureTest] - public void GeoRemove() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(Db); - RedisKey key = Me(); - db.KeyDelete(key); - db.GeoAdd(key, all); - - var pos = db.GeoPosition(key, "Palermo"); - Assert.IsTrue(pos.HasValue); - - Assert.IsFalse(db.GeoRemove(key, "Nowhere")); - Assert.IsTrue(db.GeoRemove(key, "Palermo")); - Assert.IsFalse(db.GeoRemove(key, "Palermo")); - - pos = db.GeoPosition(key, "Palermo"); - Assert.IsFalse(pos.HasValue); - } - } - - [AzureTest] - public void GeoRadius() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(Db); - RedisKey key = Me(); - db.KeyDelete(key); - db.GeoAdd(key, all); - - var results = db.GeoRadius(key, cefal�.Member, 60, GeoUnit.Miles, 2, Order.Ascending); - Assert.AreEqual(2, results.Length); - - Assert.AreEqual(results[0].Member, cefal�.Member); - Assert.AreEqual(0, results[0].Distance.Value); - Assert.AreEqual(Math.Round(results[0].Position.Value.Longitude, 5), Math.Round(cefal�.Position.Longitude, 5)); - Assert.AreEqual(Math.Round(results[0].Position.Value.Latitude, 5), Math.Round(cefal�.Position.Latitude, 5)); - Assert.IsFalse(results[0].Hash.HasValue); - - Assert.AreEqual(results[1].Member, palermo.Member); - Assert.AreEqual(Math.Round(36.5319, 6), Math.Round(results[1].Distance.Value, 6)); - Assert.AreEqual(Math.Round(results[1].Position.Value.Longitude, 5), Math.Round(palermo.Position.Longitude, 5)); - Assert.AreEqual(Math.Round(results[1].Position.Value.Latitude, 5), Math.Round(palermo.Position.Latitude, 5)); - Assert.IsFalse(results[1].Hash.HasValue); - - results = db.GeoRadius(key, cefal�.Member, 60, GeoUnit.Miles, 2, Order.Ascending, GeoRadiusOptions.None); - Assert.AreEqual(2, results.Length); - Assert.AreEqual(results[0].Member, cefal�.Member); - Assert.IsFalse(results[0].Position.HasValue); - Assert.IsFalse(results[0].Distance.HasValue); - Assert.IsFalse(results[0].Hash.HasValue); - - Assert.AreEqual(results[1].Member, palermo.Member); - Assert.IsFalse(results[1].Position.HasValue); - Assert.IsFalse(results[1].Distance.HasValue); - Assert.IsFalse(results[1].Hash.HasValue); - } - } - } -} diff --git a/StackExchange.Redis.Tests/HyperLogLog.cs b/StackExchange.Redis.Tests/HyperLogLog.cs deleted file mode 100644 index 32a50e9f2..000000000 --- a/StackExchange.Redis.Tests/HyperLogLog.cs +++ /dev/null @@ -1,41 +0,0 @@ -using NUnit.Framework; -using System.IO; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class HyperLogLog : TestBase - { - [Test] - public void SingleKeyLength() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(); - RedisKey key = "hll1"; - - db.HyperLogLogAdd(key, "a"); - db.HyperLogLogAdd(key, "b"); - db.HyperLogLogAdd(key, "c"); - - Assert.IsTrue(db.HyperLogLogLength(key) > 0); - } - } - - [Test] - public void MultiKeyLength() - { - using (var conn = Create(useSharedSocketManager: true)) - { - var db = conn.GetDatabase(); - RedisKey[] keys = { "hll1", "hll2", "hll3" }; - - db.HyperLogLogAdd(keys[0], "a"); - db.HyperLogLogAdd(keys[1], "b"); - db.HyperLogLogAdd(keys[2], "c"); - - Assert.IsTrue(db.HyperLogLogLength(keys) > 0); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Issues/BgSaveResponse.cs b/StackExchange.Redis.Tests/Issues/BgSaveResponse.cs deleted file mode 100644 index 661ca5542..000000000 --- a/StackExchange.Redis.Tests/Issues/BgSaveResponse.cs +++ /dev/null @@ -1,23 +0,0 @@ -using NUnit.Framework; - -namespace StackExchange.Redis.Tests.Issues -{ - [TestFixture] - public class BgSaveResponse : TestBase - { - [Test] -#pragma warning disable 0618 - [TestCase(SaveType.ForegroundSave)] -#pragma warning restore 0618 - [TestCase(SaveType.BackgroundSave)] - [TestCase(SaveType.BackgroundRewriteAppendOnlyFile)] - public void ShouldntThrowException(SaveType saveType) - { - using (var conn = Create(null, null, true)) - { - var Server = GetServer(conn); - Server.Save(saveType); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Issues/DefaultDatabase.cs b/StackExchange.Redis.Tests/Issues/DefaultDatabase.cs deleted file mode 100644 index 34ea7b0f5..000000000 --- a/StackExchange.Redis.Tests/Issues/DefaultDatabase.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.IO; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests.Issues -{ - [TestFixture] - public class DefaultDatabase : TestBase - { - [Test] - public void UnspecifiedDbId_ReturnsNull() - { - var config = ConfigurationOptions.Parse("localhost"); - Assert.IsNull(config.DefaultDatabase); - } - - [Test] - public void SpecifiedDbId_ReturnsExpected() - { - var config = ConfigurationOptions.Parse("localhost,defaultDatabase=3"); - Assert.AreEqual(3, config.DefaultDatabase); - } - - [Test] - public void ConfigurationOptions_UnspecifiedDefaultDb() - { - var log = new StringWriter(); - try - { - using (var conn = ConnectionMultiplexer.Connect($"{PrimaryServer}:{PrimaryPort}", log)) { - var db = conn.GetDatabase(); - Assert.AreEqual(0, db.Database); - } - } - finally - { - Console.WriteLine(log); - } - } - - [Test] - public void ConfigurationOptions_SpecifiedDefaultDb() - { - var log = new StringWriter(); - try - { - using (var conn = ConnectionMultiplexer.Connect($"{PrimaryServer}:{PrimaryPort},defaultDatabase=3", log)) { - var db = conn.GetDatabase(); - Assert.AreEqual(3, db.Database); - } - } - finally - { - Console.WriteLine(log); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Issues/Issue118.cs b/StackExchange.Redis.Tests/Issues/Issue118.cs deleted file mode 100644 index 6200b9a23..000000000 --- a/StackExchange.Redis.Tests/Issues/Issue118.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace StackExchange.Redis.Tests.Issues -{ - class Issue118 - { - } -} diff --git a/StackExchange.Redis.Tests/Issues/Issue182.cs b/StackExchange.Redis.Tests/Issues/Issue182.cs deleted file mode 100644 index 35126f05f..000000000 --- a/StackExchange.Redis.Tests/Issues/Issue182.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Linq; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests.Issues -{ - [TestFixture] - public class Issue182 : TestBase - { - protected override string GetConfiguration() - { - return "127.0.0.1:6379,responseTimeout=10000"; - } - [Test] - public void SetMembers() - { - using (var conn = Create()) - { - conn.ConnectionFailed += (s, a) => - { - Console.WriteLine(a.FailureType); - Console.WriteLine(a.Exception.Message); - Console.WriteLine(a.Exception.StackTrace); - }; - var db = conn.GetDatabase(); - - var key = Me(); - const int count = (int)5e6; - - db.KeyDeleteAsync(key).Wait(); - foreach (var _ in Enumerable.Range(0, count)) - db.SetAdd(key, Guid.NewGuid().ToByteArray(), CommandFlags.FireAndForget); - - Assert.AreEqual(count, db.SetLengthAsync(key).Result, "SCARD for set"); - - var task = db.SetMembersAsync(key); - task.Wait(); - Assert.AreEqual(count, task.Result.Length, "SMEMBERS result length"); - } - } - - [Test] - public void SetUnion() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(); - - var key1 = Me() + ":1"; - var key2 = Me() + ":2"; - var dstkey = Me() + ":dst"; - - db.KeyDeleteAsync(key1).Wait(); - db.KeyDeleteAsync(key2).Wait(); - db.KeyDeleteAsync(dstkey).Wait(); - - const int count = (int)5e6; - foreach (var _ in Enumerable.Range(0, count)) - { - db.SetAdd(key1, Guid.NewGuid().ToByteArray(), CommandFlags.FireAndForget); - db.SetAdd(key2, Guid.NewGuid().ToByteArray(), CommandFlags.FireAndForget); - } - Assert.AreEqual(count, db.SetLengthAsync(key1).Result, "SCARD for set 1"); - Assert.AreEqual(count, db.SetLengthAsync(key2).Result, "SCARD for set 2"); - - db.SetCombineAndStoreAsync(SetOperation.Union, dstkey, key1, key2).Wait(); - var dstLen = db.SetLength(dstkey); - Assert.AreEqual(count * 2, dstLen, "SCARD for destination set"); - } - } - } -} \ No newline at end of file diff --git a/StackExchange.Redis.Tests/Issues/Issue25.cs b/StackExchange.Redis.Tests/Issues/Issue25.cs deleted file mode 100644 index 78c750c11..000000000 --- a/StackExchange.Redis.Tests/Issues/Issue25.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests.Issues -{ - [TestFixture] - public class Issue25 : TestBase - { - [Test] - public void CaseInsensitive() - { - var options = ConfigurationOptions.Parse("ssl=true"); - Assert.IsTrue(options.Ssl); - Assert.AreEqual("ssl=True", options.ToString()); - - options = ConfigurationOptions.Parse("SSL=TRUE"); - Assert.IsTrue(options.Ssl); - Assert.AreEqual("ssl=True", options.ToString()); - } - - [Test] - public void UnkonwnKeywordHandling_Ignore() - { - var options = ConfigurationOptions.Parse("ssl2=true", true); - } - [Test] - public void UnkonwnKeywordHandling_ExplicitFail() - { - Assert.Throws(() => { - var options = ConfigurationOptions.Parse("ssl2=true", false); - }, - "Keyword 'ssl2' is not supported"); - } - [Test] - public void UnkonwnKeywordHandling_ImplicitFail() - { - Assert.Throws(() => { - var options = ConfigurationOptions.Parse("ssl2=true"); - }, - "Keyword 'ssl2' is not supported"); - } - } -} diff --git a/StackExchange.Redis.Tests/Issues/Issue6.cs b/StackExchange.Redis.Tests/Issues/Issue6.cs deleted file mode 100644 index f4a23d0cc..000000000 --- a/StackExchange.Redis.Tests/Issues/Issue6.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests.Issues -{ - [TestFixture] - public class Issue6 : TestBase - { - [Test] - public void ShouldWorkWithoutEchoOrPing() - { - using(var conn = Create(proxy: Proxy.Twemproxy)) - { - Console.WriteLine("config: " + conn.Configuration); - var db = conn.GetDatabase(); - var time = db.Ping(); - Console.WriteLine("ping time: " + time); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Issues/SO22786599.cs b/StackExchange.Redis.Tests/Issues/SO22786599.cs deleted file mode 100644 index 4829e472c..000000000 --- a/StackExchange.Redis.Tests/Issues/SO22786599.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Diagnostics; -using System.Linq; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests.Issues -{ - [TestFixture] - public class SO22786599 : TestBase - { - [Test] - public void Execute() - { - string CurrentIdsSetDbKey = Me() + ".x"; - string CurrentDetailsSetDbKey = Me() + ".y"; - - RedisValue[] stringIds = Enumerable.Range(1, 750).Select(i => (RedisValue)(i + " id")).ToArray(); - RedisValue[] stringDetails = Enumerable.Range(1, 750).Select(i => (RedisValue)(i + " detail")).ToArray(); - - using (var conn = Create()) - { - var db = conn.GetDatabase(); - var tran = db.CreateTransaction(); - - tran.SetAddAsync(CurrentIdsSetDbKey, stringIds); - tran.SetAddAsync(CurrentDetailsSetDbKey, stringDetails); - - var watch = Stopwatch.StartNew(); - var isOperationSuccessful = tran.Execute(); - watch.Stop(); - System.Console.WriteLine("{0}ms", watch.ElapsedMilliseconds); - Assert.IsTrue(isOperationSuccessful); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Issues/SO23949477.cs b/StackExchange.Redis.Tests/Issues/SO23949477.cs deleted file mode 100644 index 7f9a8ac5c..000000000 --- a/StackExchange.Redis.Tests/Issues/SO23949477.cs +++ /dev/null @@ -1,38 +0,0 @@ -using NUnit.Framework; - -namespace StackExchange.Redis.Tests.Issues -{ - [TestFixture] - public class SO23949477 : TestBase - { - [Test] - public void Execute() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(0); - RedisKey key = Me(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.SortedSetAdd(key, "c", 3, When.Always, CommandFlags.FireAndForget); - db.SortedSetAdd(key, - new[] { - new SortedSetEntry("a", 1), - new SortedSetEntry("b", 2), - new SortedSetEntry("d", 4), - new SortedSetEntry("e", 5) - }, - When.Always, - CommandFlags.FireAndForget); - var pairs = db.SortedSetRangeByScoreWithScores( - key, order: Order.Descending, take: 3); - Assert.AreEqual(3, pairs.Length); - Assert.AreEqual(5, pairs[0].Score); - Assert.AreEqual("e", (string)pairs[0].Element); - Assert.AreEqual(4, pairs[1].Score); - Assert.AreEqual("d", (string)pairs[1].Element); - Assert.AreEqual(3, pairs[2].Score); - Assert.AreEqual("c", (string)pairs[2].Element); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Issues/SO24807536.cs b/StackExchange.Redis.Tests/Issues/SO24807536.cs deleted file mode 100644 index cd46462f9..000000000 --- a/StackExchange.Redis.Tests/Issues/SO24807536.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Threading; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests.Issues -{ - [TestFixture] - public class SO24807536 : TestBase - { - public void Exec() - { - var key = Me(); - using(var conn = Create()) - { - var cache = conn.GetDatabase(); - - // setup some data - cache.KeyDelete(key); - cache.HashSet(key, "full", "some value"); - cache.KeyExpire(key, TimeSpan.FromSeconds(3)); - - // test while exists - var exists = cache.KeyExists(key); - var ttl = cache.KeyTimeToLive(key); - var fullWait = cache.HashGetAsync(key, "full", flags: CommandFlags.None); - Assert.IsTrue(exists, "key exists"); - Assert.IsNotNull(ttl, "ttl"); - Assert.AreEqual("some value", (string)fullWait.Result); - - // wait for expiry - Thread.Sleep(TimeSpan.FromSeconds(4)); - - // test once expired - exists = cache.KeyExists(key); - ttl = cache.KeyTimeToLive(key); - fullWait = cache.HashGetAsync(key, "full", flags: CommandFlags.None); - Assert.IsFalse(exists, "key exists"); - Assert.IsNull(ttl, "ttl"); - Assert.IsNull((string)fullWait.Result); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Issues/SO25113323.cs b/StackExchange.Redis.Tests/Issues/SO25113323.cs deleted file mode 100644 index e569bef5f..000000000 --- a/StackExchange.Redis.Tests/Issues/SO25113323.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Threading; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests.Issues -{ - [TestFixture] - public class SO25113323 : TestBase - { - [Test] - public void SetExpirationToPassed() - { - var key = Me(); - using (var conn = Create()) - { - // Given - var cache = conn.GetDatabase(); - cache.KeyDelete(key); - cache.HashSet(key, "full", "test", When.NotExists, CommandFlags.PreferMaster); - - Thread.Sleep(10 * 1000); - - // When - var expiresOn = DateTime.UtcNow.AddSeconds(-10); - - var firstResult = cache.KeyExpire(key, expiresOn, CommandFlags.PreferMaster); - var secondResult = cache.KeyExpire(key, expiresOn, CommandFlags.PreferMaster); - var exists = cache.KeyExists(key); - var ttl = cache.KeyTimeToLive(key); - - // Then - Assert.IsTrue(firstResult, "first"); // could set the first time, but this nukes the key - Assert.IsFalse(secondResult, "second"); // can't set, since nuked - Assert.IsFalse(exists, "exists"); // does not exist since nuked - Assert.IsNull(ttl, "ttl"); // no expiry since nuked - } - } - } -} diff --git a/StackExchange.Redis.Tests/Issues/SO25567566.cs b/StackExchange.Redis.Tests/Issues/SO25567566.cs deleted file mode 100644 index e9724c911..000000000 --- a/StackExchange.Redis.Tests/Issues/SO25567566.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests.Issues -{ - [TestFixture] - public class SO25567566 : TestBase - { - protected override string GetConfiguration() - { - return "127.0.0.1:6379"; - } - [Test] - public async void Execute() - { - using(var conn = ConnectionMultiplexer.Connect(GetConfiguration())) // Create()) - { - for(int i = 0; i < 100; i++) - { - Assert.AreEqual("ok", await DoStuff(conn).ConfigureAwait(false)); - - } - } - } - private async Task DoStuff(ConnectionMultiplexer conn) - { - var db = conn.GetDatabase(); - - var timeout = Task.Delay(5000); - var len = db.ListLengthAsync("list"); - - if (await Task.WhenAny(timeout, len).ConfigureAwait(false) != len) - { - return "Timeout getting length"; - } - - - if ((await len.ConfigureAwait(false)) == 0) - { - db.ListRightPush("list", "foo", flags: CommandFlags.FireAndForget); - } - var tran = db.CreateTransaction(); - var x = tran.ListRightPopLeftPushAsync("list", "list2"); - var y = tran.SetAddAsync("set", "bar"); - var z = tran.KeyExpireAsync("list2", TimeSpan.FromSeconds(60)); - timeout = Task.Delay(5000); - - var exec = tran.ExecuteAsync(); - // SWAP THESE TWO - bool ok = await Task.WhenAny(exec, timeout).ConfigureAwait(false) == exec; - //bool ok = true; - - if (ok) - { - if (await exec.ConfigureAwait(false)) - { - await Task.WhenAll(x, y, z).ConfigureAwait(false); - - var db2 = conn.GetDatabase(); - db2.HashGet("hash", "whatever"); - return "ok"; - } - else - { - return "Transaction aborted"; - } - } - else - { - return "Timeout during exec"; - } - } - } -} diff --git a/StackExchange.Redis.Tests/Keys.cs b/StackExchange.Redis.Tests/Keys.cs deleted file mode 100644 index 688bd03ed..000000000 --- a/StackExchange.Redis.Tests/Keys.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System.Linq; -using System.Text; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Keys : TestBase - { - [Test] - public void TestScan() - { - using(var muxer = Create(allowAdmin: true)) - { - const int Database = 4; - var db = muxer.GetDatabase(Database); - GetServer(muxer).FlushDatabase(flags: CommandFlags.FireAndForget); - - const int Count = 1000; - for (int i = 0; i < Count; i++) - db.StringSet("x" + i, "y" + i, flags: CommandFlags.FireAndForget); - - var count = GetServer(muxer).Keys(Database).Count(); - Assert.AreEqual(Count, count); - } - } - - [Test] - public void RandomKey() - { - using(var conn = Create(allowAdmin: true)) - { - var db = conn.GetDatabase(); - conn.GetServer(PrimaryServer, PrimaryPort).FlushDatabase(); - string anyKey = db.KeyRandom(); - - Assert.IsNull(anyKey); - db.StringSet("abc", "def"); - byte[] keyBytes = db.KeyRandom(); - - Assert.AreEqual("abc", Encoding.UTF8.GetString(keyBytes)); - } - } - - [Test] - public void Zeros() - { - using(var conn = Create()) - { - var db = conn.GetDatabase(); - db.KeyDelete("abc"); - db.StringSet("abc", 123); - int k = (int)db.StringGet("abc"); - Assert.AreEqual(123, k); - - db.KeyDelete("abc"); - int i = (int)db.StringGet("abc"); - Assert.AreEqual(0, i); - - Assert.IsTrue(db.StringGet("abc").IsNull); - int? value = (int?)db.StringGet("abc"); - Assert.IsFalse(value.HasValue); - - } - } - - [Test] - public void PrependAppend() - { - { - // simple - RedisKey key = "world"; - var ret = key.Prepend("hello"); - Assert.AreEqual("helloworld", (string)ret); - } - - { - RedisKey key1 = "world"; - RedisKey key2 = Encoding.UTF8.GetBytes("hello"); - var key3 = key1.Prepend(key2); - Assert.IsTrue(object.ReferenceEquals(key1.KeyValue, key3.KeyValue)); - Assert.IsTrue(object.ReferenceEquals(key2.KeyValue, key3.KeyPrefix)); - Assert.AreEqual("helloworld", (string)key3); - } - - { - RedisKey key = "hello"; - var ret = key.Append("world"); - Assert.AreEqual("helloworld", (string)ret); - } - - { - RedisKey key1 = Encoding.UTF8.GetBytes("hello"); - RedisKey key2 = "world"; - var key3 = key1.Append(key2); - Assert.IsTrue(object.ReferenceEquals(key2.KeyValue, key3.KeyValue)); - Assert.IsTrue(object.ReferenceEquals(key1.KeyValue, key3.KeyPrefix)); - Assert.AreEqual("helloworld", (string)key3); - } - } - } -} diff --git a/StackExchange.Redis.Tests/KeysAndValues.cs b/StackExchange.Redis.Tests/KeysAndValues.cs deleted file mode 100644 index b731afa02..000000000 --- a/StackExchange.Redis.Tests/KeysAndValues.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System; -using System.Globalization; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class KeysAndValues - { - [Test] - public void TestValues() - { - RedisValue @default = default(RedisValue); - CheckNull(@default); - - RedisValue nullString = (string)null; - CheckNull(nullString); - - RedisValue nullBlob = (byte[])null; - CheckNull(nullBlob); - - RedisValue emptyString = ""; - CheckNotNull(emptyString); - - RedisValue emptyBlob = new byte[0]; - CheckNotNull(emptyBlob); - - RedisValue a0 = new string('a', 1); - CheckNotNull(a0); - RedisValue a1 = new string('a', 1); - CheckNotNull(a1); - RedisValue b0 = new [] { (byte)'b' }; - CheckNotNull(b0); - RedisValue b1 = new [] { (byte)'b' }; - CheckNotNull(b1); - - RedisValue i4 = 1; - CheckNotNull(i4); - RedisValue i8 = 1L; - CheckNotNull(i8); - - RedisValue bool1 = true; - CheckNotNull(bool1); - RedisValue bool2 = false; - CheckNotNull(bool2); - RedisValue bool3 = true; - CheckNotNull(bool3); - - CheckSame(a0, a0); - CheckSame(a1, a1); - CheckSame(a0, a1); - - CheckSame(b0, b0); - CheckSame(b1, b1); - CheckSame(b0, b1); - - CheckSame(i4, i4); - CheckSame(i8, i8); - CheckSame(i4, i8); - - CheckSame(bool1, bool3); - CheckNotSame(bool1, bool2); - } - - private void CheckSame(RedisValue x, RedisValue y) - { - Assert.IsTrue(Equals(x, y)); - Assert.IsTrue(x.Equals(y)); - Assert.IsTrue(y.Equals(x)); - Assert.IsTrue(x.GetHashCode() == y.GetHashCode()); - } - private void CheckNotSame(RedisValue x, RedisValue y) - { - Assert.IsFalse(Equals(x, y)); - Assert.IsFalse(x.Equals(y)); - Assert.IsFalse(y.Equals(x)); - Assert.IsFalse(x.GetHashCode() == y.GetHashCode()); // well, very unlikely - } - - private void CheckNotNull(RedisValue value) - { - Assert.IsFalse(value.IsNull); - Assert.IsNotNull((byte[])value); - Assert.IsNotNull((string)value); - Assert.AreNotEqual(-1, value.GetHashCode()); - - Assert.IsNotNull((string)value); - Assert.IsNotNull((byte[])value); - - CheckSame(value, value); - CheckNotSame(value, default(RedisValue)); - CheckNotSame(value, (string)null); - CheckNotSame(value, (byte[])null); - } - private void CheckNull(RedisValue value) - { - Assert.IsTrue(value.IsNull); - Assert.IsTrue(value.IsNullOrEmpty); - Assert.IsFalse(value.IsInteger); - Assert.AreEqual(-1, value.GetHashCode()); - - Assert.IsNull((string)value); - Assert.IsNull((byte[])value); - - Assert.AreEqual(0, (int)value); - Assert.AreEqual(0L, (long)value); - - CheckSame(value, value); - CheckSame(value, default(RedisValue)); - CheckSame(value, (string)null); - CheckSame(value, (byte[])null); - } - - - [Test] - public void ValuesAreConvertible() - { - RedisValue val = 123; - object o = val; - byte[] blob = (byte[])Convert.ChangeType(o, typeof(byte[])); - - Assert.AreEqual(3, blob.Length); - Assert.AreEqual((byte)'1', blob[0]); - Assert.AreEqual((byte)'2', blob[1]); - Assert.AreEqual((byte)'3', blob[2]); - - Assert.AreEqual((double)123, Convert.ToDouble(o)); - - IConvertible c = (IConvertible)o; - Assert.AreEqual((short)123, c.ToInt16(CultureInfo.InvariantCulture)); - Assert.AreEqual((int)123, c.ToInt32(CultureInfo.InvariantCulture)); - Assert.AreEqual((long)123, c.ToInt64(CultureInfo.InvariantCulture)); - Assert.AreEqual((float)123, c.ToSingle(CultureInfo.InvariantCulture)); - Assert.AreEqual("123", c.ToString(CultureInfo.InvariantCulture)); - Assert.AreEqual((double)123, c.ToDouble(CultureInfo.InvariantCulture)); - Assert.AreEqual((decimal)123, c.ToDecimal(CultureInfo.InvariantCulture)); - Assert.AreEqual((ushort)123, c.ToUInt16(CultureInfo.InvariantCulture)); - Assert.AreEqual((uint)123, c.ToUInt32(CultureInfo.InvariantCulture)); - Assert.AreEqual((ulong)123, c.ToUInt64(CultureInfo.InvariantCulture)); - - blob = (byte[])c.ToType(typeof(byte[]), CultureInfo.InvariantCulture); - Assert.AreEqual(3, blob.Length); - Assert.AreEqual((byte)'1', blob[0]); - Assert.AreEqual((byte)'2', blob[1]); - Assert.AreEqual((byte)'3', blob[2]); - } - - [Test] - public void CanBeDynamic() - { - RedisValue val = "abc"; - object o = val; - dynamic d = o; - byte[] blob = (byte[])d; // could be in a try/catch - Assert.AreEqual(3, blob.Length); - Assert.AreEqual((byte)'a', blob[0]); - Assert.AreEqual((byte)'b', blob[1]); - Assert.AreEqual((byte)'c', blob[2]); - } - - [Test] - public void TryParse() - { - { - RedisValue val = "1"; - int i; - Assert.IsTrue(val.TryParse(out i)); - Assert.AreEqual(1, i); - long l; - Assert.IsTrue(val.TryParse(out l)); - Assert.AreEqual(1L, l); - double d; - Assert.IsTrue(val.TryParse(out d)); - Assert.AreEqual(1.0, l); - } - - { - RedisValue val = "8675309"; - int i; - Assert.IsTrue(val.TryParse(out i)); - Assert.AreEqual(8675309, i); - long l; - Assert.IsTrue(val.TryParse(out l)); - Assert.AreEqual(8675309L, l); - double d; - Assert.IsTrue(val.TryParse(out d)); - Assert.AreEqual(8675309.0, l); - } - - { - RedisValue val = "3.14159"; - double d; - Assert.IsTrue(val.TryParse(out d)); - Assert.AreEqual(3.14159, d); - } - - { - RedisValue val = "not a real number"; - int i; - Assert.IsFalse(val.TryParse(out i)); - long l; - Assert.IsFalse(val.TryParse(out l)); - double d; - Assert.IsFalse(val.TryParse(out d)); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Lex.cs b/StackExchange.Redis.Tests/Lex.cs deleted file mode 100644 index dd4271b30..000000000 --- a/StackExchange.Redis.Tests/Lex.cs +++ /dev/null @@ -1,98 +0,0 @@ -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Lex : TestBase - { - - [Test] - public void QueryRangeAndLengthByLex() - { - using(var conn = Create()) - { - var db = conn.GetDatabase(); - RedisKey key = Me(); - db.KeyDelete(key); - - db.SortedSetAdd(key, - new SortedSetEntry[] - { - new SortedSetEntry("a", 0), - new SortedSetEntry("b", 0), - new SortedSetEntry("c", 0), - new SortedSetEntry("d", 0), - new SortedSetEntry("e", 0), - new SortedSetEntry("f", 0), - new SortedSetEntry("g", 0), - }); - - var set = db.SortedSetRangeByValue(key, default(RedisValue), "c"); - var count = db.SortedSetLengthByValue(key, default(RedisValue), "c"); - Equate(set, count, "a", "b", "c"); - - set = db.SortedSetRangeByValue(key, default(RedisValue), "c", Exclude.Stop); - count = db.SortedSetLengthByValue(key, default(RedisValue), "c", Exclude.Stop); - Equate(set, count, "a", "b"); - - set = db.SortedSetRangeByValue(key, "aaa", "g", Exclude.Stop); - count = db.SortedSetLengthByValue(key, "aaa", "g", Exclude.Stop); - Equate(set, count, "b", "c", "d", "e", "f"); - - set = db.SortedSetRangeByValue(key, "aaa", "g", Exclude.Stop, 1, 3); - Equate(set, set.Length, "c", "d", "e"); - } - } - - [Test] - public void RemoveRangeByLex() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(); - RedisKey key = Me(); - db.KeyDelete(key); - - db.SortedSetAdd(key, - new SortedSetEntry[] - { - new SortedSetEntry("aaaa", 0), - new SortedSetEntry("b", 0), - new SortedSetEntry("c", 0), - new SortedSetEntry("d", 0), - new SortedSetEntry("e", 0), - }); - db.SortedSetAdd(key, - new SortedSetEntry[] - { - new SortedSetEntry("foo", 0), - new SortedSetEntry("zap", 0), - new SortedSetEntry("zip", 0), - new SortedSetEntry("ALPHA", 0), - new SortedSetEntry("alpha", 0), - }); - - var set = db.SortedSetRangeByRank(key); - Equate(set, set.Length, "ALPHA", "aaaa", "alpha", "b", "c", "d", "e", "foo", "zap", "zip"); - - long removed = db.SortedSetRemoveRangeByValue(key, "alpha", "omega"); - Assert.AreEqual(6, removed); - - set = db.SortedSetRangeByRank(key); - Equate(set, set.Length, "ALPHA", "aaaa", "zap", "zip"); - } - } - - - - private void Equate(RedisValue[] actual, long count, params string[] expected) - { - Assert.AreEqual(count, expected.Length); - Assert.AreEqual(expected.Length, actual.Length); - for(int i = 0; i < actual.Length; i++) - { - Assert.AreEqual(expected[i], (string)actual[i]); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Lists.cs b/StackExchange.Redis.Tests/Lists.cs deleted file mode 100644 index 1b585eb2c..000000000 --- a/StackExchange.Redis.Tests/Lists.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Linq; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Lists : TestBase - { - [Test] - public void Ranges() - { - using(var conn = Create()) - { - var db = conn.GetDatabase(); - RedisKey key = Me(); - - db.KeyDelete(key, CommandFlags.FireAndForget); - db.ListRightPush(key, "abcdefghijklmnopqrstuvwxyz".Select(x => (RedisValue)x.ToString()).ToArray()); - - Assert.AreEqual(26, db.ListLength(key)); - Assert.AreEqual("abcdefghijklmnopqrstuvwxyz", string.Concat(db.ListRange(key))); - - var last10 = db.ListRange(key, -10, -1); - Assert.AreEqual("qrstuvwxyz", string.Concat(last10)); - db.ListTrim(key, 0, -11); - - Assert.AreEqual(16, db.ListLength(key)); - Assert.AreEqual("abcdefghijklmnop", string.Concat(db.ListRange(key))); - - - - } - } - } -} diff --git a/StackExchange.Redis.Tests/Locking.cs b/StackExchange.Redis.Tests/Locking.cs deleted file mode 100644 index 8b51ff539..000000000 --- a/StackExchange.Redis.Tests/Locking.cs +++ /dev/null @@ -1,271 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Locking : TestBase - { - [Test] - [TestCaseSource(nameof(TestModes))] - public void AggressiveParallel(TestMode testMode) - { - int count = 2; - int errorCount = 0; - ManualResetEvent evt = new ManualResetEvent(false); - using (var c1 = Create(testMode)) - using (var c2 = Create(testMode)) - { - WaitCallback cb = obj => - { - var conn = (IDatabase)obj; - conn.Multiplexer.ErrorMessage += delegate { Interlocked.Increment(ref errorCount); }; - - for (int i = 0; i < 1000; i++) - { - conn.LockTakeAsync("abc", "def", TimeSpan.FromSeconds(5)); - } - conn.Ping(); - if (Interlocked.Decrement(ref count) == 0) evt.Set(); - }; - int db = testMode == TestMode.Twemproxy ? 0 : 2; - ThreadPool.QueueUserWorkItem(cb, c1.GetDatabase(db)); - ThreadPool.QueueUserWorkItem(cb, c2.GetDatabase(db)); - evt.WaitOne(8000); - } - Assert.AreEqual(0, Interlocked.CompareExchange(ref errorCount, 0, 0)); - } - - protected override string GetConfiguration() - { - return PrimaryServer + ":" + PrimaryPortString; - } - [Test] - public void TestOpCountByVersionLocal_UpLevel() - { - using (var conn = Create()) - { - TestLockOpCountByVersion(conn, 1, false); - TestLockOpCountByVersion(conn, 1, true); - - //TestManualLockOpCountByVersion(conn, 5, false); - //TestManualLockOpCountByVersion(conn, 3, true); - } - } - //[Test] - //public void TestOpCountByVersionLocal_DownLevel() - //{ - // using (var conn = Config.GetUnsecuredConnection(open: false)) - // { - // conn.SetServerVersion(new Version(2, 6, 0), ServerType.Master); - // TestLockOpCountByVersion(conn, 5, false); - // TestLockOpCountByVersion(conn, 3, true); - // //TestManualLockOpCountByVersion(conn, 5, false); - // //TestManualLockOpCountByVersion(conn, 3, true); - // } - //} - - //[Test] - //public void TestOpCountByVersionRemote() - //{ - // using (var conn = Config.GetRemoteConnection(open: false)) - // { - // TestLockOpCountByVersion(conn, 1, false); - // TestLockOpCountByVersion(conn, 1, true); - // //TestManualLockOpCountByVersion(conn, 1, false); - // //TestManualLockOpCountByVersion(conn, 1, true); - // } - //} - public void TestLockOpCountByVersion(ConnectionMultiplexer conn, int expected, bool existFirst) - { - const int DB = 0, LockDuration = 30; - RedisKey Key = Me(); - - var db = conn.GetDatabase(DB); - db.KeyDelete(Key); - RedisValue newVal = "us:" + Guid.NewGuid().ToString(); - RedisValue expectedVal = newVal; - if (existFirst) - { - expectedVal = "other:" + Guid.NewGuid().ToString(); - db.StringSet(Key, expectedVal, TimeSpan.FromSeconds(LockDuration)); - } - long countBefore = GetServer(conn).GetCounters().Interactive.OperationCount; - - var taken = db.LockTake(Key, newVal, TimeSpan.FromSeconds(LockDuration)); - - long countAfter = GetServer(conn).GetCounters().Interactive.OperationCount; - var valAfter = db.StringGet(Key); - - Assert.AreEqual(!existFirst, taken, "lock taken"); - Assert.AreEqual(expectedVal, valAfter, "taker"); - Assert.AreEqual(expected, countAfter - countBefore, "expected ops"); - // note we get a ping from GetCounters - } - - private ConnectionMultiplexer Create(TestMode mode) - { - switch(mode) - { - case TestMode.MultiExec: - return Create(); - case TestMode.NoMultiExec: - return Create(disabledCommands: new[] { "multi", "exec" }); - case TestMode.Twemproxy: - return Create(proxy: Proxy.Twemproxy); - default: - throw new NotSupportedException(mode.ToString()); - } - } - - public enum TestMode - { - MultiExec, - NoMultiExec, - Twemproxy - } - public static IEnumerable TestModes() - { - return (TestMode[])Enum.GetValues(typeof(TestMode)); - } - [Test] - [TestCaseSource(nameof(TestModes))] - public void TakeLockAndExtend(TestMode mode) - { - bool withTran = mode == TestMode.MultiExec; - using (var conn = Create(mode)) - { - RedisValue right = Guid.NewGuid().ToString(), - wrong = Guid.NewGuid().ToString(); - - int DB = mode == TestMode.Twemproxy ? 0 : 7; - RedisKey Key = "lock-key"; - - var db = conn.GetDatabase(DB); - - db.KeyDelete(Key); - - - var t1 = db.LockTakeAsync(Key, right, TimeSpan.FromSeconds(20)); - var t1b = db.LockTakeAsync(Key, wrong, TimeSpan.FromSeconds(10)); - var t2 = db.LockQueryAsync(Key); - var t3 = withTran ? db.LockReleaseAsync(Key, wrong) : null; - var t4 = db.LockQueryAsync(Key); - var t5 = withTran ? db.LockExtendAsync(Key, wrong, TimeSpan.FromSeconds(60)) : null; - var t6 = db.LockQueryAsync(Key); - var t7 = db.KeyTimeToLiveAsync(Key); - var t8 = db.LockExtendAsync(Key, right, TimeSpan.FromSeconds(60)); - var t9 = db.LockQueryAsync(Key); - var t10 = db.KeyTimeToLiveAsync(Key); - var t11 = db.LockReleaseAsync(Key, right); - var t12 = db.LockQueryAsync(Key); - var t13 = db.LockTakeAsync(Key, wrong, TimeSpan.FromSeconds(10)); - - - Assert.IsNotNull(right); - Assert.IsNotNull(wrong); - Assert.AreNotEqual(right, wrong); - Assert.IsTrue(conn.Wait(t1), "1"); - Assert.IsFalse(conn.Wait(t1b), "1b"); - Assert.AreEqual(right, conn.Wait(t2), "2"); - if(withTran) Assert.IsFalse(conn.Wait(t3), "3"); - Assert.AreEqual(right, conn.Wait(t4), "4"); - if (withTran) Assert.IsFalse(conn.Wait(t5), "5"); - Assert.AreEqual(right, conn.Wait(t6), "6"); - var ttl = conn.Wait(t7).Value.TotalSeconds; - Assert.IsTrue(ttl > 0 && ttl <= 20, "7"); - Assert.IsTrue(conn.Wait(t8), "8"); - Assert.AreEqual(right, conn.Wait(t9), "9"); - ttl = conn.Wait(t10).Value.TotalSeconds; - Assert.IsTrue(ttl > 50 && ttl <= 60, "10"); - Assert.IsTrue(conn.Wait(t11), "11"); - Assert.IsNull((string)conn.Wait(t12), "12"); - Assert.IsTrue(conn.Wait(t13), "13"); - } - } - - - //public void TestManualLockOpCountByVersion(RedisConnection conn, int expected, bool existFirst) - //{ - // const int DB = 0, LockDuration = 30; - // const string Key = "TestManualLockOpCountByVersion"; - // conn.Wait(conn.Open()); - // conn.Keys.Remove(DB, Key); - // var newVal = "us:" + Config.CreateUniqueName(); - // string expectedVal = newVal; - // if (existFirst) - // { - // expectedVal = "other:" + Config.CreateUniqueName(); - // conn.Strings.Set(DB, Key, expectedVal, LockDuration); - // } - // int countBefore = conn.GetCounters().MessagesSent; - - // var tran = conn.CreateTransaction(); - // tran.AddCondition(Condition.KeyNotExists(DB, Key)); - // tran.Strings.Set(DB, Key, newVal, LockDuration); - // var taken = conn.Wait(tran.Execute()); - - // int countAfter = conn.GetCounters().MessagesSent; - // var valAfter = conn.Wait(conn.Strings.GetString(DB, Key)); - // Assert.AreEqual(!existFirst, taken, "lock taken (manual)"); - // Assert.AreEqual(expectedVal, valAfter, "taker (manual)"); - // Assert.AreEqual(expected, (countAfter - countBefore) - 1, "expected ops (including ping) (manual)"); - // // note we get a ping from GetCounters - //} - - - - [Test] - [TestCaseSource(nameof(TestModes))] - public void TestBasicLockNotTaken(TestMode testMode) - { - using (var conn = Create(testMode)) - { - int errorCount = 0; - conn.ErrorMessage += delegate { Interlocked.Increment(ref errorCount); }; - Task taken = null; - Task newValue = null; - Task ttl = null; - - const int LOOP = 50; - var db = conn.GetDatabase(0); - for (int i = 0; i < LOOP; i++) - { - db.KeyDeleteAsync("lock-not-exists"); - taken = db.LockTakeAsync("lock-not-exists", "new-value", TimeSpan.FromSeconds(10)); - newValue = db.StringGetAsync("lock-not-exists"); - ttl = db.KeyTimeToLiveAsync("lock-not-exists"); - } - Assert.IsTrue(conn.Wait(taken), "taken"); - Assert.AreEqual("new-value", (string)conn.Wait(newValue)); - var ttlValue = conn.Wait(ttl).Value.TotalSeconds; - Assert.IsTrue(ttlValue >= 8 && ttlValue <= 10, "ttl"); - - Assert.AreEqual(0, errorCount); - } - } - - [Test] - [TestCaseSource(nameof(TestModes))] - public void TestBasicLockTaken(TestMode testMode) - { - using (var conn = Create(testMode)) - { - var db = conn.GetDatabase(0); - db.KeyDelete("lock-exists"); - db.StringSet("lock-exists", "old-value", TimeSpan.FromSeconds(20)); - var taken = db.LockTakeAsync("lock-exists", "new-value", TimeSpan.FromSeconds(10)); - var newValue = db.StringGetAsync("lock-exists"); - var ttl = db.KeyTimeToLiveAsync("lock-exists"); - - Assert.IsFalse(conn.Wait(taken), "taken"); - Assert.AreEqual("old-value", (string)conn.Wait(newValue)); - var ttlValue = conn.Wait(ttl).Value.TotalSeconds; - Assert.IsTrue(ttlValue >= 18 && ttlValue <= 20, "ttl"); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Migrate.cs b/StackExchange.Redis.Tests/Migrate.cs deleted file mode 100644 index d5d2e8189..000000000 --- a/StackExchange.Redis.Tests/Migrate.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - public class Migrate : TestBase - { - public void Basic() - { - var fromConfig = new ConfigurationOptions { EndPoints = { { PrimaryServer, SecurePort } }, Password = SecurePassword }; - var toConfig = new ConfigurationOptions { EndPoints = { { PrimaryServer, PrimaryPort } } }; - using (var from = ConnectionMultiplexer.Connect(fromConfig)) - using (var to = ConnectionMultiplexer.Connect(toConfig)) - { - RedisKey key = Me(); - var fromDb = from.GetDatabase(); - var toDb = to.GetDatabase(); - fromDb.KeyDelete(key); - toDb.KeyDelete(key); - fromDb.StringSet(key, "foo"); - var dest = to.GetEndPoints(true).Single(); - fromDb.KeyMigrate(key, dest); - Assert.IsFalse(fromDb.KeyExists(key)); - Assert.IsTrue(toDb.KeyExists(key)); - string s = toDb.StringGet(key); - Assert.AreEqual("foo", s); - } - } - } -} diff --git a/StackExchange.Redis.Tests/MultiAdd.cs b/StackExchange.Redis.Tests/MultiAdd.cs deleted file mode 100644 index 59ed19bdd..000000000 --- a/StackExchange.Redis.Tests/MultiAdd.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System.Linq; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class MultiAdd : TestBase - { - [Test] - public void AddSortedSetEveryWay() - { - using(var conn = Create()) - { - var db = conn.GetDatabase(3); - - RedisKey key = Me(); - db.KeyDelete(key); - db.SortedSetAdd(key, "a", 1); - db.SortedSetAdd(key, new[] { - new SortedSetEntry("b", 2) }); - db.SortedSetAdd(key, new[] { - new SortedSetEntry("c", 3), - new SortedSetEntry("d", 4)}); - db.SortedSetAdd(key, new[] { - new SortedSetEntry("e", 5), - new SortedSetEntry("f", 6), - new SortedSetEntry("g", 7)}); - db.SortedSetAdd(key, new[] { - new SortedSetEntry("h", 8), - new SortedSetEntry("i", 9), - new SortedSetEntry("j", 10), - new SortedSetEntry("k", 11)}); - var vals = db.SortedSetRangeByScoreWithScores(key); - string s = string.Join(",", vals.OrderByDescending(x => x.Score).Select(x => x.Element)); - Assert.AreEqual("k,j,i,h,g,f,e,d,c,b,a", s); - s = string.Join(",", vals.OrderBy(x => x.Score).Select(x => x.Score)); - Assert.AreEqual("1,2,3,4,5,6,7,8,9,10,11", s); - } - } - - [Test] - public void AddHashEveryWay() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(3); - - RedisKey key = Me(); - db.KeyDelete(key); - db.HashSet(key, "a", 1); - db.HashSet(key, new[] { - new HashEntry("b", 2) }); - db.HashSet(key, new[] { - new HashEntry("c", 3), - new HashEntry("d", 4)}); - db.HashSet(key, new[] { - new HashEntry("e", 5), - new HashEntry("f", 6), - new HashEntry("g", 7)}); - db.HashSet(key, new[] { - new HashEntry("h", 8), - new HashEntry("i", 9), - new HashEntry("j", 10), - new HashEntry("k", 11)}); - var vals = db.HashGetAll(key); - string s = string.Join(",", vals.OrderByDescending(x => (double)x.Value).Select(x => x.Name)); - Assert.AreEqual("k,j,i,h,g,f,e,d,c,b,a", s); - s = string.Join(",", vals.OrderBy(x => (double)x.Value).Select(x => x.Value)); - Assert.AreEqual("1,2,3,4,5,6,7,8,9,10,11", s); - } - } - - [Test] - public void AddSetEveryWay() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(3); - - RedisKey key = Me(); - db.KeyDelete(key); - db.SetAdd(key, "a"); - db.SetAdd(key, new RedisValue[] { "b" }); - db.SetAdd(key, new RedisValue[] { "c", "d" }); - db.SetAdd(key, new RedisValue[] { "e", "f", "g" }); - db.SetAdd(key, new RedisValue[] { "h", "i", "j","k" }); - - var vals = db.SetMembers(key); - string s = string.Join(",", vals.OrderByDescending(x => x)); - Assert.AreEqual("k,j,i,h,g,f,e,d,c,b,a", s); - } - } - - [Test] - public void AddSetEveryWayNumbers() - { - using (var conn = Create()) - { - var db = conn.GetDatabase(3); - - RedisKey key = Me(); - db.KeyDelete(key); - db.SetAdd(key, "a"); - db.SetAdd(key, new RedisValue[] { "1" }); - db.SetAdd(key, new RedisValue[] { "11", "2" }); - db.SetAdd(key, new RedisValue[] { "10", "3", "1.5" }); - db.SetAdd(key, new RedisValue[] { "2.2", "-1", "s", "t" }); - - var vals = db.SetMembers(key); - string s = string.Join(",", vals.OrderByDescending(x => x)); - Assert.AreEqual("t,s,a,11,10,3,2.2,2,1.5,1,-1", s); - } - } - } -} diff --git a/StackExchange.Redis.Tests/MultiMaster.cs b/StackExchange.Redis.Tests/MultiMaster.cs deleted file mode 100644 index c82d285d9..000000000 --- a/StackExchange.Redis.Tests/MultiMaster.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Net; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class MultiMaster : TestBase - { - protected override string GetConfiguration() - { - return PrimaryServer + ":" + SecurePort + "," + PrimaryServer + ":" + PrimaryPort + ",password=" + SecurePassword; - } - - [Test] - public void CannotFlushSlave() - { - Assert.Throws(() => { - ConfigurationOptions config = GetMasterSlaveConfig(); - using (var conn = ConnectionMultiplexer.Connect(config)) - { - var servers = conn.GetEndPoints().Select(e => conn.GetServer(e)); - var slave = servers.First(x => x.IsSlave); - slave.FlushDatabase(); - } - }, - "Command cannot be issued to a slave: FLUSHDB"); - } - - [Test] - public void DeslaveGoesToPrimary() - { - ConfigurationOptions config = GetMasterSlaveConfig(); - using (var conn = ConnectionMultiplexer.Connect(config)) - { - - var primary = conn.GetServer(new IPEndPoint(IPAddress.Parse(PrimaryServer), PrimaryPort)); - var secondary = conn.GetServer(new IPEndPoint(IPAddress.Parse(PrimaryServer), SlavePort)); - - primary.Ping(); - secondary.Ping(); - - primary.MakeMaster(ReplicationChangeOptions.SetTiebreaker); - secondary.MakeMaster(ReplicationChangeOptions.None); - - primary.Ping(); - secondary.Ping(); - - using (var writer = new StringWriter()) - { - conn.Configure(writer); - string log = writer.ToString(); - - Assert.IsTrue(log.Contains("tie-break is unanimous at " + PrimaryServer + ":" + PrimaryPort), "unanimous"); - } - // k, so we know everyone loves 6379; is that what we get? - - var db = conn.GetDatabase(); - RedisKey key = Me(); - - EndPoint demandMaster, preferMaster, preferSlave, demandSlave; - preferMaster = db.IdentifyEndpoint(key, CommandFlags.PreferMaster); - demandMaster = db.IdentifyEndpoint(key, CommandFlags.DemandMaster); - preferSlave = db.IdentifyEndpoint(key, CommandFlags.PreferSlave); - - Assert.AreEqual(primary.EndPoint, demandMaster, "demand master"); - Assert.AreEqual(primary.EndPoint, preferMaster, "prefer master"); - Assert.AreEqual(primary.EndPoint, preferSlave, "prefer slave"); - - try - { - demandSlave = db.IdentifyEndpoint(key, CommandFlags.DemandSlave); - Assert.Fail("this should not have worked"); - } - catch (RedisConnectionException ex) - { - Assert.AreEqual("No connection is available to service this operation: EXISTS DeslaveGoesToPrimary", ex.Message); - } - - primary.MakeMaster(ReplicationChangeOptions.Broadcast | ReplicationChangeOptions.EnslaveSubordinates | ReplicationChangeOptions.SetTiebreaker); - - primary.Ping(); - secondary.Ping(); - - preferMaster = db.IdentifyEndpoint(key, CommandFlags.PreferMaster); - demandMaster = db.IdentifyEndpoint(key, CommandFlags.DemandMaster); - preferSlave = db.IdentifyEndpoint(key, CommandFlags.PreferSlave); - demandSlave = db.IdentifyEndpoint(key, CommandFlags.DemandSlave); - - Assert.AreEqual(primary.EndPoint, demandMaster, "demand master"); - Assert.AreEqual(primary.EndPoint, preferMaster, "prefer master"); - Assert.AreEqual(secondary.EndPoint, preferSlave, "prefer slave"); - Assert.AreEqual(secondary.EndPoint, preferSlave, "demand slave slave"); - - } - } - - private static ConfigurationOptions GetMasterSlaveConfig() - { - return new ConfigurationOptions - { - AllowAdmin = true, - SyncTimeout = 100000, - EndPoints = - { - { PrimaryServer, PrimaryPort }, - { PrimaryServer, SlavePort }, - } - }; - } - - [Test] - public void TestMultiNoTieBreak() - { - using (var log = new StringWriter()) - using (var conn = Create(log: log, tieBreaker: "")) - { - Console.WriteLine(log); - Assert.IsTrue(log.ToString().Contains("Choosing master arbitrarily")); - } - } - - [Test] - [TestCase(PrimaryServer + ":" + PrimaryPortString, PrimaryServer + ":" + PrimaryPortString, PrimaryServer + ":" + PrimaryPortString)] - [TestCase(PrimaryServer + ":" + SecurePortString, PrimaryServer + ":" + SecurePortString, PrimaryServer + ":" + SecurePortString)] - [TestCase(PrimaryServer + ":" + SecurePortString, PrimaryServer + ":" + PrimaryPortString, null)] - [TestCase(PrimaryServer + ":" + PrimaryPortString, PrimaryServer + ":" + SecurePortString, null)] - - [TestCase(null, PrimaryServer + ":" + PrimaryPortString, PrimaryServer + ":" + PrimaryPortString)] - [TestCase(PrimaryServer + ":" + PrimaryPortString, null, PrimaryServer + ":" + PrimaryPortString)] - [TestCase(null, PrimaryServer + ":" + SecurePortString, PrimaryServer + ":" + SecurePortString)] - [TestCase(PrimaryServer + ":" + SecurePortString, null, PrimaryServer + ":" + SecurePortString)] - [TestCase(null, null, null)] - - public void TestMultiWithTiebreak(string a, string b, string elected) - { - const string TieBreak = "__tie__"; - // set the tie-breakers to the expected state - using(var aConn = ConnectionMultiplexer.Connect(PrimaryServer + ":" + PrimaryPort)) - { - aConn.GetDatabase().StringSet(TieBreak, a); - } - using (var aConn = ConnectionMultiplexer.Connect(PrimaryServer + ":" + SecurePort + ",password=" + SecurePassword)) - { - aConn.GetDatabase().StringSet(TieBreak, b); - } - - // see what happens - using (var log = new StringWriter()) - using (var conn = Create(log: log, tieBreaker: TieBreak)) - { - string text = log.ToString(); - Console.WriteLine(text); - Assert.IsFalse(text.Contains("failed to nominate"), "failed to nominate"); - if (elected != null) - { - Assert.IsTrue(text.Contains("Elected: " + elected), "elected"); - } - int nullCount = (a == null ? 1 : 0) + (b == null ? 1 : 0); - if((a == b && nullCount == 0) || nullCount == 1) - { - Assert.IsTrue(text.Contains("tie-break is unanimous"), "unanimous"); - Assert.IsFalse(text.Contains("Choosing master arbitrarily"), "arbitrarily"); - } - else - { - Assert.IsFalse(text.Contains("tie-break is unanimous"), "unanimous"); - Assert.IsTrue(text.Contains("Choosing master arbitrarily"), "arbitrarily"); - } - } - } - } -} diff --git a/StackExchange.Redis.Tests/Naming.cs b/StackExchange.Redis.Tests/Naming.cs deleted file mode 100644 index be5548e83..000000000 --- a/StackExchange.Redis.Tests/Naming.cs +++ /dev/null @@ -1,258 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Naming - { - [Test] - [TestCase(typeof(IDatabase), false)] - [TestCase(typeof(IDatabaseAsync), true)] - [TestCase(typeof(Condition), false)] - public void CheckSignatures(Type type, bool isAsync) - { - // check that all methods and interfaces look appropriate for their sync/async nature - CheckName(type.GetTypeInfo(), isAsync); - var members = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); - foreach(var member in members) - { - if (member.Name.StartsWith("get_") || member.Name.StartsWith("set_") || member.Name.StartsWith("add_") || member.Name.StartsWith("remove_")) continue; - CheckMethod(member, isAsync); - } - } - - [Test] - public void ShowReadOnlyOperations() - { - var msg = typeof(ConnectionMultiplexer).GetTypeInfo().Assembly.GetType("StackExchange.Redis.Message"); - Assert.IsNotNull(msg, "Message"); - var cmd = typeof(ConnectionMultiplexer).GetTypeInfo().Assembly.GetType("StackExchange.Redis.RedisCommand"); - Assert.IsNotNull(cmd, "RedisCommand"); - var method = msg.GetMethod("IsMasterOnly", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); - Assert.IsNotNull(method, "IsMasterOnly"); - object[] args = new object[1]; - - List masterSlave = new List(); - List masterOnly = new List(); - foreach (var val in Enum.GetValues(cmd)) - { - args[0] = val; - bool isMasterOnly = (bool)method.Invoke(null, args); - (isMasterOnly ? masterOnly : masterSlave).Add(val); - - if(!isMasterOnly) - { - Console.WriteLine(val); - } - } - Console.WriteLine("master-only: {0}, vs master/slave: {1}", masterOnly.Count, masterSlave.Count); - Console.WriteLine(); - Console.WriteLine("master-only:"); - foreach (var val in masterOnly) Console.WriteLine(val); - Console.WriteLine(); - Console.WriteLine("master/slave:"); - foreach (var val in masterSlave) Console.WriteLine(val); - } - - [Test] - [TestCase(typeof(IDatabase))] - [TestCase(typeof(IDatabaseAsync))] - public void CheckDatabaseMethodsUseKeys(Type type) - { - foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) - { - if (IgnoreMethodConventions(method)) continue; - - switch(method.Name) - { - case "KeyRandom": - case "KeyRandomAsync": - case "Publish": - case "PublishAsync": - continue; // they're fine, but don't want to widen check to return type - } - - bool usesKey = method.GetParameters().Any(p => UsesKey(p.ParameterType)); - Assert.IsTrue(usesKey, type.Name + ":" + method.Name); - } - } - static bool UsesKey(Type type) - { - if (type == typeof(RedisKey)) return true; - - if(type.IsArray) - { - if (UsesKey(type.GetElementType())) return true; - } - if(type.GetTypeInfo().IsGenericType) // KVP, etc - { - var args = type.GetGenericArguments(); - if (args.Any(UsesKey)) return true; - } - return false; - } - - static bool IgnoreMethodConventions(MethodInfo method) - { - string name = method.Name; - if (name.StartsWith("get_") || name.StartsWith("set_") || name.StartsWith("add_") || name.StartsWith("remove_")) return true; - switch(name) - { - case "CreateBatch": - case "CreateTransaction": - case "IsConnected": - case "SetScan": - case "SortedSetScan": - case "HashScan": - case "SubscribedEndpoint": - return true; - } - return false; - } - [Test] - [TestCase(typeof(IDatabase), typeof(IDatabaseAsync))] - [TestCase(typeof(IDatabaseAsync), typeof(IDatabase))] - public void CheckSyncAsyncMethodsMatch(Type from, Type to) - { - const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; - int count = 0; - foreach (var method in from.GetMethods(flags)) - { - if (IgnoreMethodConventions(method)) continue; - - string name = method.Name, huntName; - - if (name.EndsWith("Async")) huntName = name.Substring(0, name.Length - 5); - else huntName = name + "Async"; - - Type huntType; - if (method.ReturnType == null || method.ReturnType == typeof(void)) - { - huntType = typeof(Task); - } - else if (method.ReturnType == typeof(Task)) - { - huntType = null; - } - else if (method.ReturnType.GetTypeInfo().IsSubclassOf(typeof(Task))) - { - huntType = method.ReturnType.GetGenericArguments()[0]; - } - else - { - huntType = typeof(Task<>).MakeGenericType(method.ReturnType); - } - var pFrom = method.GetParameters(); - Type[] args = pFrom.Select(x => x.ParameterType).ToArray(); - Assert.AreEqual(typeof(CommandFlags), args.Last()); -#if !CORE_CLR - var found = to.GetMethod(huntName, flags, null, method.CallingConvention, args, null); -#else - var found = to.GetMethods(flags) - .SingleOrDefault(m => m.Name == huntName && m.HasMatchingParameterTypes(args)); -#endif - Assert.IsNotNull(found, "Found " + name + ", no " + huntName); - var pTo = found.GetParameters(); - - for(int i = 0; i < pFrom.Length;i++) - { - Assert.AreEqual(pFrom[i].Name, pTo[i].Name, method.Name + ":" + pFrom[i].Name); - Assert.AreEqual(pFrom[i].ParameterType, pTo[i].ParameterType, method.Name + ":" + pFrom[i].Name); - } - - - count++; - } - Console.WriteLine("Validated: {0} ({1} methods)", from.Name, count); - } - - static readonly Type ignoreType = typeof(ConnectionMultiplexer).GetTypeInfo().Assembly.GetType("StackExchange.Redis.IgnoreNamePrefixAttribute"); - void CheckMethod(MethodInfo method, bool isAsync) - { - -#if DEBUG -#if !CORE_CLR - bool ignorePrefix = ignoreType != null && Attribute.IsDefined(method, ignoreType); -#else - bool ignorePrefix = ignoreType != null && method.IsDefined(ignoreType); -#endif - if (ignorePrefix) - { -#if !CORE_CLR - Attribute attrib = Attribute.GetCustomAttribute(method, ignoreType); -#else - Attribute attrib = method.GetCustomAttribute(ignoreType); -#endif - if ((bool)attrib.GetType().GetProperty("IgnoreEntireMethod").GetValue(attrib)) - { - return; - } - } - string shortName = method.Name, fullName = method.DeclaringType.Name + "." + shortName; - CheckName(method, isAsync); - if (!ignorePrefix) - { - Assert.IsTrue(shortName.StartsWith("Hash") || shortName.StartsWith("Key") - || shortName.StartsWith("String") || shortName.StartsWith("List") - || shortName.StartsWith("SortedSet") || shortName.StartsWith("Set") - || shortName.StartsWith("Debug") || shortName.StartsWith("Lock") - || shortName.StartsWith("Script") || shortName.StartsWith("HyperLogLog") - , fullName + ":Prefix"); - } - - Assert.IsFalse(shortName.Contains("If"), fullName + ":If"); // should probably be a When option - - var returnType = method.ReturnType ?? typeof(void); - if (isAsync) - { - Assert.IsTrue(typeof(Task).IsAssignableFrom(returnType), fullName + ":Task"); - } - else - { - Assert.IsFalse(typeof(Task).IsAssignableFrom(returnType), fullName + ":Task"); - } -#endif - } - - void CheckName(MemberInfo member, bool isAsync) - { - if (isAsync) Assert.IsTrue(member.Name.EndsWith("Async"), member.Name + ":Name - end *Async"); - else Assert.IsFalse(member.Name.EndsWith("Async"), member.Name + ":Name - don't end *Async"); - } - } - - public static class ReflectionExtensions - { -#if !CORE_CLR - public static Type GetTypeInfo(this Type type) - { - return type; - } - -#else - public static bool HasMatchingParameterTypes(this MethodInfo method, Type[] paramTypes) - { - var types = method.GetParameters().Select(pi => pi.ParameterType).ToArray(); - if (types.Length != paramTypes.Length) - { - return false; - } - - for (int i = 0; i < types.Length; i++) - { - if (types[i] != paramTypes[i]) - { - return false; - } - } - - return true; - } -#endif - } -} diff --git a/StackExchange.Redis.Tests/PreserveOrder.cs b/StackExchange.Redis.Tests/PreserveOrder.cs deleted file mode 100644 index f09c91b52..000000000 --- a/StackExchange.Redis.Tests/PreserveOrder.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class PreserveOrder : TestBase - { - [Test] - [TestCase(true)] - [TestCase(false)] - public void Execute(bool preserveAsyncOrder) - { - using (var conn = Create()) - { - var sub = conn.GetSubscriber(); - var received = new List(); - Console.WriteLine("Subscribing..."); - const int COUNT = 1000; - sub.Subscribe("foo", (channel, message) => - { - lock (received) - { - received.Add((int)message); - if (received.Count == COUNT) - Monitor.PulseAll(received); // wake the test rig - } - Thread.Sleep(1); // you kinda need to be slow, otherwise - // the pool will end up doing everything on one thread - }); - conn.PreserveAsyncOrder = preserveAsyncOrder; - Console.WriteLine(); - Console.WriteLine("Sending ({0})...", (preserveAsyncOrder ? "preserved order" : "any order")); - lock (received) - { - received.Clear(); - // we'll also use received as a wait-detection mechanism; sneaky - - // note: this does not do any cheating; - // it all goes to the server and back - for (int i = 0; i < COUNT; i++) - { - sub.Publish("foo", i); - } - - Console.WriteLine("Allowing time for delivery etc..."); - var watch = Stopwatch.StartNew(); - if (!Monitor.Wait(received, 10000)) - { - Console.WriteLine("Timed out; expect less data"); - } - watch.Stop(); - Console.WriteLine("Checking..."); - lock (received) - { - Console.WriteLine("Received: {0} in {1}ms", received.Count, watch.ElapsedMilliseconds); - int wrongOrder = 0; - for (int i = 0; i < Math.Min(COUNT, received.Count); i++) - { - if (received[i] != i) wrongOrder++; - } - Console.WriteLine("Out of order: " + wrongOrder); - if (preserveAsyncOrder) Assert.AreEqual(0, wrongOrder); - else Assert.AreNotEqual(0, wrongOrder); - } - } - } - } - } -} diff --git a/StackExchange.Redis.Tests/Profiling.cs b/StackExchange.Redis.Tests/Profiling.cs deleted file mode 100644 index 04f47cdb6..000000000 --- a/StackExchange.Redis.Tests/Profiling.cs +++ /dev/null @@ -1,596 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -#if CORE_CLR -using System.Reflection; -#endif -using System.Threading.Tasks; -using NUnit.Framework; -using System.Threading; -using System.Collections.Concurrent; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Profiling : TestBase - { - class TestProfiler : IProfiler - { - public object MyContext = new object(); - - public object GetContext() - { - return MyContext; - } - } - - [Test] - public void Simple() - { - using (var conn = Create()) - { - var profiler = new TestProfiler(); - - conn.RegisterProfiler(profiler); - conn.BeginProfiling(profiler.MyContext); - var db = conn.GetDatabase(4); - db.StringSet("hello", "world"); - var val = db.StringGet("hello"); - Assert.AreEqual("world", (string)val); - - var cmds = conn.FinishProfiling(profiler.MyContext); - Assert.AreEqual(2, cmds.Count()); - - var set = cmds.SingleOrDefault(cmd => cmd.Command == "SET"); - Assert.IsNotNull(set); - var get = cmds.SingleOrDefault(cmd => cmd.Command == "GET"); - Assert.IsNotNull(get); - - Assert.IsTrue(set.CommandCreated <= get.CommandCreated); - - Assert.AreEqual(4, set.Db); - Assert.AreEqual(conn.GetEndPoints()[0], set.EndPoint); - Assert.IsTrue(set.CreationToEnqueued > TimeSpan.Zero); - Assert.IsTrue(set.EnqueuedToSending > TimeSpan.Zero); - Assert.IsTrue(set.SentToResponse > TimeSpan.Zero); - Assert.IsTrue(set.ResponseToCompletion > TimeSpan.Zero); - Assert.IsTrue(set.ElapsedTime > TimeSpan.Zero); - Assert.IsTrue(set.ElapsedTime > set.CreationToEnqueued && set.ElapsedTime > set.EnqueuedToSending && set.ElapsedTime > set.SentToResponse); - Assert.IsTrue(set.RetransmissionOf == null); - Assert.IsTrue(set.RetransmissionReason == null); - - Assert.AreEqual(4, get.Db); - Assert.AreEqual(conn.GetEndPoints()[0], get.EndPoint); - Assert.IsTrue(get.CreationToEnqueued > TimeSpan.Zero); - Assert.IsTrue(get.EnqueuedToSending > TimeSpan.Zero); - Assert.IsTrue(get.SentToResponse > TimeSpan.Zero); - Assert.IsTrue(get.ResponseToCompletion > TimeSpan.Zero); - Assert.IsTrue(get.ElapsedTime > TimeSpan.Zero); - Assert.IsTrue(get.ElapsedTime > get.CreationToEnqueued && get.ElapsedTime > get.EnqueuedToSending && get.ElapsedTime > get.SentToResponse); - Assert.IsTrue(get.RetransmissionOf == null); - Assert.IsTrue(get.RetransmissionReason == null); - } - } - - [Test] - public void ManyThreads() - { - using (var conn = Create()) - { - var profiler = new TestProfiler(); - - conn.RegisterProfiler(profiler); - conn.BeginProfiling(profiler.MyContext); - - var threads = new List(); - - for (var i = 0; i < 16; i++) - { - var db = conn.GetDatabase(i); - - threads.Add( - new Thread( - delegate() - { - var threadTasks = new List(); - - for (var j = 0; j < 1000; j++) - { - var task = db.StringSetAsync("" + j, "" + j); - threadTasks.Add(task); - } - - Task.WaitAll(threadTasks.ToArray()); - } - ) - ); - } - - threads.ForEach(thread => thread.Start()); - threads.ForEach(thread => thread.Join()); - - var allVals = conn.FinishProfiling(profiler.MyContext); - - var kinds = allVals.Select(cmd => cmd.Command).Distinct().ToList(); - Assert.IsTrue(kinds.Count <= 2); - Assert.IsTrue(kinds.Contains("SET")); - if (kinds.Count == 2 && !kinds.Contains("SELECT")) - { - Assert.Fail("Non-SET, Non-SELECT command seen"); - } - - Assert.AreEqual(16 * 1000, allVals.Count()); - Assert.AreEqual(16, allVals.Select(cmd => cmd.Db).Distinct().Count()); - - for (var i = 0; i < 16; i++) - { - var setsInDb = allVals.Where(cmd => cmd.Db == i && cmd.Command == "SET").Count(); - Assert.AreEqual(1000, setsInDb); - } - } - } - - class TestProfiler2 : IProfiler - { - ConcurrentDictionary Contexts = new ConcurrentDictionary(); - - public void RegisterContext(object context) - { - Contexts[Thread.CurrentThread.ManagedThreadId] = context; - } - - public object GetContext() - { - object ret; - if (!Contexts.TryGetValue(Thread.CurrentThread.ManagedThreadId, out ret)) ret = null; - - return ret; - } - } - - [Test] - public void ManyContexts() - { - using (var conn = Create()) - { - var profiler = new TestProfiler2(); - conn.RegisterProfiler(profiler); - - var perThreadContexts = new List(); - for (var i = 0; i < 16; i++) - { - perThreadContexts.Add(new object()); - } - - var threads = new List(); - - var results = new IEnumerable[16]; - - for (var i = 0; i < 16; i++) - { - var ix = i; - var thread = - new Thread( - delegate() - { - var ctx = perThreadContexts[ix]; - profiler.RegisterContext(ctx); - - conn.BeginProfiling(ctx); - var db = conn.GetDatabase(ix); - - var allTasks = new List(); - - for (var j = 0; j < 1000; j++) - { - allTasks.Add(db.StringGetAsync("hello" + ix)); - allTasks.Add(db.StringSetAsync("hello" + ix, "world" + ix)); - } - - Task.WaitAll(allTasks.ToArray()); - - results[ix] = conn.FinishProfiling(ctx); - } - ); - - threads.Add(thread); - } - - threads.ForEach(t => t.Start()); - threads.ForEach(t => t.Join()); - - for (var i = 0; i < results.Length; i++) - { - var res = results[i]; - Assert.IsNotNull(res); - - var numGets = res.Count(r => r.Command == "GET"); - var numSets = res.Count(r => r.Command == "SET"); - - Assert.AreEqual(1000, numGets); - Assert.AreEqual(1000, numSets); - Assert.IsTrue(res.All(cmd => cmd.Db == i)); - } - } - } - - class TestProfiler3 : IProfiler - { - ConcurrentDictionary Contexts = new ConcurrentDictionary(); - - public void RegisterContext(object context) - { - Contexts[Thread.CurrentThread.ManagedThreadId] = context; - } - - public object AnyContext() - { - return Contexts.First().Value; - } - - public void Reset() - { - Contexts.Clear(); - } - - public object GetContext() - { - object ret; - if (!Contexts.TryGetValue(Thread.CurrentThread.ManagedThreadId, out ret)) ret = null; - - return ret; - } - } - - // This is a separate method for target=DEBUG purposes. - // In release builds, the runtime is smart enough to figure out - // that the contexts are unreachable and should be collected but in - // debug builds... well, it's not very smart. - object LeaksCollectedAndRePooled_Initialize(ConnectionMultiplexer conn, int threadCount) - { - var profiler = new TestProfiler3(); - conn.RegisterProfiler(profiler); - - var perThreadContexts = new List(); - for (var i = 0; i < threadCount; i++) - { - perThreadContexts.Add(new object()); - } - - var threads = new List(); - - var results = new IEnumerable[threadCount]; - - for (var i = 0; i < threadCount; i++) - { - var ix = i; - var thread = - new Thread( - delegate() - { - var ctx = perThreadContexts[ix]; - profiler.RegisterContext(ctx); - - conn.BeginProfiling(ctx); - var db = conn.GetDatabase(ix); - - var allTasks = new List(); - - for (var j = 0; j < 1000; j++) - { - allTasks.Add(db.StringGetAsync("hello" + ix)); - allTasks.Add(db.StringSetAsync("hello" + ix, "world" + ix)); - } - - Task.WaitAll(allTasks.ToArray()); - - // intentionally leaking! - } - ); - - threads.Add(thread); - } - - threads.ForEach(t => t.Start()); - threads.ForEach(t => t.Join()); - - var anyContext = profiler.AnyContext(); - profiler.Reset(); - - return anyContext; - } - - [Test] - public void LeaksCollectedAndRePooled() - { - const int ThreadCount = 16; - - using (var conn = Create()) - { - var anyContext = LeaksCollectedAndRePooled_Initialize(conn, ThreadCount); - - // force collection of everything but `anyContext` - GC.Collect(3, GCCollectionMode.Forced, blocking: true); - GC.WaitForPendingFinalizers(); - - Thread.Sleep(TimeSpan.FromMinutes(1.01)); - conn.FinishProfiling(anyContext); - - // make sure we haven't left anything in the active contexts dictionary - Assert.AreEqual(0, conn.profiledCommands.ContextCount); - Assert.AreEqual(ThreadCount, ConcurrentProfileStorageCollection.AllocationCount); - Assert.AreEqual(ThreadCount, ConcurrentProfileStorageCollection.CountInPool()); - } - } - - [Test] - public void ReuseStorage() - { - const int ThreadCount = 16; - - // have to reset so other tests don't clober - ConcurrentProfileStorageCollection.AllocationCount = 0; - - using (var conn = Create()) - { - var profiler = new TestProfiler2(); - conn.RegisterProfiler(profiler); - - var perThreadContexts = new List(); - for (var i = 0; i < 16; i++) - { - perThreadContexts.Add(new object()); - } - - var threads = new List(); - - var results = new List>[16]; - for (var i = 0; i < 16; i++) - { - results[i] = new List>(); - } - - for (var i = 0; i < ThreadCount; i++) - { - var ix = i; - var thread = - new Thread( - delegate() - { - for (var k = 0; k < 10; k++) - { - var ctx = perThreadContexts[ix]; - profiler.RegisterContext(ctx); - - conn.BeginProfiling(ctx); - var db = conn.GetDatabase(ix); - - var allTasks = new List(); - - for (var j = 0; j < 1000; j++) - { - allTasks.Add(db.StringGetAsync("hello" + ix)); - allTasks.Add(db.StringSetAsync("hello" + ix, "world" + ix)); - } - - Task.WaitAll(allTasks.ToArray()); - - results[ix].Add(conn.FinishProfiling(ctx)); - } - } - ); - - threads.Add(thread); - } - - threads.ForEach(t => t.Start()); - threads.ForEach(t => t.Join()); - - // only 16 allocations can ever be in flight at once - var allocCount = ConcurrentProfileStorageCollection.AllocationCount; - Assert.IsTrue(allocCount <= ThreadCount, allocCount.ToString()); - - // correctness check for all allocations - for (var i = 0; i < results.Length; i++) - { - var resList = results[i]; - foreach (var res in resList) - { - Assert.IsNotNull(res); - - var numGets = res.Count(r => r.Command == "GET"); - var numSets = res.Count(r => r.Command == "SET"); - - Assert.AreEqual(1000, numGets); - Assert.AreEqual(1000, numSets); - Assert.IsTrue(res.All(cmd => cmd.Db == i)); - } - } - - // no crossed streams - var everything = results.SelectMany(r => r).ToList(); - for (var i = 0; i < everything.Count; i++) - { - for (var j = 0; j < everything.Count; j++) - { - if (i == j) continue; - - if (object.ReferenceEquals(everything[i], everything[j])) - { - Assert.Fail("Profilings were jumbled"); - } - } - } - } - } - - [Test] - public void LowAllocationEnumerable() - { - const int OuterLoop = 10000; - - using(var conn = Create()) - { - var profiler = new TestProfiler(); - conn.RegisterProfiler(profiler); - - conn.BeginProfiling(profiler.MyContext); - - var db = conn.GetDatabase(); - - var allTasks = new List>(); - - foreach (var i in Enumerable.Range(0, OuterLoop)) - { - var t = - db.StringSetAsync("foo" + i, "bar" + i) - .ContinueWith( - async _ => - { - return (string)(await db.StringGetAsync("foo" + i).ConfigureAwait(false)); - } - ); - - var finalResult = t.Unwrap(); - allTasks.Add(finalResult); - } - - conn.WaitAll(allTasks.ToArray()); - - var res = conn.FinishProfiling(profiler.MyContext); - Assert.IsTrue(res.GetType().GetTypeInfo().IsValueType); - - using(var e = res.GetEnumerator()) - { - Assert.IsTrue(e.GetType().GetTypeInfo().IsValueType); - - Assert.IsTrue(e.MoveNext()); - var i = e.Current; - - e.Reset(); - Assert.IsTrue(e.MoveNext()); - var j = e.Current; - - Assert.IsTrue(object.ReferenceEquals(i, j)); - } - - Assert.AreEqual(OuterLoop * 2, res.Count()); - Assert.AreEqual(OuterLoop, res.Count(r => r.Command == "GET")); - Assert.AreEqual(OuterLoop, res.Count(r => r.Command == "SET")); - } - } - - class ToyProfiler : IProfiler - { - public ConcurrentDictionary Contexts = new ConcurrentDictionary(); - - public object GetContext() - { - object ctx; - if (!Contexts.TryGetValue(Thread.CurrentThread, out ctx)) ctx = null; - - return ctx; - } - } - - [Test] - public void ProfilingMD_Ex1() - { - using (var c = Create()) - { - ConnectionMultiplexer conn = c; - var profiler = new ToyProfiler(); - var thisGroupContext = new object(); - - conn.RegisterProfiler(profiler); - - var threads = new List(); - - for (var i = 0; i < 16; i++) - { - var db = conn.GetDatabase(i); - - var thread = - new Thread( - delegate() - { - var threadTasks = new List(); - - for (var j = 0; j < 1000; j++) - { - var task = db.StringSetAsync("" + j, "" + j); - threadTasks.Add(task); - } - - Task.WaitAll(threadTasks.ToArray()); - } - ); - - profiler.Contexts[thread] = thisGroupContext; - - threads.Add(thread); - } - - conn.BeginProfiling(thisGroupContext); - - threads.ForEach(thread => thread.Start()); - threads.ForEach(thread => thread.Join()); - - IEnumerable timings = conn.FinishProfiling(thisGroupContext); - - Assert.AreEqual(16000, timings.Count()); - } - } - - [Test] - public void ProfilingMD_Ex2() - { - using (var c = Create()) - { - ConnectionMultiplexer conn = c; - var profiler = new ToyProfiler(); - - conn.RegisterProfiler(profiler); - - var threads = new List(); - - var perThreadTimings = new ConcurrentDictionary>(); - - for (var i = 0; i < 16; i++) - { - var db = conn.GetDatabase(i); - - var thread = - new Thread( - delegate() - { - var threadTasks = new List(); - - conn.BeginProfiling(Thread.CurrentThread); - - for (var j = 0; j < 1000; j++) - { - var task = db.StringSetAsync("" + j, "" + j); - threadTasks.Add(task); - } - - Task.WaitAll(threadTasks.ToArray()); - - perThreadTimings[Thread.CurrentThread] = conn.FinishProfiling(Thread.CurrentThread).ToList(); - } - ); - - profiler.Contexts[thread] = thread; - - threads.Add(thread); - } - - threads.ForEach(thread => thread.Start()); - threads.ForEach(thread => thread.Join()); - - Assert.AreEqual(16, perThreadTimings.Count); - Assert.IsTrue(perThreadTimings.All(kv => kv.Value.Count == 1000)); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Program.cs b/StackExchange.Redis.Tests/Program.cs deleted file mode 100644 index 43a7e60e1..000000000 --- a/StackExchange.Redis.Tests/Program.cs +++ /dev/null @@ -1,18 +0,0 @@ -#if NUNITLITE && !CORE_CLR -using System; -using System.Reflection; -using NUnit.Common; -using NUnitLite; - -namespace StackExchange.Redis.Tests -{ - public class Program - { - public static int Main(string[] args) - { - return new AutoRun(typeof(TestBase).GetTypeInfo().Assembly) - .Execute(args, new ExtendedTextWrapper(Console.Out), Console.In); - } - } -} -#endif \ No newline at end of file diff --git a/StackExchange.Redis.Tests/Properties/AssemblyInfo.cs b/StackExchange.Redis.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 1337b6083..000000000 --- a/StackExchange.Redis.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("StackExchange.Redis.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("StackExchange.Redis.Tests")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("70fae8f5-4f2f-4422-92c6-05bde74cdd52")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/StackExchange.Redis.Tests/PubSub.cs b/StackExchange.Redis.Tests/PubSub.cs deleted file mode 100644 index 2c7bc4863..000000000 --- a/StackExchange.Redis.Tests/PubSub.cs +++ /dev/null @@ -1,427 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class PubSub : TestBase - { - - [Test] - public void ExplicitPublishMode() - { - using(var mx = Create(channelPrefix: "foo:")) - { - var pub = mx.GetSubscriber(); - int a = 0, b = 0, c = 0, d = 0; - pub.Subscribe(new RedisChannel("*bcd", RedisChannel.PatternMode.Literal), (x, y) => - { - Interlocked.Increment(ref a); - }); - pub.Subscribe(new RedisChannel("a*cd", RedisChannel.PatternMode.Pattern), (x, y) => - { - Interlocked.Increment(ref b); - }); - pub.Subscribe(new RedisChannel("ab*d", RedisChannel.PatternMode.Auto), (x, y) => - { - Interlocked.Increment(ref c); - }); - pub.Subscribe("abc*", (x, y) => - { - Interlocked.Increment(ref d); - }); - - Thread.Sleep(1000); - pub.Publish("abcd", "efg"); - Thread.Sleep(500); - Assert.AreEqual(0, VolatileWrapper.Read(ref a), "a1"); - Assert.AreEqual(1, VolatileWrapper.Read(ref b), "b1"); - Assert.AreEqual(1, VolatileWrapper.Read(ref c), "c1"); - Assert.AreEqual(1, VolatileWrapper.Read(ref d), "d1"); - - pub.Publish("*bcd", "efg"); - Thread.Sleep(500); - Assert.AreEqual(1, VolatileWrapper.Read(ref a), "a2"); - //Assert.AreEqual(1, VolatileWrapper.Read(ref b), "b2"); - //Assert.AreEqual(1, VolatileWrapper.Read(ref c), "c2"); - //Assert.AreEqual(1, VolatileWrapper.Read(ref d), "d2"); - - } - } - - [Test] - [TestCase(true, null, false)] - [TestCase(false, null, false)] - [TestCase(true, "", false)] - [TestCase(false, "", false)] - [TestCase(true, "Foo:", false)] - [TestCase(false, "Foo:", false)] - [TestCase(true, null, true)] - [TestCase(false, null, true)] - [TestCase(true, "", true)] - [TestCase(false, "", true)] - [TestCase(true, "Foo:", true)] - [TestCase(false, "Foo:", true)] - public void TestBasicPubSub(bool preserveOrder, string channelPrefix, bool wildCard) - { - using (var muxer = Create(channelPrefix: channelPrefix)) - { - muxer.PreserveAsyncOrder = preserveOrder; - var pub = GetServer(muxer); - var sub = muxer.GetSubscriber(); - Ping(muxer, pub, sub); - HashSet received = new HashSet(); - int secondHandler = 0; - string subChannel = wildCard ? "a*c" : "abc"; - const string pubChannel = "abc"; - Action handler1 = (channel, payload) => - { - lock (received) - { - if (channel == pubChannel) - { - received.Add(payload); - } else - { - Console.WriteLine((string)channel); - } - } - }, handler2 = (channel, payload) => - { - Interlocked.Increment(ref secondHandler); - }; - sub.Subscribe(subChannel, handler1); - sub.Subscribe(subChannel, handler2); - - lock (received) - { - Assert.AreEqual(0, received.Count); - } - Assert.AreEqual(0, VolatileWrapper.Read(ref secondHandler)); - var count = sub.Publish(pubChannel, "def"); - - Ping(muxer, pub, sub, 3); - - lock (received) - { - Assert.AreEqual(1, received.Count); - } - Assert.AreEqual(1, VolatileWrapper.Read(ref secondHandler)); - - // unsubscribe from first; should still see second - sub.Unsubscribe(subChannel, handler1); - count = sub.Publish(pubChannel, "ghi"); - Ping(muxer, pub, sub); - lock(received) - { - Assert.AreEqual(1, received.Count); - } - Assert.AreEqual(2, VolatileWrapper.Read(ref secondHandler)); - Assert.AreEqual(1, count); - - // unsubscribe from second; should see nothing this time - sub.Unsubscribe(subChannel, handler2); - count = sub.Publish(pubChannel, "ghi"); - Ping(muxer, pub, sub); - lock (received) - { - Assert.AreEqual(1, received.Count); - } - Assert.AreEqual(2, VolatileWrapper.Read(ref secondHandler)); - Assert.AreEqual(0, count); - } - } - - [Test] - [TestCase(true)] - [TestCase(false)] - public void TestBasicPubSubFireAndForget(bool preserveOrder) - { - using (var muxer = Create()) - { - muxer.PreserveAsyncOrder = preserveOrder; - var pub = GetServer(muxer); - var sub = muxer.GetSubscriber(); - - RedisChannel key = Guid.NewGuid().ToString(); - HashSet received = new HashSet(); - int secondHandler = 0; - Ping(muxer, pub, sub); - sub.Subscribe(key, (channel, payload) => - { - lock (received) - { - if (channel == key) - { - received.Add(payload); - } - } - }, CommandFlags.FireAndForget); - - - sub.Subscribe(key, (channel, payload) => - { - Interlocked.Increment(ref secondHandler); - }, CommandFlags.FireAndForget); - - lock (received) - { - Assert.AreEqual(0, received.Count); - } - Assert.AreEqual(0, VolatileWrapper.Read(ref secondHandler)); - Ping(muxer, pub, sub); - var count = sub.Publish(key, "def", CommandFlags.FireAndForget); - Ping(muxer, pub, sub); - - lock (received) - { - Assert.AreEqual(1, received.Count); - } - Assert.AreEqual(1, VolatileWrapper.Read(ref secondHandler)); - - sub.Unsubscribe(key); - count = sub.Publish(key, "ghi", CommandFlags.FireAndForget); - - Ping(muxer, pub, sub); - - lock (received) - { - Assert.AreEqual(1, received.Count); - } - Assert.AreEqual(0, count); - } - } - - static void Ping(ConnectionMultiplexer muxer, IServer pub, ISubscriber sub, int times = 1) - { - while (times-- > 0) - { - // both use async because we want to drain the completion managers, and the only - // way to prove that is to use TPL objects - var t1 = sub.PingAsync(); - var t2 = pub.PingAsync(); - Thread.Sleep(100); // especially useful when testing any-order mode - - if (!Task.WaitAll(new[] { t1, t2 }, muxer.TimeoutMilliseconds * 2)) throw new TimeoutException(); - } - } - - //protected override string GetConfiguration() - //{ - // return PrimaryServer + ":" + PrimaryPortString; - //} - - [Test] - [TestCase(true)] - [TestCase(false)] - public void TestPatternPubSub(bool preserveOrder) - { - using (var muxer = Create()) - { - muxer.PreserveAsyncOrder = preserveOrder; - var pub = GetServer(muxer); - var sub = muxer.GetSubscriber(); - - HashSet received = new HashSet(); - int secondHandler = 0; - sub.Subscribe("a*c", (channel, payload) => - { - lock(received) - { - if (channel == "abc") - { - received.Add(payload); - } - } - }); - - sub.Subscribe("a*c", (channel, payload) => - { - Interlocked.Increment(ref secondHandler); - }); - lock (received) - { - Assert.AreEqual(0, received.Count); - } - Assert.AreEqual(0, VolatileWrapper.Read(ref secondHandler)); - var count = sub.Publish("abc", "def"); - - Ping(muxer, pub, sub); - - lock(received) - { - Assert.AreEqual(1, received.Count); - } - Assert.AreEqual(1, VolatileWrapper.Read(ref secondHandler)); - - sub.Unsubscribe("a*c"); - count = sub.Publish("abc", "ghi"); - - Ping(muxer, pub, sub); - - lock(received) - { - Assert.AreEqual(1, received.Count); - } - Assert.AreEqual(0, count); - } - } - - [Test] - [TestCase(false)] - [TestCase(true)] - public void SubscriptionsSurviveMasterSwitch(bool useSharedSocketManager) - { - using (var a = Create(allowAdmin: true, useSharedSocketManager: useSharedSocketManager)) - using (var b = Create(allowAdmin: true, useSharedSocketManager: useSharedSocketManager)) - { - RedisChannel channel = Me(); - var subA = a.GetSubscriber(); - var subB = b.GetSubscriber(); - - long masterChanged = 0, aCount = 0, bCount = 0; - a.ConfigurationChangedBroadcast += delegate { - Console.WriteLine("a noticed config broadcast: " + Interlocked.Increment(ref masterChanged)); - }; - b.ConfigurationChangedBroadcast += delegate { - Console.WriteLine("b noticed config broadcast: " + Interlocked.Increment(ref masterChanged)); - }; - subA.Subscribe(channel, (ch, message) => { - Console.WriteLine("a got message: " + message); - Interlocked.Increment(ref aCount); - }); - subB.Subscribe(channel, (ch, message) => { - Console.WriteLine("b got message: " + message); - Interlocked.Increment(ref bCount); - }); - - - Assert.IsFalse(a.GetServer(PrimaryServer, PrimaryPort).IsSlave, PrimaryPortString + " is master via a"); - Assert.IsTrue(a.GetServer(PrimaryServer, SlavePort).IsSlave, SlavePortString + " is slave via a"); - Assert.IsFalse(b.GetServer(PrimaryServer, PrimaryPort).IsSlave, PrimaryPortString + " is master via b"); - Assert.IsTrue(b.GetServer(PrimaryServer, SlavePort).IsSlave, SlavePortString + " is slave via b"); - - - var epA = subA.SubscribedEndpoint(channel); - var epB = subB.SubscribedEndpoint(channel); - Console.WriteLine("a: " + EndPointCollection.ToString(epA)); - Console.WriteLine("b: " + EndPointCollection.ToString(epB)); - subA.Publish(channel, "a1"); - subB.Publish(channel, "b1"); - subA.Ping(); - subB.Ping(); - - Assert.AreEqual(2, Interlocked.Read(ref aCount), "a"); - Assert.AreEqual(2, Interlocked.Read(ref bCount), "b"); - Assert.AreEqual(0, Interlocked.Read(ref masterChanged), "master"); - - try - { - Interlocked.Exchange(ref masterChanged, 0); - Interlocked.Exchange(ref aCount, 0); - Interlocked.Exchange(ref bCount, 0); - Console.WriteLine("Changing master..."); - using (var sw = new StringWriter()) - { - a.GetServer(PrimaryServer, SlavePort).MakeMaster(ReplicationChangeOptions.All, sw); - Console.WriteLine(sw); - } - subA.Ping(); - subB.Ping(); - Console.WriteLine("Pausing..."); - Thread.Sleep(2000); - - Assert.IsTrue(a.GetServer(PrimaryServer, PrimaryPort).IsSlave, PrimaryPortString + " is slave via a"); - Assert.IsFalse(a.GetServer(PrimaryServer, SlavePort).IsSlave, SlavePortString + " is master via a"); - Assert.IsTrue(b.GetServer(PrimaryServer, PrimaryPort).IsSlave, PrimaryPortString + " is slave via b"); - Assert.IsFalse(b.GetServer(PrimaryServer, SlavePort).IsSlave, SlavePortString + " is master via b"); - - Console.WriteLine("Pause complete"); - var counters = a.GetCounters(); - Console.WriteLine("a outstanding: " + counters.TotalOutstanding); - counters = b.GetCounters(); - Console.WriteLine("b outstanding: " + counters.TotalOutstanding); - subA.Ping(); - subB.Ping(); - epA = subA.SubscribedEndpoint(channel); - epB = subB.SubscribedEndpoint(channel); - Console.WriteLine("a: " + EndPointCollection.ToString(epA)); - Console.WriteLine("b: " + EndPointCollection.ToString(epB)); - Console.WriteLine("a2 sent to: " + subA.Publish(channel, "a2")); - Console.WriteLine("b2 sent to: " + subB.Publish(channel, "b2")); - subA.Ping(); - subB.Ping(); - Console.WriteLine("Checking..."); - - Assert.AreEqual(2, Interlocked.Read(ref aCount), "a"); - Assert.AreEqual(2, Interlocked.Read(ref bCount), "b"); - Assert.AreEqual(4, Interlocked.CompareExchange(ref masterChanged, 0, 0), "master"); - } - finally - { - Console.WriteLine("Restoring configuration..."); - try - { - a.GetServer(PrimaryServer, PrimaryPort).MakeMaster(ReplicationChangeOptions.All); - } - catch - { } - } - } - } - - [Test] - public void SubscriptionsSurviveConnectionFailure() - { - -#if !DEBUG - Assert.Inconclusive("Needs #DEBUG"); -#endif - using(var muxer = Create( allowAdmin: true)) - { - RedisChannel channel = Me(); - var sub = muxer.GetSubscriber(); - int counter = 0; - sub.Subscribe(channel, delegate - { - Interlocked.Increment(ref counter); - }); - sub.Publish(channel, "abc"); - sub.Ping(); - Assert.AreEqual(1, VolatileWrapper.Read(ref counter), "counter"); - var server = GetServer(muxer); - Assert.AreEqual(1, server.GetCounters().Subscription.SocketCount, "sockets"); - -#if DEBUG - server.SimulateConnectionFailure(); - SetExpectedAmbientFailureCount(2); -#endif - - Thread.Sleep(100); - sub.Ping(); -#if DEBUG - Assert.AreEqual(2, server.GetCounters().Subscription.SocketCount, "sockets"); -#endif - sub.Publish(channel, "abc"); - sub.Ping(); - Assert.AreEqual(2, VolatileWrapper.Read(ref counter), "counter"); - } - } - } - - internal static class VolatileWrapper - { - public static int Read(ref int location) - { -#if !CORE_CLR - return Thread.VolatileRead(ref location); -#else - return Volatile.Read(ref location); -#endif - } - } -} diff --git a/StackExchange.Redis.Tests/PubSubCommand.cs b/StackExchange.Redis.Tests/PubSubCommand.cs deleted file mode 100644 index 61cd210de..000000000 --- a/StackExchange.Redis.Tests/PubSubCommand.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Linq; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class PubSubCommand : TestBase - { - [Test] - public void SubscriberCount() - { - using(var conn = Create()) - { - RedisChannel channel = Me() + Guid.NewGuid(); - var server = conn.GetServer(conn.GetEndPoints()[0]); - - var channels = server.SubscriptionChannels(Me() + "*"); - Assert.IsFalse(channels.Contains(channel)); - - long justWork = server.SubscriptionPatternCount(); - var count = server.SubscriptionSubscriberCount(channel); - Assert.AreEqual(0, count); - conn.GetSubscriber().Subscribe(channel, delegate { }); - count = server.SubscriptionSubscriberCount(channel); - Assert.AreEqual(1, count); - - channels = server.SubscriptionChannels(Me() + "*"); - Assert.IsTrue(channels.Contains(channel)); - } - } - } -} diff --git a/StackExchange.Redis.Tests/RealWorld.cs b/StackExchange.Redis.Tests/RealWorld.cs deleted file mode 100644 index 8d993d641..000000000 --- a/StackExchange.Redis.Tests/RealWorld.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class RealWorld - { - [Test] - public void WhyDoesThisNotWork() - { - var sw = new StringWriter(); - Console.WriteLine("first:"); - using (var conn = ConnectionMultiplexer.Connect("localhost:6379,localhost:6380,name=Core (Q&A),tiebreaker=:RedisMaster,abortConnect=False", sw)) - { - Console.WriteLine(sw); - Console.WriteLine(); - Console.WriteLine("pausing..."); - Thread.Sleep(200); - Console.WriteLine("second:"); - - sw = new StringWriter(); - bool result = conn.Configure(sw); - Console.WriteLine("Returned: {0}", result); - Console.WriteLine(sw); - } - - } - } -} diff --git a/StackExchange.Redis.Tests/SSDB.cs b/StackExchange.Redis.Tests/SSDB.cs deleted file mode 100644 index 9f5240e65..000000000 --- a/StackExchange.Redis.Tests/SSDB.cs +++ /dev/null @@ -1,27 +0,0 @@ -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class SSDB : TestBase - { - [Test] - public void ConnectToSSDB() - { - var config = new ConfigurationOptions - { - EndPoints = { { "ubuntu", 8888 } }, - CommandMap = CommandMap.SSDB - }; - RedisKey key = Me(); - using (var conn = ConnectionMultiplexer.Connect(config)) - { - var db = conn.GetDatabase(0); - db.KeyDelete(key); - Assert.IsTrue(db.StringGet(key).IsNull); - db.StringSet(key, "abc"); - Assert.AreEqual("abc", (string)db.StringGet(key)); - } - } - } -} diff --git a/StackExchange.Redis.Tests/SSL.cs b/StackExchange.Redis.Tests/SSL.cs deleted file mode 100644 index a4a862145..000000000 --- a/StackExchange.Redis.Tests/SSL.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class SSL : TestBase - { - [Test] - [TestCase(null, true)] - [TestCase(null, false)] - [TestCase(6380, true)] - [TestCase(6379, false)] - public void ConnectToAzure(int? port, bool ssl) - { - string name, password; - GetAzureCredentials(out name, out password); - var options = new ConfigurationOptions(); - if (port == null) - { - options.EndPoints.Add(name + ".redis.cache.windows.net"); - } else - { - options.EndPoints.Add(name + ".redis.cache.windows.net", port.Value); - } - options.Ssl = ssl; - options.Password = password; - Console.WriteLine(options); - using(var connection = ConnectionMultiplexer.Connect(options)) - { - var ttl = connection.GetDatabase().Ping(); - Console.WriteLine(ttl); - } - } - - [Test] - [TestCase(false, false)] - [TestCase(true, false)] - [TestCase(true, true)] - public void ConnectToSSLServer(bool useSsl, bool specifyHost) - { - string host = null; - - const string path = @"D:\RedisSslHost.txt"; // because I choose not to advertise my server here! - if (File.Exists(path)) host = File.ReadLines(path).First(); - if (string.IsNullOrWhiteSpace(host)) Assert.Inconclusive("no ssl host specified at: " + path); - - var config = new ConfigurationOptions - { - CommandMap = CommandMap.Create( // looks like "config" is disabled - new Dictionary - { - { "config", null }, - { "cluster", null } - } - ), - EndPoints = { { host } }, - AllowAdmin = true, - SyncTimeout = Debugger.IsAttached ? int.MaxValue : 5000 - }; - if(useSsl) - { - config.Ssl = useSsl; - if (specifyHost) - { - config.SslHost = host; - } - config.CertificateValidation += (sender, cert, chain, errors) => - { - Console.WriteLine("errors: " + errors); - Console.WriteLine("cert issued to: " + cert.Subject); - return true; // fingers in ears, pretend we don't know this is wrong - }; - } - - var configString = config.ToString(); - Console.WriteLine("config: " + configString); - var clone = ConfigurationOptions.Parse(configString); - Assert.AreEqual(configString, clone.ToString(), "config string"); - - using(var log = new StringWriter()) - using (var muxer = ConnectionMultiplexer.Connect(config, log)) - { - Console.WriteLine("Connect log:"); - Console.WriteLine(log); - Console.WriteLine("===="); - muxer.ConnectionFailed += OnConnectionFailed; - muxer.InternalError += OnInternalError; - var db = muxer.GetDatabase(); - db.Ping(); - using (var file = File.Create("ssl-" + useSsl + "-" + specifyHost + ".zip")) - { - muxer.ExportConfiguration(file); - } - RedisKey key = "SE.Redis"; - - const int AsyncLoop = 2000; - // perf; async - db.KeyDelete(key, CommandFlags.FireAndForget); - var watch = Stopwatch.StartNew(); - for (int i = 0; i < AsyncLoop; i++) - { - db.StringIncrement(key, flags: CommandFlags.FireAndForget); - } - // need to do this inside the timer to measure the TTLB - long value = (long)db.StringGet(key); - watch.Stop(); - Assert.AreEqual(AsyncLoop, value); - Console.WriteLine("F&F: {0} INCR, {1:###,##0}ms, {2} ops/s; final value: {3}", - AsyncLoop, - (long)watch.ElapsedMilliseconds, - (long)(AsyncLoop / watch.Elapsed.TotalSeconds), - value); - - // perf: sync/multi-threaded - TestConcurrent(db, key, 30, 10); - //TestConcurrent(db, key, 30, 20); - //TestConcurrent(db, key, 30, 30); - //TestConcurrent(db, key, 30, 40); - //TestConcurrent(db, key, 30, 50); - } - } - - - private static void TestConcurrent(IDatabase db, RedisKey key, int SyncLoop, int Threads) - { - long value; - db.KeyDelete(key, CommandFlags.FireAndForget); - var time = RunConcurrent(delegate - { - for (int i = 0; i < SyncLoop; i++) - { - db.StringIncrement(key); - } - }, Threads, timeout: 45000); - value = (long)db.StringGet(key); - Assert.AreEqual(SyncLoop * Threads, value); - Console.WriteLine("Sync: {0} INCR using {1} threads, {2:###,##0}ms, {3} ops/s; final value: {4}", - SyncLoop * Threads, Threads, - (long)time.TotalMilliseconds, - (long)((SyncLoop * Threads) / time.TotalSeconds), - value); - } - - - - const string RedisLabsSslHostFile = @"d:\RedisLabsSslHost.txt"; - const string RedisLabsPfxPath = @"d:\RedisLabsUser.pfx"; - - [Test] - public void RedisLabsSSL() - { - if (!File.Exists(RedisLabsSslHostFile)) Assert.Inconclusive(); - string hostAndPort = File.ReadAllText(RedisLabsSslHostFile); - int timeout = 5000; - if (Debugger.IsAttached) timeout *= 100; - var options = new ConfigurationOptions - { - EndPoints = { hostAndPort }, - ConnectTimeout = timeout, - AllowAdmin = true, - CommandMap = CommandMap.Create(new HashSet { - "subscribe", "unsubscribe", "cluster" - }, false) - }; - if (!Directory.Exists(Me())) Directory.CreateDirectory(Me()); -#if LOGOUTPUT - ConnectionMultiplexer.EchoPath = Me(); -#endif - options.Ssl = true; - options.CertificateSelection += delegate { - return new X509Certificate2(RedisLabsPfxPath, ""); - }; - RedisKey key = Me(); - using(var conn = ConnectionMultiplexer.Connect(options)) - { - var db = conn.GetDatabase(); - db.KeyDelete(key); - string s = db.StringGet(key); - Assert.IsNull(s); - db.StringSet(key, "abc"); - s = db.StringGet(key); - Assert.AreEqual("abc", s); - - var latency = db.Ping(); - Console.WriteLine("RedisLabs latency: {0:###,##0.##}ms", latency.TotalMilliseconds); - - using (var file = File.Create("RedisLabs.zip")) - { - conn.ExportConfiguration(file); - } - } - } - - [Test] - [TestCase(false)] - [TestCase(true)] - public void RedisLabsEnvironmentVariableClientCertificate(bool setEnv) - { - try - { - if (setEnv) - { - Environment.SetEnvironmentVariable("SERedis_ClientCertPfxPath", RedisLabsPfxPath); - } - if (!File.Exists(RedisLabsSslHostFile)) Assert.Inconclusive(); - string hostAndPort = File.ReadAllText(RedisLabsSslHostFile); - int timeout = 5000; - if (Debugger.IsAttached) timeout *= 100; - var options = new ConfigurationOptions - { - EndPoints = { hostAndPort }, - ConnectTimeout = timeout, - AllowAdmin = true, - CommandMap = CommandMap.Create(new HashSet { - "subscribe", "unsubscribe", "cluster" - }, false) - }; - if (!Directory.Exists(Me())) Directory.CreateDirectory(Me()); -#if LOGOUTPUT - ConnectionMultiplexer.EchoPath = Me(); -#endif - options.Ssl = true; - RedisKey key = Me(); - using (var conn = ConnectionMultiplexer.Connect(options)) - { - if (!setEnv) Assert.Fail(); - - var db = conn.GetDatabase(); - db.KeyDelete(key); - string s = db.StringGet(key); - Assert.IsNull(s); - db.StringSet(key, "abc"); - s = db.StringGet(key); - Assert.AreEqual("abc", s); - - var latency = db.Ping(); - Console.WriteLine("RedisLabs latency: {0:###,##0.##}ms", latency.TotalMilliseconds); - - using (var file = File.Create("RedisLabs.zip")) - { - conn.ExportConfiguration(file); - } - } - } - catch(RedisConnectionException ex) - { - if(setEnv || ex.FailureType != ConnectionFailureType.UnableToConnect) - { - throw; - } - } - finally - { - Environment.SetEnvironmentVariable("SERedis_ClientCertPfxPath", null); - } - - } - - [Test] - public void SSLHostInferredFromEndpoints() { - var options = new ConfigurationOptions() { - EndPoints = { - { "mycache.rediscache.windows.net", 15000}, - { "mycache.rediscache.windows.net", 15001 }, - { "mycache.rediscache.windows.net", 15002 }, - } - }; - options.Ssl = true; - Assert.True(options.SslHost == "mycache.rediscache.windows.net"); - options = new ConfigurationOptions() { - EndPoints = { - { "121.23.23.45", 15000}, - } - }; - Assert.True(options.SslHost == null); - } - - } -} diff --git a/StackExchange.Redis.Tests/Scans.cs b/StackExchange.Redis.Tests/Scans.cs deleted file mode 100644 index c05ad24ab..000000000 --- a/StackExchange.Redis.Tests/Scans.cs +++ /dev/null @@ -1,358 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Scans : TestBase - { - [Test] - [TestCase(true)] - [TestCase(false)] - public void KeysScan(bool supported) - { - string[] disabledCommands = supported ? null : new[] { "scan" }; - using (var conn = Create(disabledCommands: disabledCommands, allowAdmin: true)) - { - const int DB = 7; - var db = conn.GetDatabase(DB); - var server = GetServer(conn); - server.FlushDatabase(DB); - for(int i = 0 ; i < 100 ; i++) - { - db.StringSet("KeysScan:" + i, Guid.NewGuid().ToString(), flags: CommandFlags.FireAndForget); - } - var seq = server.Keys(DB, pageSize:50); - bool isScanning = seq is IScanningCursor; - Assert.AreEqual(supported, isScanning, "scanning"); - Assert.AreEqual(100, seq.Distinct().Count()); - Assert.AreEqual(100, seq.Distinct().Count()); - Assert.AreEqual(100, server.Keys(DB, "KeysScan:*").Distinct().Count()); - // 7, 70, 71, ..., 79 - Assert.AreEqual(11, server.Keys(DB, "KeysScan:7*").Distinct().Count()); - } - } - - - public void ScansIScanning() - { - using (var conn = Create(allowAdmin: true)) - { - const int DB = 7; - var db = conn.GetDatabase(DB); - var server = GetServer(conn); - server.FlushDatabase(DB); - for (int i = 0; i < 100; i++) - { - db.StringSet("ScansRepeatable:" + i, Guid.NewGuid().ToString(), flags: CommandFlags.FireAndForget); - } - var seq = server.Keys(DB, pageSize: 15); - using(var iter = seq.GetEnumerator()) - { - IScanningCursor s0 = (IScanningCursor)seq, s1 = (IScanningCursor)iter; - - Assert.AreEqual(15, s0.PageSize); - Assert.AreEqual(15, s1.PageSize); - - // start at zero - Assert.AreEqual(0, s0.Cursor); - Assert.AreEqual(s0.Cursor, s1.Cursor); - - for(int i = 0 ; i < 47 ; i++) - { - Assert.IsTrue(iter.MoveNext()); - } - - // non-zero in the middle - Assert.AreNotEqual(0, s0.Cursor); - Assert.AreEqual(s0.Cursor, s1.Cursor); - - for (int i = 0; i < 53; i++) - { - Assert.IsTrue(iter.MoveNext()); - } - - // zero "next" at the end - Assert.IsFalse(iter.MoveNext()); - Assert.AreNotEqual(0, s0.Cursor); - Assert.AreNotEqual(0, s1.Cursor); - } - } - } - - public void ScanResume() - { - using (var conn = Create(allowAdmin: true)) - { - const int DB = 7; - var db = conn.GetDatabase(DB); - var server = GetServer(conn); - server.FlushDatabase(DB); - int i; - for (i = 0; i < 100; i++) - { - db.StringSet("ScanResume:" + i, Guid.NewGuid().ToString(), flags: CommandFlags.FireAndForget); - } - - var expected = new HashSet(); - long snapCursor = 0; - int snapOffset = 0, snapPageSize = 0; - - i = 0; - var seq = server.Keys(DB, pageSize: 15); - foreach(var key in seq) - { - i++; - if (i < 57) continue; - if (i == 57) - { - snapCursor = ((IScanningCursor)seq).Cursor; - snapOffset = ((IScanningCursor)seq).PageOffset; - snapPageSize = ((IScanningCursor)seq).PageSize; - } - expected.Add((string)key); - } - Assert.AreNotEqual(43, expected.Count); - Assert.AreNotEqual(0, snapCursor); - Assert.AreEqual(11, snapOffset); - Assert.AreEqual(15, snapPageSize); - - seq = server.Keys(DB, pageSize: 15, cursor: snapCursor, pageOffset: snapOffset); - var seqCur = (IScanningCursor)seq; - Assert.AreEqual(snapCursor, seqCur.Cursor); - Assert.AreEqual(snapPageSize, seqCur.PageSize); - Assert.AreEqual(snapOffset, seqCur.PageOffset); - using(var iter = seq.GetEnumerator()) - { - var iterCur = (IScanningCursor)iter; - Assert.AreEqual(snapCursor, iterCur.Cursor); - Assert.AreEqual(snapOffset, iterCur.PageOffset); - Assert.AreEqual(snapCursor, seqCur.Cursor); - Assert.AreEqual(snapOffset, seqCur.PageOffset); - - Assert.IsTrue(iter.MoveNext()); - Assert.AreEqual(snapCursor, iterCur.Cursor); - Assert.AreEqual(snapOffset, iterCur.PageOffset); - Assert.AreEqual(snapCursor, seqCur.Cursor); - Assert.AreEqual(snapOffset, seqCur.PageOffset); - - Assert.IsTrue(iter.MoveNext()); - Assert.AreEqual(snapCursor, iterCur.Cursor); - Assert.AreEqual(snapOffset + 1, iterCur.PageOffset); - Assert.AreEqual(snapCursor, seqCur.Cursor); - Assert.AreEqual(snapOffset + 1, seqCur.PageOffset); - } - - int count = 0; - foreach(var key in seq) - { - expected.Remove((string)key); - count++; - } - Assert.AreEqual(0, expected.Count); - Assert.AreEqual(44, count); // expect the initial item to be repeated - - } - } - - - - - [Test] - [TestCase(true)] - [TestCase(false)] - public void SetScan(bool supported) - { - string[] disabledCommands = supported ? null : new[] { "sscan" }; - using(var conn = Create(disabledCommands: disabledCommands)) - { - RedisKey key = Me(); - var db = conn.GetDatabase(); - db.KeyDelete(key); - - db.SetAdd(key, "a"); - db.SetAdd(key, "b"); - db.SetAdd(key, "c"); - var arr = db.SetScan(key).ToArray(); - Assert.AreEqual(3, arr.Length); - Assert.IsTrue(arr.Contains("a"), "a"); - Assert.IsTrue(arr.Contains("b"), "b"); - Assert.IsTrue(arr.Contains("c"), "c"); - } - } - - [Test] - [TestCase(true)] - [TestCase(false)] - public void SortedSetScan(bool supported) - { - string[] disabledCommands = supported ? null : new[] { "zscan" }; - using (var conn = Create(disabledCommands: disabledCommands)) - { - RedisKey key = Me(); - var db = conn.GetDatabase(); - db.KeyDelete(key); - - db.SortedSetAdd(key, "a", 1); - db.SortedSetAdd(key, "b", 2); - db.SortedSetAdd(key, "c", 3); - - var arr = db.SortedSetScan(key).ToArray(); - Assert.AreEqual(3, arr.Length); - Assert.IsTrue(arr.Any(x => x.Element == "a" && x.Score == 1), "a"); - Assert.IsTrue(arr.Any(x => x.Element == "b" && x.Score == 2), "b"); - Assert.IsTrue(arr.Any(x => x.Element == "c" && x.Score == 3), "c"); - - var dictionary = arr.ToDictionary(); - Assert.AreEqual(1, dictionary["a"]); - Assert.AreEqual(2, dictionary["b"]); - Assert.AreEqual(3, dictionary["c"]); - - var sDictionary = arr.ToStringDictionary(); - Assert.AreEqual(1, sDictionary["a"]); - Assert.AreEqual(2, sDictionary["b"]); - Assert.AreEqual(3, sDictionary["c"]); - - var basic = db.SortedSetRangeByRankWithScores(key, order: Order.Ascending).ToDictionary(); - Assert.AreEqual(3, basic.Count); - Assert.AreEqual(1, basic["a"]); - Assert.AreEqual(2, basic["b"]); - Assert.AreEqual(3, basic["c"]); - - basic = db.SortedSetRangeByRankWithScores(key, order: Order.Descending).ToDictionary(); - Assert.AreEqual(3, basic.Count); - Assert.AreEqual(1, basic["a"]); - Assert.AreEqual(2, basic["b"]); - Assert.AreEqual(3, basic["c"]); - - var basicArr = db.SortedSetRangeByScoreWithScores(key, order: Order.Ascending); - Assert.AreEqual(3, basicArr.Length); - Assert.AreEqual(1, basicArr[0].Score); - Assert.AreEqual(2, basicArr[1].Score); - Assert.AreEqual(3, basicArr[2].Score); - basic = basicArr.ToDictionary(); - Assert.AreEqual(3, basic.Count, "asc"); - Assert.AreEqual(1, basic["a"]); - Assert.AreEqual(2, basic["b"]); - Assert.AreEqual(3, basic["c"]); - - basicArr = db.SortedSetRangeByScoreWithScores(key, order: Order.Descending); - Assert.AreEqual(3, basicArr.Length); - Assert.AreEqual(3, basicArr[0].Score); - Assert.AreEqual(2, basicArr[1].Score); - Assert.AreEqual(1, basicArr[2].Score); - basic = basicArr.ToDictionary(); - Assert.AreEqual(3, basic.Count, "desc"); - Assert.AreEqual(1, basic["a"]); - Assert.AreEqual(2, basic["b"]); - Assert.AreEqual(3, basic["c"]); - } - } - - [Test] - [TestCase(true)] - [TestCase(false)] - public void HashScan(bool supported) - { - string[] disabledCommands = supported ? null : new[] { "hscan" }; - using (var conn = Create(disabledCommands: disabledCommands)) - { - RedisKey key = Me(); - var db = conn.GetDatabase(); - db.KeyDelete(key); - - db.HashSet(key, "a", "1"); - db.HashSet(key, "b", "2"); - db.HashSet(key, "c", "3"); - - var arr = db.HashScan(key).ToArray(); - Assert.AreEqual(3, arr.Length); - Assert.IsTrue(arr.Any(x => x.Name == "a" && x.Value == "1"), "a"); - Assert.IsTrue(arr.Any(x => x.Name == "b" && x.Value == "2"), "b"); - Assert.IsTrue(arr.Any(x => x.Name == "c" && x.Value == "3"), "c"); - - var dictionary = arr.ToDictionary(); - Assert.AreEqual(1, (long)dictionary["a"]); - Assert.AreEqual(2, (long)dictionary["b"]); - Assert.AreEqual(3, (long)dictionary["c"]); - - var sDictionary = arr.ToStringDictionary(); - Assert.AreEqual("1", sDictionary["a"]); - Assert.AreEqual("2", sDictionary["b"]); - Assert.AreEqual("3", sDictionary["c"]); - - - var basic = db.HashGetAll(key).ToDictionary(); - Assert.AreEqual(3, basic.Count); - Assert.AreEqual(1, (long)basic["a"]); - Assert.AreEqual(2, (long)basic["b"]); - Assert.AreEqual(3, (long)basic["c"]); - } - } - - [Test] - [TestCase(10)] - [TestCase(100)] - [TestCase(1000)] - [TestCase(10000)] - public void HashScanLarge(int pageSize) - { - using (var conn = Create()) - { - RedisKey key = Me(); - var db = conn.GetDatabase(); - db.KeyDelete(key); - - for(int i = 0; i < 2000;i++) - db.HashSet(key, "k" + i, "v" + i, flags: CommandFlags.FireAndForget); - - int count = db.HashScan(key, pageSize: pageSize).Count(); - Assert.AreEqual(2000, count); - } - } - - [Test] - [TestCase(10)] - [TestCase(100)] - [TestCase(1000)] - [TestCase(10000)] - public void SetScanLarge(int pageSize) - { - using (var conn = Create()) - { - RedisKey key = Me(); - var db = conn.GetDatabase(); - db.KeyDelete(key); - - for (int i = 0; i < 2000; i++) - db.SetAdd(key, "s" + i, flags: CommandFlags.FireAndForget); - - int count = db.SetScan(key, pageSize: pageSize).Count(); - Assert.AreEqual(2000, count); - } - } - - [Test] - [TestCase(10)] - [TestCase(100)] - [TestCase(1000)] - [TestCase(10000)] - public void SortedSetScanLarge(int pageSize) - { - using (var conn = Create()) - { - RedisKey key = Me(); - var db = conn.GetDatabase(); - db.KeyDelete(key); - - for (int i = 0; i < 2000; i++) - db.SortedSetAdd(key, "z" + i, i, flags: CommandFlags.FireAndForget); - - int count = db.SortedSetScan(key, pageSize: pageSize).Count(); - Assert.AreEqual(2000, count); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Scripting.cs b/StackExchange.Redis.Tests/Scripting.cs deleted file mode 100644 index be5d51334..000000000 --- a/StackExchange.Redis.Tests/Scripting.cs +++ /dev/null @@ -1,513 +0,0 @@ -using System; -using System.Diagnostics; -using System.Linq; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Scripting : TestBase - { - [Test] - public void TestBasicScripting() - { - using (var conn = Create()) - { - RedisValue newId = Guid.NewGuid().ToString(); - RedisKey custKey = Me(); - var db = conn.GetDatabase(); - db.KeyDelete(custKey); - db.HashSet(custKey, "id", 123); - - var wasSet = (bool) db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end", - new RedisKey[] { custKey }, new RedisValue[] { newId }); - - Assert.IsTrue(wasSet); - - wasSet = (bool)db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end", - new RedisKey[] { custKey }, new RedisValue[] { newId }); - Assert.IsFalse(wasSet); - } - } - - [Test] - [TestCase(true)] - [TestCase(false)] - public void CheckLoads(bool async) - { - using (var conn0 = Create(allowAdmin: true)) - using (var conn1 = Create(allowAdmin: true)) - { - // note that these are on different connections (so we wouldn't expect - // the flush to drop the local cache - assume it is a surprise!) - var server = conn0.GetServer(PrimaryServer, PrimaryPort); - var db = conn1.GetDatabase(); - const string script = "return 1;"; - - // start empty - server.ScriptFlush(); - Assert.IsFalse(server.ScriptExists(script)); - - // run once, causes to be cached - Assert.IsTrue((bool)db.ScriptEvaluate(script)); - Assert.IsTrue(server.ScriptExists(script)); - - // can run again - Assert.IsTrue((bool)db.ScriptEvaluate(script)); - - // ditch the scripts; should no longer exist - db.Ping(); - server.ScriptFlush(); - Assert.IsFalse(server.ScriptExists(script)); - db.Ping(); - - if (async) - { - // now: fails the first time - try - { - db.Wait(db.ScriptEvaluateAsync(script)); - Assert.Fail(); - } - catch(AggregateException ex) - { - Assert.AreEqual(1, ex.InnerExceptions.Count); - Assert.IsInstanceOf(ex.InnerExceptions[0]); - Assert.AreEqual("NOSCRIPT No matching script. Please use EVAL.", ex.InnerExceptions[0].Message); - } - } else - { - // just works; magic - Assert.IsTrue((bool)db.ScriptEvaluate(script)); - } - - // but gets marked as unloaded, so we can use it again... - Assert.IsTrue((bool)db.ScriptEvaluate(script)); - - // which will cause it to be cached - Assert.IsTrue(server.ScriptExists(script)); - } - } - - [Test] - public void CompareScriptToDirect() - { - const string Script = "return redis.call('incr', KEYS[1])"; - - using (var conn = Create(allowAdmin: true)) - { - var server = conn.GetServer(PrimaryServer, PrimaryPort); - server.FlushAllDatabases(); - server.ScriptFlush(); - - server.ScriptLoad(Script); - var db = conn.GetDatabase(); - db.Ping(); // k, we're all up to date now; clean db, minimal script cache - - // we're using a pipeline here, so send 1000 messages, but for timing: only care about the last - const int LOOP = 5000; - RedisKey key = "foo"; - RedisKey[] keys = new[] { key }; // script takes an array - - // run via script - db.KeyDelete(key); - CollectGarbage(); - var watch = Stopwatch.StartNew(); - for(int i = 1; i < LOOP; i++) // the i=1 is to do all-but-one - { - db.ScriptEvaluate(Script, keys, flags: CommandFlags.FireAndForget); - } - var scriptResult = db.ScriptEvaluate(Script, keys); // last one we wait for (no F+F) - watch.Stop(); - TimeSpan scriptTime = watch.Elapsed; - - // run via raw op - db.KeyDelete(key); - CollectGarbage(); - watch = Stopwatch.StartNew(); - for (int i = 1; i < LOOP; i++) // the i=1 is to do all-but-one - { - db.StringIncrement(key, flags: CommandFlags.FireAndForget); - } - var directResult = db.StringIncrement(key); // last one we wait for (no F+F) - watch.Stop(); - TimeSpan directTime = watch.Elapsed; - - Assert.AreEqual(LOOP, (long)scriptResult, "script result"); - Assert.AreEqual(LOOP, (long)directResult, "direct result"); - - Console.WriteLine("script: {0}ms; direct: {1}ms", - scriptTime.TotalMilliseconds, - directTime.TotalMilliseconds); - } - } - - [Test] - public void TestCallByHash() - { - const string Script = "return redis.call('incr', KEYS[1])"; - - using (var conn = Create(allowAdmin: true)) - { - var server = conn.GetServer(PrimaryServer, PrimaryPort); - server.FlushAllDatabases(); - server.ScriptFlush(); - - byte[] hash = server.ScriptLoad(Script); - - var db = conn.GetDatabase(); - RedisKey[] keys = { Me() }; - - string hexHash = string.Concat(hash.Select(x => x.ToString("X2"))); - Assert.AreEqual("2BAB3B661081DB58BD2341920E0BA7CF5DC77B25", hexHash); - - db.ScriptEvaluate(hexHash, keys); - db.ScriptEvaluate(hash, keys); - - var count = (int)db.StringGet(keys)[0]; - Assert.AreEqual(2, count); - - } - } - - [Test] - public void SimpleLuaScript() - { - const string Script = "return @ident"; - - using(var conn = Create(allowAdmin: true)) - { - var server = conn.GetServer(PrimaryServer, PrimaryPort); - server.FlushAllDatabases(); - server.ScriptFlush(); - - var prepared = LuaScript.Prepare(Script); - - var db = conn.GetDatabase(); - - { - var val = prepared.Evaluate(db, new { ident = "hello" }); - Assert.AreEqual("hello", (string)val); - } - - { - var val = prepared.Evaluate(db, new { ident = 123 }); - Assert.AreEqual(123, (int)val); - } - - { - var val = prepared.Evaluate(db, new { ident = 123L }); - Assert.AreEqual(123L, (long)val); - } - - { - var val = prepared.Evaluate(db, new { ident = 1.1 }); - Assert.AreEqual(1.1, (double)val); - } - - { - var val = prepared.Evaluate(db, new { ident = true }); - Assert.AreEqual(true, (bool)val); - } - - { - var val = prepared.Evaluate(db, new { ident = new byte[] { 4, 5, 6 } }); - Assert.IsTrue(new byte [] { 4, 5, 6}.SequenceEqual((byte[])val)); - } - } - } - - [Test] - public void LuaScriptWithKeys() - { - const string Script = "redis.call('set', @key, @value)"; - - using (var conn = Create(allowAdmin: true)) - { - var server = conn.GetServer(PrimaryServer, PrimaryPort); - server.FlushAllDatabases(); - server.ScriptFlush(); - - var script = LuaScript.Prepare(Script); - - var db = conn.GetDatabase(); - - var p = new { key = (RedisKey)"testkey", value = 123 }; - - script.Evaluate(db, p); - var val = db.StringGet("testkey"); - Assert.AreEqual(123, (int)val); - - // no super clean way to extract this; so just abuse InternalsVisibleTo - RedisKey[] keys; - RedisValue[] args; - script.ExtractParameters(p, null, out keys, out args); - Assert.IsNotNull(keys); - Assert.AreEqual(1, keys.Length); - Assert.AreEqual("testkey", (string)keys[0]); - } - } - - [Test] - public void NoInlineReplacement() - { - const string Script = "redis.call('set', @key, 'hello@example')"; - using (var conn = Create(allowAdmin: true)) - { - var server = conn.GetServer(PrimaryServer, PrimaryPort); - server.FlushAllDatabases(); - server.ScriptFlush(); - - var script = LuaScript.Prepare(Script); - - Assert.AreEqual("redis.call('set', ARGV[1], 'hello@example')", script.ExecutableScript); - - var db = conn.GetDatabase(); - - var p = new { key = (RedisKey)"key" }; - - script.Evaluate(db, p); - var val = db.StringGet("key"); - Assert.AreEqual("hello@example", (string)val); - } - } - - [Test] - public void EscapeReplacement() - { - const string Script = "redis.call('set', @key, @@escapeMe)"; - var script = LuaScript.Prepare(Script); - - Assert.AreEqual("redis.call('set', ARGV[1], @escapeMe)", script.ExecutableScript); - } - - [Test] - public void SimpleLoadedLuaScript() - { - const string Script = "return @ident"; - - using (var conn = Create(allowAdmin: true)) - { - var server = conn.GetServer(PrimaryServer, PrimaryPort); - server.FlushAllDatabases(); - server.ScriptFlush(); - - var prepared = LuaScript.Prepare(Script); - var loaded = prepared.Load(server); - - var db = conn.GetDatabase(); - - { - var val = loaded.Evaluate(db, new { ident = "hello" }); - Assert.AreEqual("hello", (string)val); - } - - { - var val = loaded.Evaluate(db, new { ident = 123 }); - Assert.AreEqual(123, (int)val); - } - - { - var val = loaded.Evaluate(db, new { ident = 123L }); - Assert.AreEqual(123L, (long)val); - } - - { - var val = loaded.Evaluate(db, new { ident = 1.1 }); - Assert.AreEqual(1.1, (double)val); - } - - { - var val = loaded.Evaluate(db, new { ident = true }); - Assert.AreEqual(true, (bool)val); - } - - { - var val = loaded.Evaluate(db, new { ident = new byte[] { 4, 5, 6 } }); - Assert.IsTrue(new byte[] { 4, 5, 6 }.SequenceEqual((byte[])val)); - } - } - } - - [Test] - public void LoadedLuaScriptWithKeys() - { - const string Script = "redis.call('set', @key, @value)"; - - using (var conn = Create(allowAdmin: true)) - { - var server = conn.GetServer(PrimaryServer, PrimaryPort); - server.FlushAllDatabases(); - server.ScriptFlush(); - - var script = LuaScript.Prepare(Script); - var prepared = script.Load(server); - - var db = conn.GetDatabase(); - - var p = new { key = (RedisKey)"testkey", value = 123 }; - - prepared.Evaluate(db, p); - var val = db.StringGet("testkey"); - Assert.AreEqual(123, (int)val); - - // no super clean way to extract this; so just abuse InternalsVisibleTo - RedisKey[] keys; - RedisValue[] args; - prepared.Original.ExtractParameters(p, null, out keys, out args); - Assert.IsNotNull(keys); - Assert.AreEqual(1, keys.Length); - Assert.AreEqual("testkey", (string)keys[0]); - } - } - - [Test] - public void PurgeLuaScriptCache() - { - const string Script = "redis.call('set', @PurgeLuaScriptCacheKey, @PurgeLuaScriptCacheValue)"; - var first = LuaScript.Prepare(Script); - var fromCache = LuaScript.Prepare(Script); - - Assert.IsTrue(object.ReferenceEquals(first, fromCache)); - - LuaScript.PurgeCache(); - var shouldBeNew = LuaScript.Prepare(Script); - - Assert.IsFalse(object.ReferenceEquals(first, shouldBeNew)); - } - - static void _PurgeLuaScriptOnFinalize(string script) - { - var first = LuaScript.Prepare(script); - var fromCache = LuaScript.Prepare(script); - Assert.IsTrue(object.ReferenceEquals(first, fromCache)); - Assert.AreEqual(1, LuaScript.GetCachedScriptCount()); - } - - [Test] - public void PurgeLuaScriptOnFinalize() - { - const string Script = "redis.call('set', @PurgeLuaScriptOnFinalizeKey, @PurgeLuaScriptOnFinalizeValue)"; - LuaScript.PurgeCache(); - Assert.AreEqual(0, LuaScript.GetCachedScriptCount()); - - // This has to be a separate method to guarantee that the created LuaScript objects go out of scope, - // and are thus available to be GC'd - _PurgeLuaScriptOnFinalize(Script); - - GC.Collect(2, GCCollectionMode.Forced, blocking: true); - GC.WaitForPendingFinalizers(); - - Assert.AreEqual(0, LuaScript.GetCachedScriptCount()); - - var shouldBeNew = LuaScript.Prepare(Script); - Assert.AreEqual(1, LuaScript.GetCachedScriptCount()); - } - - [Test] - public void IDatabaseLuaScriptConvenienceMethods() - { - const string Script = "redis.call('set', @key, @value)"; - - using (var conn = Create(allowAdmin: true)) - { - var script = LuaScript.Prepare(Script); - var db = conn.GetDatabase(); - db.ScriptEvaluate(script, new { key = (RedisKey)"key", value = "value" }); - var val = db.StringGet("key"); - Assert.AreEqual("value", (string)val); - - var prepared = script.Load(conn.GetServer(conn.GetEndPoints()[0])); - - db.ScriptEvaluate(prepared, new { key = (RedisKey)"key2", value = "value2" }); - var val2 = db.StringGet("key2"); - Assert.AreEqual("value2", (string)val2); - } - } - - [Test] - public void IServerLuaScriptConvenienceMethods() - { - const string Script = "redis.call('set', @key, @value)"; - - using (var conn = Create(allowAdmin: true)) - { - var script = LuaScript.Prepare(Script); - var server = conn.GetServer(conn.GetEndPoints()[0]); - var db = conn.GetDatabase(); - - var prepared = server.ScriptLoad(script); - - db.ScriptEvaluate(prepared, new { key = (RedisKey)"key3", value = "value3" }); - var val = db.StringGet("key3"); - Assert.AreEqual("value3", (string)val); - } - } - - [Test] - public void LuaScriptPrefixedKeys() - { - const string Script = "redis.call('set', @key, @value)"; - var prepared = LuaScript.Prepare(Script); - var p = new { key = (RedisKey)"key", value = "hello" }; - - // no super clean way to extract this; so just abuse InternalsVisibleTo - RedisKey[] keys; - RedisValue[] args; - prepared.ExtractParameters(p, "prefix-", out keys, out args); - Assert.IsNotNull(keys); - Assert.AreEqual(1, keys.Length); - Assert.AreEqual("prefix-key", (string)keys[0]); - Assert.AreEqual(2, args.Length); - Assert.AreEqual("prefix-key", (string)args[0]); - Assert.AreEqual("hello", (string)args[1]); - } - - [Test] - public void LuaScriptWithWrappedDatabase() - { - const string Script = "redis.call('set', @key, @value)"; - - using (var conn = Create(allowAdmin: true)) - { - var db = conn.GetDatabase(0); - var wrappedDb = StackExchange.Redis.KeyspaceIsolation.DatabaseExtensions.WithKeyPrefix(db, "prefix-"); - - var prepared = LuaScript.Prepare(Script); - wrappedDb.ScriptEvaluate(prepared, new { key = (RedisKey)"mykey", value = 123 }); - var val1 = wrappedDb.StringGet("mykey"); - Assert.AreEqual(123, (int)val1); - - var val2 = db.StringGet("prefix-mykey"); - Assert.AreEqual(123, (int)val2); - - var val3 = db.StringGet("mykey"); - Assert.IsTrue(val3.IsNull); - } - } - - [Test] - public void LoadedLuaScriptWithWrappedDatabase() - { - const string Script = "redis.call('set', @key, @value)"; - - using (var conn = Create(allowAdmin: true)) - { - var db = conn.GetDatabase(0); - var wrappedDb = StackExchange.Redis.KeyspaceIsolation.DatabaseExtensions.WithKeyPrefix(db, "prefix2-"); - - var server = conn.GetServer(conn.GetEndPoints()[0]); - var prepared = LuaScript.Prepare(Script).Load(server); - wrappedDb.ScriptEvaluate(prepared, new { key = (RedisKey)"mykey", value = 123 }); - var val1 = wrappedDb.StringGet("mykey"); - Assert.AreEqual(123, (int)val1); - - var val2 = db.StringGet("prefix2-mykey"); - Assert.AreEqual(123, (int)val2); - - var val3 = db.StringGet("mykey"); - Assert.IsTrue(val3.IsNull); - } - } - } -} diff --git a/StackExchange.Redis.Tests/Secure.cs b/StackExchange.Redis.Tests/Secure.cs deleted file mode 100644 index b0097f264..000000000 --- a/StackExchange.Redis.Tests/Secure.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Diagnostics; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Secure : TestBase - { - protected override string GetConfiguration() - { - return PrimaryServer + ":" + SecurePort + ",password=" + SecurePassword +",name=MyClient"; - } - - [Test] - [TestCase(true)] - [TestCase(false)] - public void MassiveBulkOpsFireAndForgetSecure(bool preserveOrder) - { - using (var muxer = Create()) - { - muxer.PreserveAsyncOrder = preserveOrder; -#if DEBUG - long oldAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount(); -#endif - RedisKey key = "MBOF"; - var conn = muxer.GetDatabase(); - conn.Ping(); - - var watch = Stopwatch.StartNew(); - - for (int i = 0; i <= AsyncOpsQty; i++) - { - conn.StringSet(key, i, flags: CommandFlags.FireAndForget); - } - int val = (int)conn.StringGet(key); - Assert.AreEqual(AsyncOpsQty, val); - watch.Stop(); - Console.WriteLine("{2}: Time for {0} ops: {1}ms ({3}); ops/s: {4}", AsyncOpsQty, watch.ElapsedMilliseconds, Me(), - preserveOrder ? "preserve order" : "any order", - AsyncOpsQty / watch.Elapsed.TotalSeconds); -#if DEBUG - long newAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount(); - Console.WriteLine("ResultBox allocations: {0}", - newAlloc - oldAlloc); - Assert.IsTrue(newAlloc - oldAlloc <= 2); -#endif - } - } - - [Test] - public void CheckConfig() - { - var config = ConfigurationOptions.Parse(GetConfiguration()); - foreach(var ep in config.EndPoints) - Console.WriteLine(ep); - Assert.AreEqual(1, config.EndPoints.Count); - Assert.AreEqual("changeme", config.Password); - } - [Test] - public void Connect() - { - using(var server = Create()) - { - server.GetDatabase().Ping(); - } - } - [Test] - [TestCase("wrong")] - [TestCase("")] - public void ConnectWithWrongPassword(string password) - { - Assert.Throws(() => { - SetExpectedAmbientFailureCount(-1); - using (var server = Create(password: password, checkConnect: false)) - { - server.GetDatabase().Ping(); - } - }, - "No connection is available to service this operation: PING"); - } - } -} diff --git a/StackExchange.Redis.Tests/Sentinel.cs b/StackExchange.Redis.Tests/Sentinel.cs deleted file mode 100755 index b244762c4..000000000 --- a/StackExchange.Redis.Tests/Sentinel.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Threading; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture, Ignore("reason?")] - public class Sentinel - { - // TODO fill in these constants before running tests - private const string IP = "127.0.0.1"; - private const int Port = 26379; - private const string ServiceName = "mymaster"; - - private static readonly ConnectionMultiplexer Conn = GetConn(); - private static readonly IServer Server = Conn.GetServer(IP, Port); - - public static ConnectionMultiplexer GetConn() - { - // create a connection - var options = new ConfigurationOptions() - { - CommandMap = CommandMap.Sentinel, - EndPoints = { { IP, Port } }, - AllowAdmin = true, - TieBreaker = "", - ServiceName = ServiceName, - SyncTimeout = 5000 - }; - var connection = ConnectionMultiplexer.Connect(options, Console.Out); - Thread.Sleep(3000); - Assert.IsTrue(connection.IsConnected); - return connection; - } - - [Test] - public void PingTest() - { - var test = Server.Ping(); - Console.WriteLine("ping took {0} ms", test.TotalMilliseconds); - } - - [Test] - public void SentinelGetMasterAddressByNameTest() - { - var endpoint = Server.SentinelGetMasterAddressByName(ServiceName); - Assert.IsNotNull(endpoint); - var ipEndPoint = endpoint as IPEndPoint; - Assert.IsNotNull(ipEndPoint); - Console.WriteLine("{0}:{1}", ipEndPoint.Address, ipEndPoint.Port); - } - - [Test] - public void SentinelGetMasterAddressByNameNegativeTest() - { - var endpoint = Server.SentinelGetMasterAddressByName("FakeServiceName"); - Assert.IsNull(endpoint); - } - - [Test] - public void SentinelMasterTest() - { - var dict = Server.SentinelMaster(ServiceName).ToDictionary(); - Assert.AreEqual(ServiceName, dict["name"]); - foreach (var kvp in dict) - { - Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value); - } - } - - [Test] - public void SentinelMastersTest() - { - var masterConfigs = Server.SentinelMasters(); - Assert.IsTrue(masterConfigs.First().ToDictionary().ContainsKey("name")); - foreach (var config in masterConfigs) - { - foreach (var kvp in config) - { - Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value); - } - } - } - - [Test] - public void SentinelSlavesTest() - { - var slaveConfigs = Server.SentinelSlaves(ServiceName); - if (slaveConfigs.Any()) - { - Assert.IsTrue(slaveConfigs.First().ToDictionary().ContainsKey("name")); - } - foreach (var config in slaveConfigs) - { - foreach (var kvp in config) { - Console.WriteLine("{0}:{1}", kvp.Key, kvp.Value); - } - } - } - - [Test, Ignore("reason?")] - public void SentinelFailoverTest() - { - Server.SentinelFailover(ServiceName); - } - } -} diff --git a/StackExchange.Redis.Tests/Sets.cs b/StackExchange.Redis.Tests/Sets.cs deleted file mode 100644 index 0654a28ad..000000000 --- a/StackExchange.Redis.Tests/Sets.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Linq; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Sets : TestBase - { - [Test] - public void SScan() - { - using (var conn = Create()) - { - var server = GetServer(conn); - - - RedisKey key = "a"; - var db = conn.GetDatabase(); - db.KeyDelete(key); - - int totalUnfiltered = 0, totalFiltered = 0; - for (int i = 0; i < 1000; i++) - { - db.SetAdd(key, i); - totalUnfiltered += i; - if (i.ToString().Contains("3")) totalFiltered += i; - } - var unfilteredActual = db.SetScan(key).Select(x => (int)x).Sum(); - Assert.AreEqual(totalUnfiltered, unfilteredActual); - if (server.Features.Scan) - { - var filteredActual = db.SetScan(key, "*3*").Select(x => (int)x).Sum(); - Assert.AreEqual(totalFiltered, filteredActual); - } - - } - } - } -} diff --git a/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj b/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj deleted file mode 100644 index 6ace5f7d9..000000000 --- a/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - - StackExchange.Redis.Tests - netcoreapp1.0 - $(TargetFramework) - StackExchange.Redis.Tests - StackExchange.Redis.Tests - true - false - $(PackageTargetFallback);portable-net45+win8 - 1.0.4 - false - false - false - false - false - false - false - false - - - - - - - - - - - - - - - $(DefineConstants);CORE_CLR - - - - - - - - - - - - diff --git a/StackExchange.Redis.Tests/TaskTests.cs b/StackExchange.Redis.Tests/TaskTests.cs deleted file mode 100644 index 04d958b61..000000000 --- a/StackExchange.Redis.Tests/TaskTests.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class TaskTests - { -#if DEBUG - -#if !PLAT_SAFE_CONTINUATIONS // IsSyncSafe doesn't exist if PLAT_SAFE_CONTINUATIONS is defined - [Test] - [TestCase(SourceOrign.NewTCS, false)] - [TestCase(SourceOrign.Create, false)] - [TestCase(SourceOrign.CreateDenyExec, true)] - public void VerifyIsSyncSafe(SourceOrign origin, bool expected) - { - var source = Create(origin); - Assert.AreEqual(expected, TaskSource.IsSyncSafe(source.Task)); - } -#endif - static TaskCompletionSource Create(SourceOrign origin) - { - switch (origin) - { - case SourceOrign.NewTCS: return new TaskCompletionSource(); - case SourceOrign.Create: return TaskSource.Create(null); - case SourceOrign.CreateDenyExec: return TaskSource.CreateDenyExecSync(null); - default: throw new ArgumentOutOfRangeException(nameof(origin)); - } - } - [Test] - // regular framework behaviour: 2 out of 3 cause hijack - [TestCase(SourceOrign.NewTCS, AttachMode.ContinueWith, false)] - [TestCase(SourceOrign.NewTCS, AttachMode.ContinueWithExecSync, true)] - [TestCase(SourceOrign.NewTCS, AttachMode.Await, true)] - // Create is just a wrapper of ^^^; expect the same - [TestCase(SourceOrign.Create, AttachMode.ContinueWith, false)] - [TestCase(SourceOrign.Create, AttachMode.ContinueWithExecSync, true)] - [TestCase(SourceOrign.Create, AttachMode.Await, true)] - // deny exec-sync: none should cause hijack - [TestCase(SourceOrign.CreateDenyExec, AttachMode.ContinueWith, false)] - [TestCase(SourceOrign.CreateDenyExec, AttachMode.ContinueWithExecSync, false)] - [TestCase(SourceOrign.CreateDenyExec, AttachMode.Await, false)] - public void TestContinuationHijacking(SourceOrign origin, AttachMode attachMode, bool expectHijack) - { - TaskCompletionSource source = Create(origin); - - int settingThread = Environment.CurrentManagedThreadId; - var state = new AwaitState(); - state.Attach(source.Task, attachMode); - source.TrySetResult(123); - state.Wait(); // waits for the continuation to run - int from = state.Thread; - Assert.AreNotEqual(-1, from, "not set"); - if (expectHijack) - { - Assert.AreEqual(settingThread, from, "expected hijack; didn't happen"); - } - else - { - Assert.AreNotEqual(settingThread, from, "setter was hijacked"); - } - } - public enum SourceOrign - { - NewTCS, - Create, - CreateDenyExec - } - public enum AttachMode - { - ContinueWith, - ContinueWithExecSync, - Await - } - class AwaitState - { - public int Thread => continuationThread; - volatile int continuationThread = -1; - private ManualResetEventSlim evt = new ManualResetEventSlim(); - public void Wait() - { - if (!evt.Wait(5000)) throw new TimeoutException(); - } - public void Attach(Task task, AttachMode attachMode) - { - switch(attachMode) - { - case AttachMode.ContinueWith: - task.ContinueWith(Continue); - break; - case AttachMode.ContinueWithExecSync: - task.ContinueWith(Continue, TaskContinuationOptions.ExecuteSynchronously); - break; - case AttachMode.Await: - DoAwait(task); - break; - default: - throw new ArgumentOutOfRangeException(nameof(attachMode)); - } - } - private void Continue(Task task) - { - continuationThread = Environment.CurrentManagedThreadId; - evt.Set(); - } - private async void DoAwait(Task task) - { - await task.ConfigureAwait(false); - continuationThread = Environment.CurrentManagedThreadId; - evt.Set(); - } - } -#endif - } -} - diff --git a/StackExchange.Redis.Tests/TestBase.cs b/StackExchange.Redis.Tests/TestBase.cs deleted file mode 100644 index c172d9d03..000000000 --- a/StackExchange.Redis.Tests/TestBase.cs +++ /dev/null @@ -1,324 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -#if FEATURE_BOOKSLEEVE -using BookSleeve; -#endif -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - - public abstract class TestBase : IDisposable - { - - protected void CollectGarbage() - { - for (int i = 0; i < 3; i++) - { - GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); - GC.WaitForPendingFinalizers(); - } - } - private readonly SocketManager socketManager; - - protected TestBase() - { - socketManager = new SocketManager(GetType().Name); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")] - public void Dispose() - { - socketManager.Dispose(); - } -#if VERBOSE - protected const int AsyncOpsQty = 100, SyncOpsQty = 10; -#else - protected const int AsyncOpsQty = 100000, SyncOpsQty = 10000; -#endif - static TestBase() - { - TaskScheduler.UnobservedTaskException += (sender,args)=> - { - Console.WriteLine("Unobserved: " + args.Exception); - args.SetObserved(); -#if CORE_CLR - if (IgnorableExceptionPredicates.Any(predicate => predicate(args.Exception.InnerException))) return; -#endif - Interlocked.Increment(ref failCount); - lock (exceptions) - { - exceptions.Add(args.Exception.Message); - } - }; - } - -#if CORE_CLR - static Func[] IgnorableExceptionPredicates = new Func[] - { - e => e != null && e is ObjectDisposedException && e.Message.Equals("Cannot access a disposed object.\r\nObject name: 'System.Net.Sockets.NetworkStream'."), - e => e != null && e is IOException && e.Message.StartsWith("Unable to read data from the transport connection:") - }; -#endif - - protected void OnConnectionFailed(object sender, ConnectionFailedEventArgs e) - { - Interlocked.Increment(ref failCount); - lock(exceptions) - { - exceptions.Add("Connection failed: " + EndPointCollection.ToString(e.EndPoint) + "/" + e.ConnectionType); - } - } - - protected void OnInternalError(object sender, InternalErrorEventArgs e) - { - Interlocked.Increment(ref failCount); - lock (exceptions) - { - exceptions.Add("Internal error: " + e.Origin + ", " + EndPointCollection.ToString(e.EndPoint) + "/" + e.ConnectionType); - } - } - - static int failCount; - volatile int expectedFailCount; - [SetUp] - public void Setup() - { - ClearAmbientFailures(); - } - public void ClearAmbientFailures() - { - Collect(); - Interlocked.Exchange(ref failCount, 0); - expectedFailCount = 0; - lock(exceptions) - { - exceptions.Clear(); - } - } - private static readonly List exceptions = new List(); - public void SetExpectedAmbientFailureCount(int count) - { - expectedFailCount = count; - } - static void Collect() { - for (int i = 0; i < GC.MaxGeneration; i++) - { - GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true); - GC.WaitForPendingFinalizers(); - } - } - [TearDown] - public void Teardown() - { - Collect(); - if (expectedFailCount >= 0 && Interlocked.CompareExchange(ref failCount, 0, 0) != expectedFailCount) - { - var sb = new StringBuilder("There were ").Append(failCount).Append(" general ambient exceptions; expected ").Append(expectedFailCount); - lock(exceptions) - { - foreach(var item in exceptions.Take(5)) - { - sb.Append("; ").Append(item); - } - } - Assert.Fail(sb.ToString()); - } - } - - protected const int PrimaryPort = 6379, SlavePort = 6380, SecurePort = 6381; - protected const string PrimaryServer = "127.0.0.1", SecurePassword = "changeme", PrimaryPortString = "6379", SlavePortString = "6380", SecurePortString = "6381"; - internal static Task Swallow(Task task) - { - if (task != null) task.ContinueWith(swallowErrors, TaskContinuationOptions.OnlyOnFaulted); - return task; - } - private static readonly Action swallowErrors = SwallowErrors; - private static void SwallowErrors(Task task) - { - if (task != null) GC.KeepAlive(task.Exception); - } - protected IServer GetServer(ConnectionMultiplexer muxer) - { - EndPoint[] endpoints = muxer.GetEndPoints(); - IServer result = null; - foreach(var endpoint in endpoints) - { - var server = muxer.GetServer(endpoint); - if (server.IsSlave || !server.IsConnected) continue; - if(result != null) throw new InvalidOperationException("Requires exactly one master endpoint (found " + server.EndPoint + " and " + result.EndPoint + ")"); - result = server; - } - if(result == null) throw new InvalidOperationException("Requires exactly one master endpoint (found none)"); - return result; - } - - protected virtual ConnectionMultiplexer Create( - string clientName = null, int? syncTimeout = null, bool? allowAdmin = null, int? keepAlive = null, - int? connectTimeout = null, string password = null, string tieBreaker = null, TextWriter log = null, - bool fail = true, string[] disabledCommands = null, string[] enabledCommands = null, - bool checkConnect = true, bool pause = true, string failMessage = null, - string channelPrefix = null, bool useSharedSocketManager = true, Proxy? proxy = null) - { - if(pause) Thread.Sleep(250); // get a lot of glitches when hammering new socket creations etc; pace it out a bit - string configuration = GetConfiguration(); - var config = ConfigurationOptions.Parse(configuration); - if (disabledCommands != null && disabledCommands.Length != 0) - { - config.CommandMap = CommandMap.Create(new HashSet(disabledCommands), false); - } else if (enabledCommands != null && enabledCommands.Length != 0) - { - config.CommandMap = CommandMap.Create(new HashSet(enabledCommands), true); - } - - if(Debugger.IsAttached) - { - syncTimeout = int.MaxValue; - } - - if (useSharedSocketManager) config.SocketManager = socketManager; - if (channelPrefix != null) config.ChannelPrefix = channelPrefix; - if (tieBreaker != null) config.TieBreaker = tieBreaker; - if (password != null) config.Password = string.IsNullOrEmpty(password) ? null : password; - if (clientName != null) config.ClientName = clientName; - if (syncTimeout != null) config.SyncTimeout = syncTimeout.Value; - if (allowAdmin != null) config.AllowAdmin = allowAdmin.Value; - if (keepAlive != null) config.KeepAlive = keepAlive.Value; - if (connectTimeout != null) config.ConnectTimeout = connectTimeout.Value; - if (proxy != null) config.Proxy = proxy.Value; - var watch = Stopwatch.StartNew(); - var task = ConnectionMultiplexer.ConnectAsync(config, log ?? Console.Out); - if (!task.Wait(config.ConnectTimeout >= (int.MaxValue / 2) ? int.MaxValue : config.ConnectTimeout * 2)) - { - task.ContinueWith(x => - { - try - { - GC.KeepAlive(x.Exception); - } - catch - { } - }, TaskContinuationOptions.OnlyOnFaulted); - throw new TimeoutException("Connect timeout"); - } - watch.Stop(); - Console.WriteLine("Connect took: " + watch.ElapsedMilliseconds + "ms"); - var muxer = task.Result; - if (checkConnect) - { - if (!muxer.IsConnected) - { - if (fail) Assert.Fail(failMessage + "Server is not available"); - Assert.Inconclusive(failMessage + "Server is not available"); - } - } - muxer.InternalError += OnInternalError; - muxer.ConnectionFailed += OnConnectionFailed; - return muxer; - } - - protected virtual string GetConfiguration() - { - return PrimaryServer + ":" + PrimaryPort + "," + PrimaryServer + ":" + SlavePort; - } - - protected static string Me([CallerMemberName] string caller = null) - { - return caller; - } - -#if FEATURE_BOOKSLEEVE - protected static RedisConnection GetOldStyleConnection(bool open = true, bool allowAdmin = false, bool waitForOpen = false, int syncTimeout = 5000, int ioTimeout = 5000) - { - return GetOldStyleConnection(PrimaryServer, PrimaryPort, open, allowAdmin, waitForOpen, syncTimeout, ioTimeout); - } - private static RedisConnection GetOldStyleConnection(string host, int port, bool open = true, bool allowAdmin = false, bool waitForOpen = false, int syncTimeout = 5000, int ioTimeout = 5000) - { - var conn = new RedisConnection(host, port, syncTimeout: syncTimeout, ioTimeout: ioTimeout, allowAdmin: allowAdmin); - conn.Error += (s, args) => - { - Trace.WriteLine(args.Exception.Message, args.Cause); - }; - if (open) - { - var openAsync = conn.Open(); - if (waitForOpen) conn.Wait(openAsync); - } - return conn; - } -#endif - protected static TimeSpan RunConcurrent(Action work, int threads, int timeout = 10000, [CallerMemberName] string caller = null) - { - if (work == null) throw new ArgumentNullException(nameof(work)); - if (threads < 1) throw new ArgumentOutOfRangeException(nameof(threads)); - if(string.IsNullOrWhiteSpace(caller)) caller = Me(); - Stopwatch watch = null; - ManualResetEvent allDone = new ManualResetEvent(false); - object token = new object(); - int active = 0; - ThreadStart callback = delegate - { - lock (token) - { - int nowActive = Interlocked.Increment(ref active); - if (nowActive == threads) - { - watch = Stopwatch.StartNew(); - Monitor.PulseAll(token); - } - else - { - Monitor.Wait(token); - } - } - work(); - if (Interlocked.Decrement(ref active) == 0) - { - watch.Stop(); - allDone.Set(); - } - }; - - Thread[] threadArr = new Thread[threads]; - for (int i = 0; i < threads; i++) - { - var thd = new Thread(callback); - thd.Name = caller; - threadArr[i] = thd; - thd.Start(); - } - if (!allDone.WaitOne(timeout)) - { -#if !CORE_CLR - for (int i = 0; i < threads; i++) - { - var thd = threadArr[i]; - if (thd.IsAlive) thd.Abort(); - } -#endif - throw new TimeoutException(); - } - - return watch.Elapsed; - } - - - protected virtual void GetAzureCredentials(out string name, out string password) - { - var lines = File.ReadAllLines(@"d:\dev\azure.txt"); - if (lines == null || lines.Length != 2) - Assert.Inconclusive("azure credentials missing"); - name = lines[0]; - password = lines[1]; - } - - } -} diff --git a/StackExchange.Redis.Tests/TestInfoReplicationChecks.cs b/StackExchange.Redis.Tests/TestInfoReplicationChecks.cs deleted file mode 100644 index f698be5aa..000000000 --- a/StackExchange.Redis.Tests/TestInfoReplicationChecks.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Threading; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class TestInfoReplicationChecks : TestBase - { - [Test] - public void Exec() - { - using(var conn = Create()) - { - var parsed = ConfigurationOptions.Parse(conn.Configuration); - Assert.AreEqual(5, parsed.ConfigCheckSeconds); - var before = conn.GetCounters(); - Thread.Sleep(TimeSpan.FromSeconds(13)); - var after = conn.GetCounters(); - int done = (int)(after.Interactive.CompletedSynchronously - before.Interactive.CompletedSynchronously); - Assert.IsTrue(done >= 2); - } - } - protected override string GetConfiguration() - { - return base.GetConfiguration() + ",configCheckSeconds=5"; - } - } -} diff --git a/StackExchange.Redis.Tests/TransactionWrapperTests.cs b/StackExchange.Redis.Tests/TransactionWrapperTests.cs deleted file mode 100644 index 967f3575a..000000000 --- a/StackExchange.Redis.Tests/TransactionWrapperTests.cs +++ /dev/null @@ -1,93 +0,0 @@ -#if FEATURE_MOQ -using System.Text; -using Moq; -using NUnit.Framework; -using StackExchange.Redis.KeyspaceIsolation; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public sealed class TransactionWrapperTests - { - private Mock mock; - private TransactionWrapper wrapper; - - [OneTimeSetUp] - public void Initialize() - { - mock = new Mock(); - wrapper = new TransactionWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:")); - } - - [Test] - public void AddCondition_HashEqual() - { - wrapper.AddCondition(Condition.HashEqual("key", "field", "value")); - mock.Verify(_ => _.AddCondition(It.Is(value => "prefix:key > field == value" == value.ToString()))); - } - - [Test] - public void AddCondition_HashNotEqual() - { - wrapper.AddCondition(Condition.HashNotEqual("key", "field", "value")); - mock.Verify(_ => _.AddCondition(It.Is(value => "prefix:key > field != value" == value.ToString()))); - } - - [Test] - public void AddCondition_HashExists() - { - wrapper.AddCondition(Condition.HashExists("key", "field")); - mock.Verify(_ => _.AddCondition(It.Is(value => "prefix:key > field exists" == value.ToString()))); - } - - [Test] - public void AddCondition_HashNotExists() - { - wrapper.AddCondition(Condition.HashNotExists("key", "field")); - mock.Verify(_ => _.AddCondition(It.Is(value => "prefix:key > field does not exists" == value.ToString()))); - } - - [Test] - public void AddCondition_KeyExists() - { - wrapper.AddCondition(Condition.KeyExists("key")); - mock.Verify(_ => _.AddCondition(It.Is(value => "prefix:key exists" == value.ToString()))); - } - - [Test] - public void AddCondition_KeyNotExists() - { - wrapper.AddCondition(Condition.KeyNotExists("key")); - mock.Verify(_ => _.AddCondition(It.Is(value => "prefix:key does not exists" == value.ToString()))); - } - - [Test] - public void AddCondition_StringEqual() - { - wrapper.AddCondition(Condition.StringEqual("key", "value")); - mock.Verify(_ => _.AddCondition(It.Is(value => "prefix:key == value" == value.ToString()))); - } - - [Test] - public void AddCondition_StringNotEqual() - { - wrapper.AddCondition(Condition.StringNotEqual("key", "value")); - mock.Verify(_ => _.AddCondition(It.Is(value => "prefix:key != value" == value.ToString()))); - } - - [Test] - public void ExecuteAsync() - { - wrapper.ExecuteAsync(CommandFlags.HighPriority); - mock.Verify(_ => _.ExecuteAsync(CommandFlags.HighPriority), Times.Once()); - } - - [Test] - public void Execute() - { - wrapper.Execute(CommandFlags.HighPriority); - mock.Verify(_ => _.Execute(CommandFlags.HighPriority), Times.Once()); - } - } -} -#endif \ No newline at end of file diff --git a/StackExchange.Redis.Tests/Transactions.cs b/StackExchange.Redis.Tests/Transactions.cs deleted file mode 100644 index 091431ec0..000000000 --- a/StackExchange.Redis.Tests/Transactions.cs +++ /dev/null @@ -1,754 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class Transactions : TestBase - { - [Test] - public void BasicEmptyTran() - { - using(var muxer = Create()) - { - RedisKey key = Me(); - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - Assert.IsFalse(db.KeyExists(key)); - - var tran = db.CreateTransaction(); - - var result = tran.Execute(); - Assert.IsTrue(result); - } - } - - [Test] - [TestCase(false, false, true)] - [TestCase(false, true, false)] - [TestCase(true, false, false)] - [TestCase(true, true, true)] - public void BasicTranWithExistsCondition(bool demandKeyExists, bool keyExists, bool expectTran) - { - using (var muxer = Create(disabledCommands: new[] { "info", "config" })) - { - RedisKey key = Me(), key2 = Me() + "2"; - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.KeyDelete(key2, CommandFlags.FireAndForget); - if (keyExists) db.StringSet(key2, "any value", flags: CommandFlags.FireAndForget); - Assert.IsFalse(db.KeyExists(key)); - Assert.AreEqual(keyExists, db.KeyExists(key2)); - - var tran = db.CreateTransaction(); - var cond = tran.AddCondition(demandKeyExists ? Condition.KeyExists(key2) : Condition.KeyNotExists(key2)); - var incr = tran.StringIncrementAsync(key); - var exec = tran.ExecuteAsync(); - var get = db.StringGet(key); - - Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result"); - if (demandKeyExists == keyExists) - { - Assert.IsTrue(db.Wait(exec), "eq: exec"); - Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied"); - Assert.AreEqual(1, db.Wait(incr), "eq: incr"); - Assert.AreEqual(1, (long)get, "eq: get"); - } - else - { - Assert.IsFalse(db.Wait(exec), "neq: exec"); - Assert.False(cond.WasSatisfied, "neq: was satisfied"); - Assert.AreEqual(TaskStatus.Canceled, incr.Status, "neq: incr"); - Assert.AreEqual(0, (long)get, "neq: get"); - } - } - } - - [Test] - [TestCase("same", "same", true, true)] - [TestCase("x", "y", true, false)] - [TestCase("x", null, true, false)] - [TestCase(null, "y", true, false)] - [TestCase(null, null, true, true)] - - [TestCase("same", "same", false, false)] - [TestCase("x", "y", false, true)] - [TestCase("x", null, false, true)] - [TestCase(null, "y", false, true)] - [TestCase(null, null, false, false)] - public void BasicTranWithEqualsCondition(string expected, string value, bool expectEqual, bool expectTran) - { - using (var muxer = Create()) - { - RedisKey key = Me(), key2 = Me() + "2"; - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.KeyDelete(key2, CommandFlags.FireAndForget); - - if (value != null) db.StringSet(key2, value, flags: CommandFlags.FireAndForget); - Assert.IsFalse(db.KeyExists(key)); - Assert.AreEqual(value, (string)db.StringGet(key2)); - - var tran = db.CreateTransaction(); - var cond = tran.AddCondition(expectEqual ? Condition.StringEqual(key2, expected) : Condition.StringNotEqual(key2, expected)); - var incr = tran.StringIncrementAsync(key); - var exec = tran.ExecuteAsync(); - var get = db.StringGet(key); - - Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result"); - if (expectEqual == (value == expected)) - { - Assert.IsTrue(db.Wait(exec), "eq: exec"); - Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied"); - Assert.AreEqual(1, db.Wait(incr), "eq: incr"); - Assert.AreEqual(1, (long)get, "eq: get"); - } - else - { - Assert.IsFalse(db.Wait(exec), "neq: exec"); - Assert.False(cond.WasSatisfied, "neq: was satisfied"); - Assert.AreEqual(TaskStatus.Canceled, incr.Status, "neq: incr"); - Assert.AreEqual(0, (long)get, "neq: get"); - } - } - } - - - [Test] - [TestCase(false, false, true)] - [TestCase(false, true, false)] - [TestCase(true, false, false)] - [TestCase(true, true, true)] - public void BasicTranWithHashExistsCondition(bool demandKeyExists, bool keyExists, bool expectTran) - { - using (var muxer = Create(disabledCommands: new[] { "info", "config" })) - { - RedisKey key = Me(), key2 = Me() + "2"; - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.KeyDelete(key2, CommandFlags.FireAndForget); - RedisValue hashField = "field"; - if (keyExists) db.HashSet(key2, hashField, "any value", flags: CommandFlags.FireAndForget); - Assert.IsFalse(db.KeyExists(key)); - Assert.AreEqual(keyExists, db.HashExists(key2, hashField)); - - var tran = db.CreateTransaction(); - var cond = tran.AddCondition(demandKeyExists ? Condition.HashExists(key2, hashField) : Condition.HashNotExists(key2, hashField)); - var incr = tran.StringIncrementAsync(key); - var exec = tran.ExecuteAsync(); - var get = db.StringGet(key); - - Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result"); - if (demandKeyExists == keyExists) - { - Assert.IsTrue(db.Wait(exec), "eq: exec"); - Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied"); - Assert.AreEqual(1, db.Wait(incr), "eq: incr"); - Assert.AreEqual(1, (long)get, "eq: get"); - } - else - { - Assert.IsFalse(db.Wait(exec), "neq: exec"); - Assert.False(cond.WasSatisfied, "neq: was satisfied"); - Assert.AreEqual(TaskStatus.Canceled, incr.Status, "neq: incr"); - Assert.AreEqual(0, (long)get, "neq: get"); - } - } - } - - [Test] - [TestCase("same", "same", true, true)] - [TestCase("x", "y", true, false)] - [TestCase("x", null, true, false)] - [TestCase(null, "y", true, false)] - [TestCase(null, null, true, true)] - - [TestCase("same", "same", false, false)] - [TestCase("x", "y", false, true)] - [TestCase("x", null, false, true)] - [TestCase(null, "y", false, true)] - [TestCase(null, null, false, false)] - public void BasicTranWithHashEqualsCondition(string expected, string value, bool expectEqual, bool expectTran) - { - using (var muxer = Create()) - { - RedisKey key = Me(), key2 = Me() + "2"; - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.KeyDelete(key2, CommandFlags.FireAndForget); - - RedisValue hashField = "field"; - if (value != null) db.HashSet(key2, hashField, value, flags: CommandFlags.FireAndForget); - Assert.IsFalse(db.KeyExists(key)); - Assert.AreEqual(value, (string)db.HashGet(key2, hashField)); - - - var tran = db.CreateTransaction(); - var cond = tran.AddCondition(expectEqual ? Condition.HashEqual(key2, hashField, expected) : Condition.HashNotEqual(key2, hashField, expected)); - var incr = tran.StringIncrementAsync(key); - var exec = tran.ExecuteAsync(); - var get = db.StringGet(key); - - Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result"); - if (expectEqual == (value == expected)) - { - Assert.IsTrue(db.Wait(exec), "eq: exec"); - Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied"); - Assert.AreEqual(1, db.Wait(incr), "eq: incr"); - Assert.AreEqual(1, (long)get, "eq: get"); - } - else - { - Assert.IsFalse(db.Wait(exec), "neq: exec"); - Assert.False(cond.WasSatisfied, "neq: was satisfied"); - Assert.AreEqual(TaskStatus.Canceled, incr.Status, "neq: incr"); - Assert.AreEqual(0, (long)get, "neq: get"); - } - } - } - - [Test] - [TestCase(false, false, true)] - [TestCase(false, true, false)] - [TestCase(true, false, false)] - [TestCase(true, true, true)] - public void BasicTranWithListExistsCondition(bool demandKeyExists, bool keyExists, bool expectTran) - { - using (var muxer = Create(disabledCommands: new[] { "info", "config" })) - { - RedisKey key = Me(), key2 = Me() + "2"; - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.KeyDelete(key2, CommandFlags.FireAndForget); - if (keyExists) db.ListRightPush(key2, "any value", flags: CommandFlags.FireAndForget); - Assert.IsFalse(db.KeyExists(key)); - Assert.AreEqual(keyExists, db.KeyExists(key2)); - - var tran = db.CreateTransaction(); - var cond = tran.AddCondition(demandKeyExists ? Condition.ListIndexExists(key2, 0) : Condition.ListIndexNotExists(key2, 0)); - var push = tran.ListRightPushAsync(key, "any value"); - var exec = tran.ExecuteAsync(); - var get = db.ListGetByIndex(key, 0); - - Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result"); - if (demandKeyExists == keyExists) - { - Assert.IsTrue(db.Wait(exec), "eq: exec"); - Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied"); - Assert.AreEqual(1, db.Wait(push), "eq: push"); - Assert.AreEqual("any value", (string)get, "eq: get"); - } - else - { - Assert.IsFalse(db.Wait(exec), "neq: exec"); - Assert.False(cond.WasSatisfied, "neq: was satisfied"); - Assert.AreEqual(TaskStatus.Canceled, push.Status, "neq: push"); - Assert.AreEqual(null, (string)get, "neq: get"); - } - } - } - - [Test] - [TestCase("same", "same", true, true)] - [TestCase("x", "y", true, false)] - [TestCase("x", null, true, false)] - [TestCase(null, "y", true, false)] - [TestCase(null, null, true, true)] - - [TestCase("same", "same", false, false)] - [TestCase("x", "y", false, true)] - [TestCase("x", null, false, true)] - [TestCase(null, "y", false, true)] - [TestCase(null, null, false, false)] - public void BasicTranWithListEqualsCondition(string expected, string value, bool expectEqual, bool expectTran) - { - using (var muxer = Create()) - { - RedisKey key = Me(), key2 = Me() + "2"; - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.KeyDelete(key2, CommandFlags.FireAndForget); - - if (value != null) db.ListRightPush(key2, value, flags: CommandFlags.FireAndForget); - Assert.IsFalse(db.KeyExists(key)); - Assert.AreEqual(value, (string)db.ListGetByIndex(key2, 0)); - - var tran = db.CreateTransaction(); - var cond = tran.AddCondition(expectEqual ? Condition.ListIndexEqual(key2, 0, expected) : Condition.ListIndexNotEqual(key2, 0, expected)); - var push = tran.ListRightPushAsync(key, "any value"); - var exec = tran.ExecuteAsync(); - var get = db.ListGetByIndex(key, 0); - - Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result"); - if (expectEqual == (value == expected)) - { - Assert.IsTrue(db.Wait(exec), "eq: exec"); - Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied"); - Assert.AreEqual(1, db.Wait(push), "eq: push"); - Assert.AreEqual("any value", (string)get, "eq: get"); - } - else - { - Assert.IsFalse(db.Wait(exec), "neq: exec"); - Assert.False(cond.WasSatisfied, "neq: was satisfied"); - Assert.AreEqual(TaskStatus.Canceled, push.Status, "neq: push"); - Assert.AreEqual(null, (string)get, "neq: get"); - } - } - } - - public enum ComparisonType - { - Equal, - LessThan, - GreaterThan - } - - [Test] - [TestCase("five", ComparisonType.Equal, 5L, false)] - [TestCase("four", ComparisonType.Equal, 4L, true)] - [TestCase("three", ComparisonType.Equal, 3L, false)] - [TestCase("", ComparisonType.Equal, 2L, false)] - [TestCase("", ComparisonType.Equal, 0L, true)] - [TestCase(null, ComparisonType.Equal, 1L, false)] - [TestCase(null, ComparisonType.Equal, 0L, true)] - - [TestCase("five", ComparisonType.LessThan, 5L, true)] - [TestCase("four", ComparisonType.LessThan, 4L, false)] - [TestCase("three", ComparisonType.LessThan, 3L, false)] - [TestCase("", ComparisonType.LessThan, 2L, true)] - [TestCase("", ComparisonType.LessThan, 0L, false)] - [TestCase(null, ComparisonType.LessThan, 1L, true)] - [TestCase(null, ComparisonType.LessThan, 0L, false)] - - [TestCase("five", ComparisonType.GreaterThan, 5L, false)] - [TestCase("four", ComparisonType.GreaterThan, 4L, false)] - [TestCase("three", ComparisonType.GreaterThan, 3L, true)] - [TestCase("", ComparisonType.GreaterThan, 2L, false)] - [TestCase("", ComparisonType.GreaterThan, 0L, false)] - [TestCase(null, ComparisonType.GreaterThan, 1L, false)] - [TestCase(null, ComparisonType.GreaterThan, 0L, false)] - public void BasicTranWithStringLengthCondition(string value, ComparisonType type, long length, bool expectTran) - { - using (var muxer = Create()) - { - RedisKey key = Me(), key2 = Me() + "2"; - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.KeyDelete(key2, CommandFlags.FireAndForget); - - var expectSuccess = false; - Condition condition = null; - var valueLength = value?.Length ?? 0; - switch (type) { - case ComparisonType.Equal: - expectSuccess = valueLength == length; - condition = Condition.StringLengthEqual(key2, length); - Assert.That(condition.ToString(), Contains.Substring("String length == " + length)); - break; - case ComparisonType.GreaterThan: - expectSuccess = valueLength > length; - condition = Condition.StringLengthGreaterThan(key2, length); - Assert.That(condition.ToString(), Contains.Substring("String length > " + length)); - break; - case ComparisonType.LessThan: - expectSuccess = valueLength < length; - condition = Condition.StringLengthLessThan(key2, length); - Assert.That(condition.ToString(), Contains.Substring("String length < " + length)); - break; - } - - if (value != null) db.StringSet(key2, value, flags: CommandFlags.FireAndForget); - Assert.IsFalse(db.KeyExists(key)); - Assert.AreEqual(value, (string)db.StringGet(key2)); - - var tran = db.CreateTransaction(); - var cond = tran.AddCondition(condition); - var push = tran.StringSetAsync(key, "any value"); - var exec = tran.ExecuteAsync(); - var get = db.StringLength(key); - - Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result"); - - if (expectSuccess) { - Assert.IsTrue(db.Wait(exec), "eq: exec"); - Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied"); - Assert.AreEqual(true, db.Wait(push), "eq: push"); - Assert.AreEqual("any value".Length, get, "eq: get"); - } else { - Assert.IsFalse(db.Wait(exec), "neq: exec"); - Assert.False(cond.WasSatisfied, "neq: was satisfied"); - Assert.AreEqual(TaskStatus.Canceled, push.Status, "neq: push"); - Assert.AreEqual(0, get, "neq: get"); - } - } - } - - [Test] - [TestCase("five", ComparisonType.Equal, 5L, false)] - [TestCase("four", ComparisonType.Equal, 4L, true)] - [TestCase("three", ComparisonType.Equal, 3L, false)] - [TestCase("", ComparisonType.Equal, 2L, false)] - [TestCase("", ComparisonType.Equal, 0L, true)] - - [TestCase("five", ComparisonType.LessThan, 5L, true)] - [TestCase("four", ComparisonType.LessThan, 4L, false)] - [TestCase("three", ComparisonType.LessThan, 3L, false)] - [TestCase("", ComparisonType.LessThan, 2L, true)] - [TestCase("", ComparisonType.LessThan, 0L, false)] - - [TestCase("five", ComparisonType.GreaterThan, 5L, false)] - [TestCase("four", ComparisonType.GreaterThan, 4L, false)] - [TestCase("three", ComparisonType.GreaterThan, 3L, true)] - [TestCase("", ComparisonType.GreaterThan, 2L, false)] - [TestCase("", ComparisonType.GreaterThan, 0L, false)] - public void BasicTranWithHashLengthCondition(string value, ComparisonType type, long length, bool expectTran) - { - using (var muxer = Create()) - { - RedisKey key = Me(), key2 = Me() + "2"; - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.KeyDelete(key2, CommandFlags.FireAndForget); - - var expectSuccess = false; - Condition condition = null; - var valueLength = value?.Length ?? 0; - switch (type) { - case ComparisonType.Equal: - expectSuccess = valueLength == length; - condition = Condition.HashLengthEqual(key2, length); - break; - case ComparisonType.GreaterThan: - expectSuccess = valueLength > length; - condition = Condition.HashLengthGreaterThan(key2, length); - break; - case ComparisonType.LessThan: - expectSuccess = valueLength < length; - condition = Condition.HashLengthLessThan(key2, length); - break; - } - - for (var i = 0; i < valueLength; i++) { - db.HashSet(key2, i, value[i].ToString(), flags: CommandFlags.FireAndForget); - } - Assert.IsFalse(db.KeyExists(key)); - Assert.AreEqual(valueLength, db.HashLength(key2)); - - var tran = db.CreateTransaction(); - var cond = tran.AddCondition(condition); - var push = tran.StringSetAsync(key, "any value"); - var exec = tran.ExecuteAsync(); - var get = db.StringLength(key); - - Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result"); - - if (expectSuccess) { - Assert.IsTrue(db.Wait(exec), "eq: exec"); - Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied"); - Assert.AreEqual(true, db.Wait(push), "eq: push"); - Assert.AreEqual("any value".Length, get, "eq: get"); - } else { - Assert.IsFalse(db.Wait(exec), "neq: exec"); - Assert.False(cond.WasSatisfied, "neq: was satisfied"); - Assert.AreEqual(TaskStatus.Canceled, push.Status, "neq: push"); - Assert.AreEqual(0, get, "neq: get"); - } - } - } - - [Test] - [TestCase("five", ComparisonType.Equal, 5L, false)] - [TestCase("four", ComparisonType.Equal, 4L, true)] - [TestCase("three", ComparisonType.Equal, 3L, false)] - [TestCase("", ComparisonType.Equal, 2L, false)] - [TestCase("", ComparisonType.Equal, 0L, true)] - - [TestCase("five", ComparisonType.LessThan, 5L, true)] - [TestCase("four", ComparisonType.LessThan, 4L, false)] - [TestCase("three", ComparisonType.LessThan, 3L, false)] - [TestCase("", ComparisonType.LessThan, 2L, true)] - [TestCase("", ComparisonType.LessThan, 0L, false)] - - [TestCase("five", ComparisonType.GreaterThan, 5L, false)] - [TestCase("four", ComparisonType.GreaterThan, 4L, false)] - [TestCase("three", ComparisonType.GreaterThan, 3L, true)] - [TestCase("", ComparisonType.GreaterThan, 2L, false)] - [TestCase("", ComparisonType.GreaterThan, 0L, false)] - public void BasicTranWithSetCardinalityCondition(string value, ComparisonType type, long length, bool expectTran) - { - using (var muxer = Create()) - { - RedisKey key = Me(), key2 = Me() + "2"; - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.KeyDelete(key2, CommandFlags.FireAndForget); - - var expectSuccess = false; - Condition condition = null; - var valueLength = value?.Length ?? 0; - switch (type) { - case ComparisonType.Equal: - expectSuccess = valueLength == length; - condition = Condition.SetLengthEqual(key2, length); - break; - case ComparisonType.GreaterThan: - expectSuccess = valueLength > length; - condition = Condition.SetLengthGreaterThan(key2, length); - break; - case ComparisonType.LessThan: - expectSuccess = valueLength < length; - condition = Condition.SetLengthLessThan(key2, length); - break; - } - - for (var i = 0; i < valueLength; i++) { - db.SetAdd(key2, i, flags: CommandFlags.FireAndForget); - } - Assert.IsFalse(db.KeyExists(key)); - Assert.AreEqual(valueLength, db.SetLength(key2)); - - var tran = db.CreateTransaction(); - var cond = tran.AddCondition(condition); - var push = tran.StringSetAsync(key, "any value"); - var exec = tran.ExecuteAsync(); - var get = db.StringLength(key); - - Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result"); - - if (expectSuccess) { - Assert.IsTrue(db.Wait(exec), "eq: exec"); - Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied"); - Assert.AreEqual(true, db.Wait(push), "eq: push"); - Assert.AreEqual("any value".Length, get, "eq: get"); - } else { - Assert.IsFalse(db.Wait(exec), "neq: exec"); - Assert.False(cond.WasSatisfied, "neq: was satisfied"); - Assert.AreEqual(TaskStatus.Canceled, push.Status, "neq: push"); - Assert.AreEqual(0, get, "neq: get"); - } - } - } - - [Test] - [TestCase("five", ComparisonType.Equal, 5L, false)] - [TestCase("four", ComparisonType.Equal, 4L, true)] - [TestCase("three", ComparisonType.Equal, 3L, false)] - [TestCase("", ComparisonType.Equal, 2L, false)] - [TestCase("", ComparisonType.Equal, 0L, true)] - - [TestCase("five", ComparisonType.LessThan, 5L, true)] - [TestCase("four", ComparisonType.LessThan, 4L, false)] - [TestCase("three", ComparisonType.LessThan, 3L, false)] - [TestCase("", ComparisonType.LessThan, 2L, true)] - [TestCase("", ComparisonType.LessThan, 0L, false)] - - [TestCase("five", ComparisonType.GreaterThan, 5L, false)] - [TestCase("four", ComparisonType.GreaterThan, 4L, false)] - [TestCase("three", ComparisonType.GreaterThan, 3L, true)] - [TestCase("", ComparisonType.GreaterThan, 2L, false)] - [TestCase("", ComparisonType.GreaterThan, 0L, false)] - public void BasicTranWithSortedSetCardinalityCondition(string value, ComparisonType type, long length, bool expectTran) - { - using (var muxer = Create()) - { - RedisKey key = Me(), key2 = Me() + "2"; - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.KeyDelete(key2, CommandFlags.FireAndForget); - - var expectSuccess = false; - Condition condition = null; - var valueLength = value?.Length ?? 0; - switch (type) { - case ComparisonType.Equal: - expectSuccess = valueLength == length; - condition = Condition.SortedSetLengthEqual(key2, length); - break; - case ComparisonType.GreaterThan: - expectSuccess = valueLength > length; - condition = Condition.SortedSetLengthGreaterThan(key2, length); - break; - case ComparisonType.LessThan: - expectSuccess = valueLength < length; - condition = Condition.SortedSetLengthLessThan(key2, length); - break; - } - - for (var i = 0; i < valueLength; i++) { - db.SortedSetAdd(key2, i, i, flags: CommandFlags.FireAndForget); - } - Assert.IsFalse(db.KeyExists(key)); - Assert.AreEqual(valueLength, db.SortedSetLength(key2)); - - var tran = db.CreateTransaction(); - var cond = tran.AddCondition(condition); - var push = tran.StringSetAsync(key, "any value"); - var exec = tran.ExecuteAsync(); - var get = db.StringLength(key); - - Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result"); - - if (expectSuccess) { - Assert.IsTrue(db.Wait(exec), "eq: exec"); - Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied"); - Assert.AreEqual(true, db.Wait(push), "eq: push"); - Assert.AreEqual("any value".Length, get, "eq: get"); - } else { - Assert.IsFalse(db.Wait(exec), "neq: exec"); - Assert.False(cond.WasSatisfied, "neq: was satisfied"); - Assert.AreEqual(TaskStatus.Canceled, push.Status, "neq: push"); - Assert.AreEqual(0, get, "neq: get"); - } - } - } - - [Test] - [TestCase("five", ComparisonType.Equal, 5L, false)] - [TestCase("four", ComparisonType.Equal, 4L, true)] - [TestCase("three", ComparisonType.Equal, 3L, false)] - [TestCase("", ComparisonType.Equal, 2L, false)] - [TestCase("", ComparisonType.Equal, 0L, true)] - - [TestCase("five", ComparisonType.LessThan, 5L, true)] - [TestCase("four", ComparisonType.LessThan, 4L, false)] - [TestCase("three", ComparisonType.LessThan, 3L, false)] - [TestCase("", ComparisonType.LessThan, 2L, true)] - [TestCase("", ComparisonType.LessThan, 0L, false)] - - [TestCase("five", ComparisonType.GreaterThan, 5L, false)] - [TestCase("four", ComparisonType.GreaterThan, 4L, false)] - [TestCase("three", ComparisonType.GreaterThan, 3L, true)] - [TestCase("", ComparisonType.GreaterThan, 2L, false)] - [TestCase("", ComparisonType.GreaterThan, 0L, false)] - public void BasicTranWithListLengthCondition(string value, ComparisonType type, long length, bool expectTran) - { - using (var muxer = Create()) - { - RedisKey key = Me(), key2 = Me() + "2"; - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - db.KeyDelete(key2, CommandFlags.FireAndForget); - - var expectSuccess = false; - Condition condition = null; - var valueLength = value?.Length ?? 0; - switch (type) { - case ComparisonType.Equal: - expectSuccess = valueLength == length; - condition = Condition.ListLengthEqual(key2, length); - break; - case ComparisonType.GreaterThan: - expectSuccess = valueLength > length; - condition = Condition.ListLengthGreaterThan(key2, length); - break; - case ComparisonType.LessThan: - expectSuccess = valueLength < length; - condition = Condition.ListLengthLessThan(key2, length); - break; - } - - for (var i = 0; i < valueLength; i++) { - db.ListRightPush(key2, i, flags: CommandFlags.FireAndForget); - } - Assert.IsFalse(db.KeyExists(key)); - Assert.AreEqual(valueLength, db.ListLength(key2)); - - var tran = db.CreateTransaction(); - var cond = tran.AddCondition(condition); - var push = tran.StringSetAsync(key, "any value"); - var exec = tran.ExecuteAsync(); - var get = db.StringLength(key); - - Assert.AreEqual(expectTran, db.Wait(exec), "expected tran result"); - - if (expectSuccess) { - Assert.IsTrue(db.Wait(exec), "eq: exec"); - Assert.IsTrue(cond.WasSatisfied, "eq: was satisfied"); - Assert.AreEqual(true, db.Wait(push), "eq: push"); - Assert.AreEqual("any value".Length, get, "eq: get"); - } else { - Assert.IsFalse(db.Wait(exec), "neq: exec"); - Assert.False(cond.WasSatisfied, "neq: was satisfied"); - Assert.AreEqual(TaskStatus.Canceled, push.Status, "neq: push"); - Assert.AreEqual(0, get, "neq: get"); - } - } - } - - [Test] - public async void BasicTran() - { - using (var muxer = Create()) - { - RedisKey key = Me(); - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - Assert.IsFalse(db.KeyExists(key)); - - var tran = db.CreateTransaction(); - var a = tran.StringIncrementAsync(key, 10); - var b = tran.StringIncrementAsync(key, 5); - var c = tran.StringGetAsync(key); - var d = tran.KeyExistsAsync(key); - var e = tran.KeyDeleteAsync(key); - var f = tran.KeyExistsAsync(key); - Assert.IsFalse(a.IsCompleted); - Assert.IsFalse(b.IsCompleted); - Assert.IsFalse(c.IsCompleted); - Assert.IsFalse(d.IsCompleted); - Assert.IsFalse(e.IsCompleted); - Assert.IsFalse(f.IsCompleted); - var result = db.Wait(tran.ExecuteAsync()); - Assert.IsTrue(result, "result"); - db.WaitAll(a, b, c, d, e, f); - Assert.IsTrue(a.IsCompleted, "a"); - Assert.IsTrue(b.IsCompleted, "b"); - Assert.IsTrue(c.IsCompleted, "c"); - Assert.IsTrue(d.IsCompleted, "d"); - Assert.IsTrue(e.IsCompleted, "e"); - Assert.IsTrue(f.IsCompleted, "f"); - - var g = db.KeyExists(key); - - - Assert.AreEqual(10, await a.ConfigureAwait(false)); - Assert.AreEqual(15, await b.ConfigureAwait(false)); - Assert.AreEqual(15, (long)await c.ConfigureAwait(false)); - Assert.IsTrue(await d.ConfigureAwait(false)); - Assert.IsTrue(await e.ConfigureAwait(false)); - Assert.IsFalse(await f.ConfigureAwait(false)); - Assert.IsFalse(g); - } - } - - [Test] - public void CombineFireAndForgetAndRegularAsyncInTransaction() - { - using (var muxer = Create()) - { - RedisKey key = Me(); - var db = muxer.GetDatabase(); - db.KeyDelete(key, CommandFlags.FireAndForget); - Assert.IsFalse(db.KeyExists(key)); - - var tran = db.CreateTransaction("state"); - var a = tran.StringIncrementAsync(key, 5); - var b = tran.StringIncrementAsync(key, 10, CommandFlags.FireAndForget); - var c = tran.StringIncrementAsync(key, 15); - Assert.IsTrue(tran.Execute()); - var count = (long)db.StringGet(key); - - Assert.AreEqual(5, db.Wait(a), "a"); - Assert.AreEqual("state", a.AsyncState); - Assert.AreEqual(0, db.Wait(b), "b"); - Assert.IsNull(b.AsyncState); - Assert.AreEqual(30, db.Wait(c), "c"); - Assert.AreEqual("state", a.AsyncState); - Assert.AreEqual(30, count, "count"); - } - } - } -} diff --git a/StackExchange.Redis.Tests/VPNTest.cs b/StackExchange.Redis.Tests/VPNTest.cs deleted file mode 100644 index 19fe9cbe8..000000000 --- a/StackExchange.Redis.Tests/VPNTest.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.IO; -using NUnit.Framework; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public class VPNTest : TestBase - { - - [Test] - [MaxTime(100000)] - [TestCase("co-devredis01.ds.stackexchange.com:6379")] - public void Execute(string config) - { - for (int i = 0; i < 50; i++) - { - var log = new StringWriter(); - try - { - var options = ConfigurationOptions.Parse(config); - options.SyncTimeout = 3000; - options.ConnectRetry = 5; - using (var conn = ConnectionMultiplexer.Connect(options, log)) - { - var ttl = conn.GetDatabase().Ping(); - Console.WriteLine(ttl); - } - } - catch - { - Console.WriteLine(log); - Assert.Fail(); - } - Console.WriteLine(); - Console.WriteLine("==="); - Console.WriteLine(); - } - } - } -} diff --git a/StackExchange.Redis.Tests/WithKeyPrefixTests.cs b/StackExchange.Redis.Tests/WithKeyPrefixTests.cs deleted file mode 100644 index 84963306b..000000000 --- a/StackExchange.Redis.Tests/WithKeyPrefixTests.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using NUnit.Framework; -using StackExchange.Redis.KeyspaceIsolation; - -namespace StackExchange.Redis.Tests -{ - - [TestFixture] - public class WithKeyPrefixTests : TestBase - { - [Test] - public void BlankPrefixYieldsSame_Bytes() - { - using (var conn = Create()) - { - var raw = conn.GetDatabase(1); - var prefixed = raw.WithKeyPrefix(new byte[0]); - Assert.AreSame(raw, prefixed); - } - } - [Test] - public void BlankPrefixYieldsSame_String() - { - using (var conn = Create()) - { - var raw = conn.GetDatabase(1); - var prefixed = raw.WithKeyPrefix(""); - Assert.AreSame(raw, prefixed); - } - } - [Test] - public void NullPrefixIsError_Bytes() - { - Assert.Throws(() => { - using (var conn = Create()) - { - var raw = conn.GetDatabase(1); - var prefixed = raw.WithKeyPrefix((byte[])null); - } - }); - } - [Test] - public void NullPrefixIsError_String() - { - Assert.Throws(() => { - using (var conn = Create()) - { - var raw = conn.GetDatabase(1); - var prefixed = raw.WithKeyPrefix((string)null); - } - }); - } - - [Test] - [TestCase("abc")] - [TestCase("")] - [TestCase(null)] - public void NullDatabaseIsError(string prefix) - { - Assert.Throws(() => { - IDatabase raw = null; - var prefixed = raw.WithKeyPrefix(prefix); - }); - } - [Test] - public void BasicSmokeTest() - { - using(var conn = Create()) - { - var raw = conn.GetDatabase(1); - - var foo = raw.WithKeyPrefix("foo"); - var foobar = foo.WithKeyPrefix("bar"); - - string key = Me(); - - string s = Guid.NewGuid().ToString(), t = Guid.NewGuid().ToString(); - - foo.StringSet(key, s); - var val = (string)foo.StringGet(key); - Assert.AreEqual(s, val); // fooBasicSmokeTest - - foobar.StringSet(key, t); - val = (string)foobar.StringGet(key); - Assert.AreEqual(t, val); // foobarBasicSmokeTest - - val = (string)foo.StringGet("bar" + key); - Assert.AreEqual(t, val); // foobarBasicSmokeTest - - val = (string)raw.StringGet("foo" + key); - Assert.AreEqual(s, val); // fooBasicSmokeTest - - val = (string)raw.StringGet("foobar" + key); - Assert.AreEqual(t, val); // foobarBasicSmokeTest - } - } - [Test] - public void ConditionTest() - { - using(var conn = Create()) - { - var raw = conn.GetDatabase(2); - - var foo = raw.WithKeyPrefix("tran:"); - - raw.KeyDelete("tran:abc"); - raw.KeyDelete("tran:i"); - - // execute while key exists - raw.StringSet("tran:abc", "def"); - var tran = foo.CreateTransaction(); - tran.AddCondition(Condition.KeyExists("abc")); - tran.StringIncrementAsync("i"); - tran.Execute(); - - int i = (int)raw.StringGet("tran:i"); - Assert.AreEqual(1, i); - - // repeat without key - raw.KeyDelete("tran:abc"); - tran = foo.CreateTransaction(); - tran.AddCondition(Condition.KeyExists("abc")); - tran.StringIncrementAsync("i"); - tran.Execute(); - - i = (int)raw.StringGet("tran:i"); - Assert.AreEqual(1, i); - } - } - } -} diff --git a/StackExchange.Redis.Tests/WrapperBaseTests.cs b/StackExchange.Redis.Tests/WrapperBaseTests.cs deleted file mode 100644 index f731a8b5b..000000000 --- a/StackExchange.Redis.Tests/WrapperBaseTests.cs +++ /dev/null @@ -1,893 +0,0 @@ -#if FEATURE_MOQ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Net; -using System.Text; -using Moq; -using NUnit.Framework; -using StackExchange.Redis.KeyspaceIsolation; - -namespace StackExchange.Redis.Tests -{ - [TestFixture] - public sealed class WrapperBaseTests - { - private Mock mock; - private WrapperBase wrapper; - - [OneTimeSetUp] - public void Initialize() - { - mock = new Mock(); - wrapper = new WrapperBase(mock.Object, Encoding.UTF8.GetBytes("prefix:")); - } - - [Test] - public void DebugObjectAsync() - { - wrapper.DebugObjectAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.DebugObjectAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void HashDecrementAsync_1() - { - wrapper.HashDecrementAsync("key", "hashField", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.HashDecrementAsync("prefix:key", "hashField", 123, CommandFlags.HighPriority)); - } - - [Test] - public void HashDecrementAsync_2() - { - wrapper.HashDecrementAsync("key", "hashField", 1.23, CommandFlags.HighPriority); - mock.Verify(_ => _.HashDecrementAsync("prefix:key", "hashField", 1.23, CommandFlags.HighPriority)); - } - - [Test] - public void HashDeleteAsync_1() - { - wrapper.HashDeleteAsync("key", "hashField", CommandFlags.HighPriority); - mock.Verify(_ => _.HashDeleteAsync("prefix:key", "hashField", CommandFlags.HighPriority)); - } - - [Test] - public void HashDeleteAsync_2() - { - RedisValue[] hashFields = new RedisValue[0]; - wrapper.HashDeleteAsync("key", hashFields, CommandFlags.HighPriority); - mock.Verify(_ => _.HashDeleteAsync("prefix:key", hashFields, CommandFlags.HighPriority)); - } - - [Test] - public void HashExistsAsync() - { - wrapper.HashExistsAsync("key", "hashField", CommandFlags.HighPriority); - mock.Verify(_ => _.HashExistsAsync("prefix:key", "hashField", CommandFlags.HighPriority)); - } - - [Test] - public void HashGetAllAsync() - { - wrapper.HashGetAllAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.HashGetAllAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void HashGetAsync_1() - { - wrapper.HashGetAsync("key", "hashField", CommandFlags.HighPriority); - mock.Verify(_ => _.HashGetAsync("prefix:key", "hashField", CommandFlags.HighPriority)); - } - - [Test] - public void HashGetAsync_2() - { - RedisValue[] hashFields = new RedisValue[0]; - wrapper.HashGetAsync("key", hashFields, CommandFlags.HighPriority); - mock.Verify(_ => _.HashGetAsync("prefix:key", hashFields, CommandFlags.HighPriority)); - } - - [Test] - public void HashIncrementAsync_1() - { - wrapper.HashIncrementAsync("key", "hashField", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.HashIncrementAsync("prefix:key", "hashField", 123, CommandFlags.HighPriority)); - } - - [Test] - public void HashIncrementAsync_2() - { - wrapper.HashIncrementAsync("key", "hashField", 1.23, CommandFlags.HighPriority); - mock.Verify(_ => _.HashIncrementAsync("prefix:key", "hashField", 1.23, CommandFlags.HighPriority)); - } - - [Test] - public void HashKeysAsync() - { - wrapper.HashKeysAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.HashKeysAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void HashLengthAsync() - { - wrapper.HashLengthAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.HashLengthAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void HashSetAsync_1() - { - HashEntry[] hashFields = new HashEntry[0]; - wrapper.HashSetAsync("key", hashFields, CommandFlags.HighPriority); - mock.Verify(_ => _.HashSetAsync("prefix:key", hashFields, CommandFlags.HighPriority)); - } - - [Test] - public void HashSetAsync_2() - { - wrapper.HashSetAsync("key", "hashField", "value", When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.HashSetAsync("prefix:key", "hashField", "value", When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void HashValuesAsync() - { - wrapper.HashValuesAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.HashValuesAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void HyperLogLogAddAsync_1() - { - wrapper.HyperLogLogAddAsync("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.HyperLogLogAddAsync("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void HyperLogLogAddAsync_2() - { - RedisValue[] values = new RedisValue[0]; - wrapper.HyperLogLogAddAsync("key", values, CommandFlags.HighPriority); - mock.Verify(_ => _.HyperLogLogAddAsync("prefix:key", values, CommandFlags.HighPriority)); - } - - [Test] - public void HyperLogLogLengthAsync() - { - wrapper.HyperLogLogLengthAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.HyperLogLogLengthAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void HyperLogLogMergeAsync_1() - { - wrapper.HyperLogLogMergeAsync("destination", "first", "second", CommandFlags.HighPriority); - mock.Verify(_ => _.HyperLogLogMergeAsync("prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority)); - } - - [Test] - public void HyperLogLogMergeAsync_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.HyperLogLogMergeAsync("destination", keys, CommandFlags.HighPriority); - mock.Verify(_ => _.HyperLogLogMergeAsync("prefix:destination", It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void IdentifyEndpointAsync() - { - wrapper.IdentifyEndpointAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.IdentifyEndpointAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void IsConnected() - { - wrapper.IsConnected("key", CommandFlags.HighPriority); - mock.Verify(_ => _.IsConnected("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void KeyDeleteAsync_1() - { - wrapper.KeyDeleteAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.KeyDeleteAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void KeyDeleteAsync_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.KeyDeleteAsync(keys, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyDeleteAsync(It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void KeyDumpAsync() - { - wrapper.KeyDumpAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.KeyDumpAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void KeyExistsAsync() - { - wrapper.KeyExistsAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.KeyExistsAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void KeyExpireAsync_1() - { - TimeSpan expiry = TimeSpan.FromSeconds(123); - wrapper.KeyExpireAsync("key", expiry, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyExpireAsync("prefix:key", expiry, CommandFlags.HighPriority)); - } - - [Test] - public void KeyExpireAsync_2() - { - DateTime expiry = DateTime.Now; - wrapper.KeyExpireAsync("key", expiry, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyExpireAsync("prefix:key", expiry, CommandFlags.HighPriority)); - } - - [Test] - public void KeyMigrateAsync() - { - EndPoint toServer = new IPEndPoint(IPAddress.Loopback, 123); - wrapper.KeyMigrateAsync("key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyMigrateAsync("prefix:key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.HighPriority)); - } - - [Test] - public void KeyMoveAsync() - { - wrapper.KeyMoveAsync("key", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyMoveAsync("prefix:key", 123, CommandFlags.HighPriority)); - } - - [Test] - public void KeyPersistAsync() - { - wrapper.KeyPersistAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.KeyPersistAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void KeyRandomAsync() - { - Assert.Throws(() => { - wrapper.KeyRandomAsync(); - }); - } - - [Test] - public void KeyRenameAsync() - { - wrapper.KeyRenameAsync("key", "newKey", When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyRenameAsync("prefix:key", "prefix:newKey", When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void KeyRestoreAsync() - { - Byte[] value = new Byte[0]; - TimeSpan expiry = TimeSpan.FromSeconds(123); - wrapper.KeyRestoreAsync("key", value, expiry, CommandFlags.HighPriority); - mock.Verify(_ => _.KeyRestoreAsync("prefix:key", value, expiry, CommandFlags.HighPriority)); - } - - [Test] - public void KeyTimeToLiveAsync() - { - wrapper.KeyTimeToLiveAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.KeyTimeToLiveAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void KeyTypeAsync() - { - wrapper.KeyTypeAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.KeyTypeAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void ListGetByIndexAsync() - { - wrapper.ListGetByIndexAsync("key", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.ListGetByIndexAsync("prefix:key", 123, CommandFlags.HighPriority)); - } - - [Test] - public void ListInsertAfterAsync() - { - wrapper.ListInsertAfterAsync("key", "pivot", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.ListInsertAfterAsync("prefix:key", "pivot", "value", CommandFlags.HighPriority)); - } - - [Test] - public void ListInsertBeforeAsync() - { - wrapper.ListInsertBeforeAsync("key", "pivot", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.ListInsertBeforeAsync("prefix:key", "pivot", "value", CommandFlags.HighPriority)); - } - - [Test] - public void ListLeftPopAsync() - { - wrapper.ListLeftPopAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.ListLeftPopAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void ListLeftPushAsync_1() - { - wrapper.ListLeftPushAsync("key", "value", When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.ListLeftPushAsync("prefix:key", "value", When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void ListLeftPushAsync_2() - { - RedisValue[] values = new RedisValue[0]; - wrapper.ListLeftPushAsync("key", values, CommandFlags.HighPriority); - mock.Verify(_ => _.ListLeftPushAsync("prefix:key", values, CommandFlags.HighPriority)); - } - - [Test] - public void ListLengthAsync() - { - wrapper.ListLengthAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.ListLengthAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void ListRangeAsync() - { - wrapper.ListRangeAsync("key", 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.ListRangeAsync("prefix:key", 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void ListRemoveAsync() - { - wrapper.ListRemoveAsync("key", "value", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.ListRemoveAsync("prefix:key", "value", 123, CommandFlags.HighPriority)); - } - - [Test] - public void ListRightPopAsync() - { - wrapper.ListRightPopAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.ListRightPopAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void ListRightPopLeftPushAsync() - { - wrapper.ListRightPopLeftPushAsync("source", "destination", CommandFlags.HighPriority); - mock.Verify(_ => _.ListRightPopLeftPushAsync("prefix:source", "prefix:destination", CommandFlags.HighPriority)); - } - - [Test] - public void ListRightPushAsync_1() - { - wrapper.ListRightPushAsync("key", "value", When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.ListRightPushAsync("prefix:key", "value", When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void ListRightPushAsync_2() - { - RedisValue[] values = new RedisValue[0]; - wrapper.ListRightPushAsync("key", values, CommandFlags.HighPriority); - mock.Verify(_ => _.ListRightPushAsync("prefix:key", values, CommandFlags.HighPriority)); - } - - [Test] - public void ListSetByIndexAsync() - { - wrapper.ListSetByIndexAsync("key", 123, "value", CommandFlags.HighPriority); - mock.Verify(_ => _.ListSetByIndexAsync("prefix:key", 123, "value", CommandFlags.HighPriority)); - } - - [Test] - public void ListTrimAsync() - { - wrapper.ListTrimAsync("key", 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.ListTrimAsync("prefix:key", 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void LockExtendAsync() - { - TimeSpan expiry = TimeSpan.FromSeconds(123); - wrapper.LockExtendAsync("key", "value", expiry, CommandFlags.HighPriority); - mock.Verify(_ => _.LockExtendAsync("prefix:key", "value", expiry, CommandFlags.HighPriority)); - } - - [Test] - public void LockQueryAsync() - { - wrapper.LockQueryAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.LockQueryAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void LockReleaseAsync() - { - wrapper.LockReleaseAsync("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.LockReleaseAsync("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void LockTakeAsync() - { - TimeSpan expiry = TimeSpan.FromSeconds(123); - wrapper.LockTakeAsync("key", "value", expiry, CommandFlags.HighPriority); - mock.Verify(_ => _.LockTakeAsync("prefix:key", "value", expiry, CommandFlags.HighPriority)); - } - - [Test] - public void PublishAsync() - { - wrapper.PublishAsync("channel", "message", CommandFlags.HighPriority); - mock.Verify(_ => _.PublishAsync("prefix:channel", "message", CommandFlags.HighPriority)); - } - - [Test] - public void ScriptEvaluateAsync_1() - { - byte[] hash = new byte[0]; - RedisValue[] values = new RedisValue[0]; - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.ScriptEvaluateAsync(hash, keys, values, CommandFlags.HighPriority); - mock.Verify(_ => _.ScriptEvaluateAsync(hash, It.Is(valid), values, CommandFlags.HighPriority)); - } - - [Test] - public void ScriptEvaluateAsync_2() - { - RedisValue[] values = new RedisValue[0]; - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.ScriptEvaluateAsync("script", keys, values, CommandFlags.HighPriority); - mock.Verify(_ => _.ScriptEvaluateAsync("script", It.Is(valid), values, CommandFlags.HighPriority)); - } - - [Test] - public void SetAddAsync_1() - { - wrapper.SetAddAsync("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.SetAddAsync("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void SetAddAsync_2() - { - RedisValue[] values = new RedisValue[0]; - wrapper.SetAddAsync("key", values, CommandFlags.HighPriority); - mock.Verify(_ => _.SetAddAsync("prefix:key", values, CommandFlags.HighPriority)); - } - - [Test] - public void SetCombineAndStoreAsync_1() - { - wrapper.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", "first", "second", CommandFlags.HighPriority); - mock.Verify(_ => _.SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority)); - } - - [Test] - public void SetCombineAndStoreAsync_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", keys, CommandFlags.HighPriority); - mock.Verify(_ => _.SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void SetCombineAsync_1() - { - wrapper.SetCombineAsync(SetOperation.Intersect, "first", "second", CommandFlags.HighPriority); - mock.Verify(_ => _.SetCombineAsync(SetOperation.Intersect, "prefix:first", "prefix:second", CommandFlags.HighPriority)); - } - - [Test] - public void SetCombineAsync_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.SetCombineAsync(SetOperation.Intersect, keys, CommandFlags.HighPriority); - mock.Verify(_ => _.SetCombineAsync(SetOperation.Intersect, It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void SetContainsAsync() - { - wrapper.SetContainsAsync("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.SetContainsAsync("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void SetLengthAsync() - { - wrapper.SetLengthAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.SetLengthAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void SetMembersAsync() - { - wrapper.SetMembersAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.SetMembersAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void SetMoveAsync() - { - wrapper.SetMoveAsync("source", "destination", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.SetMoveAsync("prefix:source", "prefix:destination", "value", CommandFlags.HighPriority)); - } - - [Test] - public void SetPopAsync() - { - wrapper.SetPopAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.SetPopAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void SetRandomMemberAsync() - { - wrapper.SetRandomMemberAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.SetRandomMemberAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void SetRandomMembersAsync() - { - wrapper.SetRandomMembersAsync("key", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.SetRandomMembersAsync("prefix:key", 123, CommandFlags.HighPriority)); - } - - [Test] - public void SetRemoveAsync_1() - { - wrapper.SetRemoveAsync("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.SetRemoveAsync("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void SetRemoveAsync_2() - { - RedisValue[] values = new RedisValue[0]; - wrapper.SetRemoveAsync("key", values, CommandFlags.HighPriority); - mock.Verify(_ => _.SetRemoveAsync("prefix:key", values, CommandFlags.HighPriority)); - } - - [Test] - public void SortAndStoreAsync() - { - RedisValue[] get = new RedisValue[] { "a", "#" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#"; - - wrapper.SortAndStoreAsync("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.HighPriority); - wrapper.SortAndStoreAsync("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.HighPriority); - - mock.Verify(_ => _.SortAndStoreAsync("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", It.Is(valid), CommandFlags.HighPriority)); - mock.Verify(_ => _.SortAndStoreAsync("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void SortAsync() - { - RedisValue[] get = new RedisValue[] { "a", "#" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#"; - - wrapper.SortAsync("key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.HighPriority); - wrapper.SortAsync("key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.HighPriority); - - mock.Verify(_ => _.SortAsync("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", It.Is(valid), CommandFlags.HighPriority)); - mock.Verify(_ => _.SortAsync("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetAddAsync_1() - { - wrapper.SortedSetAddAsync("key", "member", 1.23, When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetAddAsync("prefix:key", "member", 1.23, When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetAddAsync_2() - { - SortedSetEntry[] values = new SortedSetEntry[0]; - wrapper.SortedSetAddAsync("key", values, When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetAddAsync("prefix:key", values, When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetCombineAndStoreAsync_1() - { - wrapper.SortedSetCombineAndStoreAsync(SetOperation.Intersect, "destination", "first", "second", Aggregate.Max, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", Aggregate.Max, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetCombineAndStoreAsync_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", keys, CommandFlags.HighPriority); - mock.Verify(_ => _.SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetDecrementAsync() - { - wrapper.SortedSetDecrementAsync("key", "member", 1.23, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetDecrementAsync("prefix:key", "member", 1.23, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetIncrementAsync() - { - wrapper.SortedSetIncrementAsync("key", "member", 1.23, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetIncrementAsync("prefix:key", "member", 1.23, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetLengthAsync() - { - wrapper.SortedSetLengthAsync("key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetLengthAsync("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetLengthByValueAsync() - { - wrapper.SortedSetLengthByValueAsync("key", "min", "max", Exclude.Start, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetLengthByValueAsync("prefix:key", "min", "max", Exclude.Start, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRangeByRankAsync() - { - wrapper.SortedSetRangeByRankAsync("key", 123, 456, Order.Descending, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRangeByRankAsync("prefix:key", 123, 456, Order.Descending, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRangeByRankWithScoresAsync() - { - wrapper.SortedSetRangeByRankWithScoresAsync("key", 123, 456, Order.Descending, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRangeByRankWithScoresAsync("prefix:key", 123, 456, Order.Descending, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRangeByScoreAsync() - { - wrapper.SortedSetRangeByScoreAsync("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRangeByScoreAsync("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRangeByScoreWithScoresAsync() - { - wrapper.SortedSetRangeByScoreWithScoresAsync("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRangeByScoreWithScoresAsync("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRangeByValueAsync() - { - wrapper.SortedSetRangeByValueAsync("key", "min", "max", Exclude.Start, 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRangeByValueAsync("prefix:key", "min", "max", Exclude.Start, 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRankAsync() - { - wrapper.SortedSetRankAsync("key", "member", Order.Descending, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRankAsync("prefix:key", "member", Order.Descending, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRemoveAsync_1() - { - wrapper.SortedSetRemoveAsync("key", "member", CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRemoveAsync("prefix:key", "member", CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRemoveAsync_2() - { - RedisValue[] members = new RedisValue[0]; - wrapper.SortedSetRemoveAsync("key", members, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRemoveAsync("prefix:key", members, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRemoveRangeByRankAsync() - { - wrapper.SortedSetRemoveRangeByRankAsync("key", 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRemoveRangeByRankAsync("prefix:key", 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRemoveRangeByScoreAsync() - { - wrapper.SortedSetRemoveRangeByScoreAsync("key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRemoveRangeByScoreAsync("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetRemoveRangeByValueAsync() - { - wrapper.SortedSetRemoveRangeByValueAsync("key", "min", "max", Exclude.Start, CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetRemoveRangeByValueAsync("prefix:key", "min", "max", Exclude.Start, CommandFlags.HighPriority)); - } - - [Test] - public void SortedSetScoreAsync() - { - wrapper.SortedSetScoreAsync("key", "member", CommandFlags.HighPriority); - mock.Verify(_ => _.SortedSetScoreAsync("prefix:key", "member", CommandFlags.HighPriority)); - } - - [Test] - public void StringAppendAsync() - { - wrapper.StringAppendAsync("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.StringAppendAsync("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void StringBitCountAsync() - { - wrapper.StringBitCountAsync("key", 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.StringBitCountAsync("prefix:key", 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void StringBitOperationAsync_1() - { - wrapper.StringBitOperationAsync(Bitwise.Xor, "destination", "first", "second", CommandFlags.HighPriority); - mock.Verify(_ => _.StringBitOperationAsync(Bitwise.Xor, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority)); - } - - [Test] - public void StringBitOperationAsync_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.StringBitOperationAsync(Bitwise.Xor, "destination", keys, CommandFlags.HighPriority); - mock.Verify(_ => _.StringBitOperationAsync(Bitwise.Xor, "prefix:destination", It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void StringBitPositionAsync() - { - wrapper.StringBitPositionAsync("key", true, 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.StringBitPositionAsync("prefix:key", true, 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void StringDecrementAsync_1() - { - wrapper.StringDecrementAsync("key", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.StringDecrementAsync("prefix:key", 123, CommandFlags.HighPriority)); - } - - [Test] - public void StringDecrementAsync_2() - { - wrapper.StringDecrementAsync("key", 1.23, CommandFlags.HighPriority); - mock.Verify(_ => _.StringDecrementAsync("prefix:key", 1.23, CommandFlags.HighPriority)); - } - - [Test] - public void StringGetAsync_1() - { - wrapper.StringGetAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.StringGetAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void StringGetAsync_2() - { - RedisKey[] keys = new RedisKey[] { "a", "b" }; - Expression> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; - wrapper.StringGetAsync(keys, CommandFlags.HighPriority); - mock.Verify(_ => _.StringGetAsync(It.Is(valid), CommandFlags.HighPriority)); - } - - [Test] - public void StringGetBitAsync() - { - wrapper.StringGetBitAsync("key", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.StringGetBitAsync("prefix:key", 123, CommandFlags.HighPriority)); - } - - [Test] - public void StringGetRangeAsync() - { - wrapper.StringGetRangeAsync("key", 123, 456, CommandFlags.HighPriority); - mock.Verify(_ => _.StringGetRangeAsync("prefix:key", 123, 456, CommandFlags.HighPriority)); - } - - [Test] - public void StringGetSetAsync() - { - wrapper.StringGetSetAsync("key", "value", CommandFlags.HighPriority); - mock.Verify(_ => _.StringGetSetAsync("prefix:key", "value", CommandFlags.HighPriority)); - } - - [Test] - public void StringGetWithExpiryAsync() - { - wrapper.StringGetWithExpiryAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.StringGetWithExpiryAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void StringIncrementAsync_1() - { - wrapper.StringIncrementAsync("key", 123, CommandFlags.HighPriority); - mock.Verify(_ => _.StringIncrementAsync("prefix:key", 123, CommandFlags.HighPriority)); - } - - [Test] - public void StringIncrementAsync_2() - { - wrapper.StringIncrementAsync("key", 1.23, CommandFlags.HighPriority); - mock.Verify(_ => _.StringIncrementAsync("prefix:key", 1.23, CommandFlags.HighPriority)); - } - - [Test] - public void StringLengthAsync() - { - wrapper.StringLengthAsync("key", CommandFlags.HighPriority); - mock.Verify(_ => _.StringLengthAsync("prefix:key", CommandFlags.HighPriority)); - } - - [Test] - public void StringSetAsync_1() - { - TimeSpan expiry = TimeSpan.FromSeconds(123); - wrapper.StringSetAsync("key", "value", expiry, When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.StringSetAsync("prefix:key", "value", expiry, When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void StringSetAsync_2() - { - KeyValuePair[] values = new KeyValuePair[] { new KeyValuePair("a", "x"), new KeyValuePair("b", "y") }; - Expression[], bool>> valid = _ => _.Length == 2 && _[0].Key == "prefix:a" && _[0].Value == "x" && _[1].Key == "prefix:b" && _[1].Value == "y"; - wrapper.StringSetAsync(values, When.Exists, CommandFlags.HighPriority); - mock.Verify(_ => _.StringSetAsync(It.Is(valid), When.Exists, CommandFlags.HighPriority)); - } - - [Test] - public void StringSetBitAsync() - { - wrapper.StringSetBitAsync("key", 123, true, CommandFlags.HighPriority); - mock.Verify(_ => _.StringSetBitAsync("prefix:key", 123, true, CommandFlags.HighPriority)); - } - - [Test] - public void StringSetRangeAsync() - { - wrapper.StringSetRangeAsync("key", 123, "value", CommandFlags.HighPriority); - mock.Verify(_ => _.StringSetRangeAsync("prefix:key", 123, "value", CommandFlags.HighPriority)); - } - } -} -#endif diff --git a/StackExchange.Redis.Tests/packages.config b/StackExchange.Redis.Tests/packages.config deleted file mode 100644 index d9c207c87..000000000 --- a/StackExchange.Redis.Tests/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/StackExchange.Redis.sln b/StackExchange.Redis.sln index 440aab09e..adb1291de 100644 --- a/StackExchange.Redis.sln +++ b/StackExchange.Redis.sln @@ -1,129 +1,219 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26403.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31808.319 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3AD17044-6BFF-4750-9AC2-2CA466375F2A}" ProjectSection(SolutionItems) = preProject - Directory.build.props = Directory.build.props + .editorconfig = .editorconfig + appveyor.yml = appveyor.yml + build.cmd = build.cmd + Build.csproj = Build.csproj + build.ps1 = build.ps1 + .github\workflows\CI.yml = .github\workflows\CI.yml + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + Directory.Packages.props = Directory.Packages.props + tests\RedisConfigs\docker-compose.yml = tests\RedisConfigs\docker-compose.yml + global.json = global.json NuGet.Config = NuGet.Config + README.md = README.md + docs\ReleaseNotes.md = docs\ReleaseNotes.md + Shared.ruleset = Shared.ruleset + version.json = version.json + tests\RedisConfigs\.docker\Redis\Dockerfile = tests\RedisConfigs\.docker\Redis\Dockerfile EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Redis Configs", "Redis Configs", "{96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RedisConfigs", "RedisConfigs", "{96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05}" ProjectSection(SolutionItems) = preProject - Redis Configs\master.conf = Redis Configs\master.conf - Redis Configs\redis-cli 7000.cmd = Redis Configs\redis-cli 7000.cmd - Redis Configs\redis-cli 7001.cmd = Redis Configs\redis-cli 7001.cmd - Redis Configs\redis-cli 7002.cmd = Redis Configs\redis-cli 7002.cmd - Redis Configs\redis-cli 7003.cmd = Redis Configs\redis-cli 7003.cmd - Redis Configs\redis-cli 7004.cmd = Redis Configs\redis-cli 7004.cmd - Redis Configs\redis-cli 7005.cmd = Redis Configs\redis-cli 7005.cmd - Redis Configs\redis-cli master.cmd = Redis Configs\redis-cli master.cmd - Redis Configs\redis-cli secure.cmd = Redis Configs\redis-cli secure.cmd - Redis Configs\redis-cli slave.cmd = Redis Configs\redis-cli slave.cmd - Redis Configs\redis-server all local.cmd = Redis Configs\redis-server all local.cmd - Redis Configs\redis-server master.cmd = Redis Configs\redis-server master.cmd - Redis Configs\redis-server secure.cmd = Redis Configs\redis-server secure.cmd - Redis Configs\redis-server slave.cmd = Redis Configs\redis-server slave.cmd - Redis Configs\secure.conf = Redis Configs\secure.conf - Redis Configs\slave.conf = Redis Configs\slave.conf + tests\RedisConfigs\cli-master.cmd = tests\RedisConfigs\cli-master.cmd + tests\RedisConfigs\cli-secure.cmd = tests\RedisConfigs\cli-secure.cmd + tests\RedisConfigs\cli-slave.cmd = tests\RedisConfigs\cli-slave.cmd + tests\RedisConfigs\docker-compose.yml = tests\RedisConfigs\docker-compose.yml + tests\RedisConfigs\Dockerfile = tests\RedisConfigs\Dockerfile + tests\RedisConfigs\start-all.cmd = tests\RedisConfigs\start-all.cmd + tests\RedisConfigs\start-all.sh = tests\RedisConfigs\start-all.sh + tests\RedisConfigs\start-basic.cmd = tests\RedisConfigs\start-basic.cmd + tests\RedisConfigs\start-basic.sh = tests\RedisConfigs\start-basic.sh + tests\RedisConfigs\start-cluster.cmd = tests\RedisConfigs\start-cluster.cmd + tests\RedisConfigs\start-sentinel.cmd = tests\RedisConfigs\start-sentinel.cmd + tests\RedisConfigs\wsl2.md = tests\RedisConfigs\wsl2.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackExchange.Redis", "StackExchange.Redis\StackExchange.Redis.csproj", "{EF84877F-59BE-41BE-9013-E765AF0BB72E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackExchange.Redis", "src\StackExchange.Redis\StackExchange.Redis.csproj", "{EF84877F-59BE-41BE-9013-E765AF0BB72E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackExchange.Redis.StrongName", "StackExchange.Redis.StrongName\StackExchange.Redis.StrongName.csproj", "{46754D2A-AC16-4686-B113-3DB08ACF4269}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackExchange.Redis.Tests", "tests\StackExchange.Redis.Tests\StackExchange.Redis.Tests.csproj", "{3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackExchange.Redis.Tests", "StackExchange.Redis.Tests\StackExchange.Redis.Tests.csproj", "{3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicTest", "tests\BasicTest\BasicTest.csproj", "{939FA5F7-16AA-4847-812B-6EBC3748A86D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicTest", "BasicTest\BasicTest.csproj", "{939FA5F7-16AA-4847-812B-6EBC3748A86D}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sentinel", "Sentinel", "{36255A0A-89EC-43C8-A642-F4C1ACAEF5BC}" + ProjectSection(SolutionItems) = preProject + tests\RedisConfigs\Sentinel\redis-7010.conf = tests\RedisConfigs\Sentinel\redis-7010.conf + tests\RedisConfigs\Sentinel\redis-7011.conf = tests\RedisConfigs\Sentinel\redis-7011.conf + tests\RedisConfigs\Sentinel\sentinel-26379.conf = tests\RedisConfigs\Sentinel\sentinel-26379.conf + tests\RedisConfigs\Sentinel\sentinel-26380.conf = tests\RedisConfigs\Sentinel\sentinel-26380.conf + tests\RedisConfigs\Sentinel\sentinel-26381.conf = tests\RedisConfigs\Sentinel\sentinel-26381.conf + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cluster", "Cluster", "{A3B4B972-5BD2-4D90-981F-7E51E350E628}" + ProjectSection(SolutionItems) = preProject + tests\RedisConfigs\Cluster\cluster-7000.conf = tests\RedisConfigs\Cluster\cluster-7000.conf + tests\RedisConfigs\Cluster\cluster-7001.conf = tests\RedisConfigs\Cluster\cluster-7001.conf + tests\RedisConfigs\Cluster\cluster-7002.conf = tests\RedisConfigs\Cluster\cluster-7002.conf + tests\RedisConfigs\Cluster\cluster-7003.conf = tests\RedisConfigs\Cluster\cluster-7003.conf + tests\RedisConfigs\Cluster\cluster-7004.conf = tests\RedisConfigs\Cluster\cluster-7004.conf + tests\RedisConfigs\Cluster\cluster-7005.conf = tests\RedisConfigs\Cluster\cluster-7005.conf + tests\RedisConfigs\Cluster\nodes-7000.conf = tests\RedisConfigs\Cluster\nodes-7000.conf + tests\RedisConfigs\Cluster\nodes-7001.conf = tests\RedisConfigs\Cluster\nodes-7001.conf + tests\RedisConfigs\Cluster\nodes-7002.conf = tests\RedisConfigs\Cluster\nodes-7002.conf + tests\RedisConfigs\Cluster\nodes-7003.conf = tests\RedisConfigs\Cluster\nodes-7003.conf + tests\RedisConfigs\Cluster\nodes-7004.conf = tests\RedisConfigs\Cluster\nodes-7004.conf + tests\RedisConfigs\Cluster\nodes-7005.conf = tests\RedisConfigs\Cluster\nodes-7005.conf + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Basic", "Basic", "{38BDEEED-7BEB-4B1F-9CE0-256D63F9C502}" + ProjectSection(SolutionItems) = preProject + tests\RedisConfigs\Basic\primary-6379.conf = tests\RedisConfigs\Basic\primary-6379.conf + tests\RedisConfigs\Basic\replica-6380.conf = tests\RedisConfigs\Basic\replica-6380.conf + tests\RedisConfigs\Basic\secure-6381.conf = tests\RedisConfigs\Basic\secure-6381.conf + tests\RedisConfigs\Basic\tls-ciphers-6384.conf = tests\RedisConfigs\Basic\tls-ciphers-6384.conf + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicTestBaseline", "tests\BasicTestBaseline\BasicTestBaseline.csproj", "{8FDB623D-779B-4A84-BC6B-75106E41D8A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestConsole", "toys\TestConsole\TestConsole.csproj", "{651FDB97-9DE3-4BD9-9A05-827AF8F1A94A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Failover", "Failover", "{D082703F-1652-4C35-840D-7D377F6B9979}" + ProjectSection(SolutionItems) = preProject + tests\RedisConfigs\Failover\primary-6382.conf = tests\RedisConfigs\Failover\primary-6382.conf + tests\RedisConfigs\Failover\replica-6383.conf = tests\RedisConfigs\Failover\replica-6383.conf + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackExchange.Redis.Server", "toys\StackExchange.Redis.Server\StackExchange.Redis.Server.csproj", "{8375813E-FBAF-4DA3-A2C7-E4645B39B931}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KestrelRedisServer", "toys\KestrelRedisServer\KestrelRedisServer.csproj", "{3DA1EEED-E9FE-43D9-B293-E000CFCCD91A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{73A5C363-CA1F-44C4-9A9B-EF791A76BA6A}" + ProjectSection(SolutionItems) = preProject + tests\.editorconfig = tests\.editorconfig + tests\Directory.Build.targets = tests\Directory.Build.targets + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{00CA0876-DA9F-44E8-B0DC-A88716BF347A}" + ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "toys", "toys", "{E25031D3-5C64-430D-B86F-697B66816FD8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestConsoleBaseline", "toys\TestConsoleBaseline\TestConsoleBaseline.csproj", "{D58114AE-4998-4647-AFCA-9353D20495AE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = ".github", ".github\.github.csproj", "{8FB98E7D-DAE2-4465-BD9A-104000E0A2D4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MigratedBookSleeveTestSuite", "MigratedBookSleeveTestSuite\MigratedBookSleeveTestSuite.csproj", "{0FA2F7C5-1D36-40C4-82D1-93DBF43765D7}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docker", "Docker", "{A9F81DA3-DA82-423E-A5DD-B11C37548E06}" + ProjectSection(SolutionItems) = preProject + tests\RedisConfigs\Docker\docker-entrypoint.sh = tests\RedisConfigs\Docker\docker-entrypoint.sh + tests\RedisConfigs\Docker\supervisord.conf = tests\RedisConfigs\Docker\supervisord.conf + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleTest", "tests\ConsoleTest\ConsoleTest.csproj", "{A0F89B8B-32A3-4C28-8F1B-ADE343F16137}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleTestBaseline", "tests\ConsoleTestBaseline\ConsoleTestBaseline.csproj", "{69A0ACF2-DF1F-4F49-B554-F732DCA938A3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NRediSearch", "NRediSearch\NRediSearch.csproj", "{71455B07-E628-4F3A-9FFF-9EC63071F78E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "docs", "docs\docs.csproj", "{1DC43E76-5372-4C7F-A433-0602273E87FC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NRediSearch.Test", "NRediSearch.Test\NRediSearch.Test.csproj", "{94D233F5-2400-4542-98B9-BA72005C57DC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis.Benchmarks", "tests\StackExchange.Redis.Benchmarks\StackExchange.Redis.Benchmarks.csproj", "{59889284-FFEE-82E7-94CB-3B43E87DA6CF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eng", "eng", "{5FA0958E-6EBD-45F4-808E-3447A293F96F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis.Build", "eng\StackExchange.Redis.Build\StackExchange.Redis.Build.csproj", "{190742E1-FA50-4E36-A8C4-88AE87654340}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Log Output|Any CPU = Log Output|Any CPU - Mono|Any CPU = Mono|Any CPU Release|Any CPU = Release|Any CPU - Verbose|Any CPU = Verbose|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {EF84877F-59BE-41BE-9013-E765AF0BB72E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EF84877F-59BE-41BE-9013-E765AF0BB72E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EF84877F-59BE-41BE-9013-E765AF0BB72E}.Log Output|Any CPU.ActiveCfg = Release|Any CPU - {EF84877F-59BE-41BE-9013-E765AF0BB72E}.Log Output|Any CPU.Build.0 = Release|Any CPU - {EF84877F-59BE-41BE-9013-E765AF0BB72E}.Mono|Any CPU.ActiveCfg = Release|Any CPU - {EF84877F-59BE-41BE-9013-E765AF0BB72E}.Mono|Any CPU.Build.0 = Release|Any CPU {EF84877F-59BE-41BE-9013-E765AF0BB72E}.Release|Any CPU.ActiveCfg = Release|Any CPU {EF84877F-59BE-41BE-9013-E765AF0BB72E}.Release|Any CPU.Build.0 = Release|Any CPU - {EF84877F-59BE-41BE-9013-E765AF0BB72E}.Verbose|Any CPU.ActiveCfg = Release|Any CPU - {EF84877F-59BE-41BE-9013-E765AF0BB72E}.Verbose|Any CPU.Build.0 = Release|Any CPU - {46754D2A-AC16-4686-B113-3DB08ACF4269}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {46754D2A-AC16-4686-B113-3DB08ACF4269}.Debug|Any CPU.Build.0 = Debug|Any CPU - {46754D2A-AC16-4686-B113-3DB08ACF4269}.Log Output|Any CPU.ActiveCfg = Release|Any CPU - {46754D2A-AC16-4686-B113-3DB08ACF4269}.Log Output|Any CPU.Build.0 = Release|Any CPU - {46754D2A-AC16-4686-B113-3DB08ACF4269}.Mono|Any CPU.ActiveCfg = Release|Any CPU - {46754D2A-AC16-4686-B113-3DB08ACF4269}.Mono|Any CPU.Build.0 = Release|Any CPU - {46754D2A-AC16-4686-B113-3DB08ACF4269}.Release|Any CPU.ActiveCfg = Release|Any CPU - {46754D2A-AC16-4686-B113-3DB08ACF4269}.Release|Any CPU.Build.0 = Release|Any CPU - {46754D2A-AC16-4686-B113-3DB08ACF4269}.Verbose|Any CPU.ActiveCfg = Release|Any CPU - {46754D2A-AC16-4686-B113-3DB08ACF4269}.Verbose|Any CPU.Build.0 = Release|Any CPU {3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}.Log Output|Any CPU.ActiveCfg = Release|Any CPU - {3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}.Log Output|Any CPU.Build.0 = Release|Any CPU - {3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}.Mono|Any CPU.ActiveCfg = Release|Any CPU - {3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}.Mono|Any CPU.Build.0 = Release|Any CPU {3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}.Release|Any CPU.ActiveCfg = Release|Any CPU {3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}.Release|Any CPU.Build.0 = Release|Any CPU - {3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}.Verbose|Any CPU.ActiveCfg = Release|Any CPU - {3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}.Verbose|Any CPU.Build.0 = Release|Any CPU {939FA5F7-16AA-4847-812B-6EBC3748A86D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {939FA5F7-16AA-4847-812B-6EBC3748A86D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {939FA5F7-16AA-4847-812B-6EBC3748A86D}.Log Output|Any CPU.ActiveCfg = Debug|Any CPU - {939FA5F7-16AA-4847-812B-6EBC3748A86D}.Log Output|Any CPU.Build.0 = Debug|Any CPU - {939FA5F7-16AA-4847-812B-6EBC3748A86D}.Mono|Any CPU.ActiveCfg = Debug|Any CPU - {939FA5F7-16AA-4847-812B-6EBC3748A86D}.Mono|Any CPU.Build.0 = Debug|Any CPU {939FA5F7-16AA-4847-812B-6EBC3748A86D}.Release|Any CPU.ActiveCfg = Release|Any CPU {939FA5F7-16AA-4847-812B-6EBC3748A86D}.Release|Any CPU.Build.0 = Release|Any CPU - {939FA5F7-16AA-4847-812B-6EBC3748A86D}.Verbose|Any CPU.ActiveCfg = Debug|Any CPU - {939FA5F7-16AA-4847-812B-6EBC3748A86D}.Verbose|Any CPU.Build.0 = Debug|Any CPU - {0FA2F7C5-1D36-40C4-82D1-93DBF43765D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0FA2F7C5-1D36-40C4-82D1-93DBF43765D7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0FA2F7C5-1D36-40C4-82D1-93DBF43765D7}.Log Output|Any CPU.ActiveCfg = Debug|Any CPU - {0FA2F7C5-1D36-40C4-82D1-93DBF43765D7}.Log Output|Any CPU.Build.0 = Debug|Any CPU - {0FA2F7C5-1D36-40C4-82D1-93DBF43765D7}.Mono|Any CPU.ActiveCfg = Debug|Any CPU - {0FA2F7C5-1D36-40C4-82D1-93DBF43765D7}.Mono|Any CPU.Build.0 = Debug|Any CPU - {0FA2F7C5-1D36-40C4-82D1-93DBF43765D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0FA2F7C5-1D36-40C4-82D1-93DBF43765D7}.Release|Any CPU.Build.0 = Release|Any CPU - {0FA2F7C5-1D36-40C4-82D1-93DBF43765D7}.Verbose|Any CPU.ActiveCfg = Debug|Any CPU - {0FA2F7C5-1D36-40C4-82D1-93DBF43765D7}.Verbose|Any CPU.Build.0 = Debug|Any CPU - {71455B07-E628-4F3A-9FFF-9EC63071F78E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {71455B07-E628-4F3A-9FFF-9EC63071F78E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {71455B07-E628-4F3A-9FFF-9EC63071F78E}.Log Output|Any CPU.ActiveCfg = Release|Any CPU - {71455B07-E628-4F3A-9FFF-9EC63071F78E}.Log Output|Any CPU.Build.0 = Release|Any CPU - {71455B07-E628-4F3A-9FFF-9EC63071F78E}.Mono|Any CPU.ActiveCfg = Release|Any CPU - {71455B07-E628-4F3A-9FFF-9EC63071F78E}.Mono|Any CPU.Build.0 = Release|Any CPU - {71455B07-E628-4F3A-9FFF-9EC63071F78E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {71455B07-E628-4F3A-9FFF-9EC63071F78E}.Release|Any CPU.Build.0 = Release|Any CPU - {71455B07-E628-4F3A-9FFF-9EC63071F78E}.Verbose|Any CPU.ActiveCfg = Release|Any CPU - {71455B07-E628-4F3A-9FFF-9EC63071F78E}.Verbose|Any CPU.Build.0 = Release|Any CPU - {94D233F5-2400-4542-98B9-BA72005C57DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94D233F5-2400-4542-98B9-BA72005C57DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {94D233F5-2400-4542-98B9-BA72005C57DC}.Log Output|Any CPU.ActiveCfg = Debug|Any CPU - {94D233F5-2400-4542-98B9-BA72005C57DC}.Log Output|Any CPU.Build.0 = Debug|Any CPU - {94D233F5-2400-4542-98B9-BA72005C57DC}.Mono|Any CPU.ActiveCfg = Debug|Any CPU - {94D233F5-2400-4542-98B9-BA72005C57DC}.Mono|Any CPU.Build.0 = Debug|Any CPU - {94D233F5-2400-4542-98B9-BA72005C57DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {94D233F5-2400-4542-98B9-BA72005C57DC}.Release|Any CPU.Build.0 = Release|Any CPU - {94D233F5-2400-4542-98B9-BA72005C57DC}.Verbose|Any CPU.ActiveCfg = Debug|Any CPU - {94D233F5-2400-4542-98B9-BA72005C57DC}.Verbose|Any CPU.Build.0 = Debug|Any CPU + {8FDB623D-779B-4A84-BC6B-75106E41D8A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FDB623D-779B-4A84-BC6B-75106E41D8A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FDB623D-779B-4A84-BC6B-75106E41D8A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FDB623D-779B-4A84-BC6B-75106E41D8A4}.Release|Any CPU.Build.0 = Release|Any CPU + {651FDB97-9DE3-4BD9-9A05-827AF8F1A94A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {651FDB97-9DE3-4BD9-9A05-827AF8F1A94A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {651FDB97-9DE3-4BD9-9A05-827AF8F1A94A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {651FDB97-9DE3-4BD9-9A05-827AF8F1A94A}.Release|Any CPU.Build.0 = Release|Any CPU + {8375813E-FBAF-4DA3-A2C7-E4645B39B931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8375813E-FBAF-4DA3-A2C7-E4645B39B931}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8375813E-FBAF-4DA3-A2C7-E4645B39B931}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8375813E-FBAF-4DA3-A2C7-E4645B39B931}.Release|Any CPU.Build.0 = Release|Any CPU + {3DA1EEED-E9FE-43D9-B293-E000CFCCD91A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DA1EEED-E9FE-43D9-B293-E000CFCCD91A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DA1EEED-E9FE-43D9-B293-E000CFCCD91A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DA1EEED-E9FE-43D9-B293-E000CFCCD91A}.Release|Any CPU.Build.0 = Release|Any CPU + {D58114AE-4998-4647-AFCA-9353D20495AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D58114AE-4998-4647-AFCA-9353D20495AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D58114AE-4998-4647-AFCA-9353D20495AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D58114AE-4998-4647-AFCA-9353D20495AE}.Release|Any CPU.Build.0 = Release|Any CPU + {8FB98E7D-DAE2-4465-BD9A-104000E0A2D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FB98E7D-DAE2-4465-BD9A-104000E0A2D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FB98E7D-DAE2-4465-BD9A-104000E0A2D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FB98E7D-DAE2-4465-BD9A-104000E0A2D4}.Release|Any CPU.Build.0 = Release|Any CPU + {A0F89B8B-32A3-4C28-8F1B-ADE343F16137}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0F89B8B-32A3-4C28-8F1B-ADE343F16137}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0F89B8B-32A3-4C28-8F1B-ADE343F16137}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0F89B8B-32A3-4C28-8F1B-ADE343F16137}.Release|Any CPU.Build.0 = Release|Any CPU + {69A0ACF2-DF1F-4F49-B554-F732DCA938A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69A0ACF2-DF1F-4F49-B554-F732DCA938A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69A0ACF2-DF1F-4F49-B554-F732DCA938A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69A0ACF2-DF1F-4F49-B554-F732DCA938A3}.Release|Any CPU.Build.0 = Release|Any CPU + {1DC43E76-5372-4C7F-A433-0602273E87FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DC43E76-5372-4C7F-A433-0602273E87FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DC43E76-5372-4C7F-A433-0602273E87FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DC43E76-5372-4C7F-A433-0602273E87FC}.Release|Any CPU.Build.0 = Release|Any CPU + {59889284-FFEE-82E7-94CB-3B43E87DA6CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59889284-FFEE-82E7-94CB-3B43E87DA6CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59889284-FFEE-82E7-94CB-3B43E87DA6CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59889284-FFEE-82E7-94CB-3B43E87DA6CF}.Release|Any CPU.Build.0 = Release|Any CPU + {190742E1-FA50-4E36-A8C4-88AE87654340}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {190742E1-FA50-4E36-A8C4-88AE87654340}.Debug|Any CPU.Build.0 = Debug|Any CPU + {190742E1-FA50-4E36-A8C4-88AE87654340}.Release|Any CPU.ActiveCfg = Release|Any CPU + {190742E1-FA50-4E36-A8C4-88AE87654340}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} + {EF84877F-59BE-41BE-9013-E765AF0BB72E} = {00CA0876-DA9F-44E8-B0DC-A88716BF347A} + {3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} + {939FA5F7-16AA-4847-812B-6EBC3748A86D} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} + {36255A0A-89EC-43C8-A642-F4C1ACAEF5BC} = {96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05} + {A3B4B972-5BD2-4D90-981F-7E51E350E628} = {96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05} + {38BDEEED-7BEB-4B1F-9CE0-256D63F9C502} = {96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05} + {8FDB623D-779B-4A84-BC6B-75106E41D8A4} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} + {651FDB97-9DE3-4BD9-9A05-827AF8F1A94A} = {E25031D3-5C64-430D-B86F-697B66816FD8} + {D082703F-1652-4C35-840D-7D377F6B9979} = {96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05} + {8375813E-FBAF-4DA3-A2C7-E4645B39B931} = {E25031D3-5C64-430D-B86F-697B66816FD8} + {3DA1EEED-E9FE-43D9-B293-E000CFCCD91A} = {E25031D3-5C64-430D-B86F-697B66816FD8} + {D58114AE-4998-4647-AFCA-9353D20495AE} = {E25031D3-5C64-430D-B86F-697B66816FD8} + {A9F81DA3-DA82-423E-A5DD-B11C37548E06} = {96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05} + {A0F89B8B-32A3-4C28-8F1B-ADE343F16137} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} + {69A0ACF2-DF1F-4F49-B554-F732DCA938A3} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} + {59889284-FFEE-82E7-94CB-3B43E87DA6CF} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} + {190742E1-FA50-4E36-A8C4-88AE87654340} = {5FA0958E-6EBD-45F4-808E-3447A293F96F} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {193AA352-6748-47C1-A5FC-C9AA6B5F000B} + EndGlobalSection EndGlobal diff --git a/StackExchange.Redis.sln.DotSettings b/StackExchange.Redis.sln.DotSettings index 165f8337f..b72a49d2c 100644 --- a/StackExchange.Redis.sln.DotSettings +++ b/StackExchange.Redis.sln.DotSettings @@ -1,3 +1,5 @@  OK - PONG \ No newline at end of file + PONG + True + True \ No newline at end of file diff --git a/StackExchange.Redis/Properties/AssemblyInfo.cs b/StackExchange.Redis/Properties/AssemblyInfo.cs deleted file mode 100644 index aeae19e01..000000000 --- a/StackExchange.Redis/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("StackExchange.Redis")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("StackExchange.Redis")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f411ad3e-53ca-4104-9c63-b1af9657ca1c")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersion("1.0.0.0")] - -[assembly: CLSCompliant(true)] \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange.Redis.csproj b/StackExchange.Redis/StackExchange.Redis.csproj deleted file mode 100644 index d64d19653..000000000 --- a/StackExchange.Redis/StackExchange.Redis.csproj +++ /dev/null @@ -1,70 +0,0 @@ - - - - $(LibraryTargetFrameworks) - High performance Redis client, incorporating both synchronous and asynchronous usage. - StackExchange.Redis - true - StackExchange.Redis - StackExchange.Redis - Async;Redis;Cache;PubSub;Messaging - Library - - - - - - - - - - - - - - - - - - - - - - - - - - - $(DefineConstants);FEATURE_SERIALIZATION;FEATURE_SOCKET_MODE_POLL - - - - $(DefineConstants);CORE_CLR - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/StackExchange.Redis/StackExchange/AssemblyInfoHack.cs b/StackExchange.Redis/StackExchange/AssemblyInfoHack.cs deleted file mode 100644 index 07307cfb9..000000000 --- a/StackExchange.Redis/StackExchange/AssemblyInfoHack.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Yes, this is embarassing. However, in .NET Core the including AssemblyInfo (ifdef'd or not) will screw with -// your version numbers. Therefore, we need to move the attribute out into another file...this file. -// When .csproj merges in, this should be able to return to Properties/AssemblyInfo.cs -#if !STRONG_NAME -using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("StackExchange.Redis.Tests")] -#endif \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/Aggregate.cs b/StackExchange.Redis/StackExchange/Redis/Aggregate.cs deleted file mode 100644 index 662eca989..000000000 --- a/StackExchange.Redis/StackExchange/Redis/Aggregate.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace StackExchange.Redis -{ - /// - /// Specifies how elements should be aggregated when combining sorted sets - /// - public enum Aggregate - { - /// - /// The values of the combined elements are added - /// - Sum, - /// - /// The least value of the combined elements is used - /// - Min, - /// - /// The greatest value of the combined elements is used - /// - Max - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/Bitwise.cs b/StackExchange.Redis/StackExchange/Redis/Bitwise.cs deleted file mode 100644 index e09f26b04..000000000 --- a/StackExchange.Redis/StackExchange/Redis/Bitwise.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace StackExchange.Redis -{ - /// - /// Bitwise operators - /// - public enum Bitwise - { - /// - /// And - /// - And, - /// - /// Or - /// - Or, - /// - /// Xor - /// - Xor, - /// - /// Not - /// - Not - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ClientFlags.cs b/StackExchange.Redis/StackExchange/Redis/ClientFlags.cs deleted file mode 100644 index b99a00715..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ClientFlags.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; - -namespace StackExchange.Redis -{ - /// - /// The client flags can be a combination of: - /// O: the client is a slave in MONITOR mode - /// S: the client is a normal slave server - /// M: the client is a master - /// x: the client is in a MULTI/EXEC context - /// b: the client is waiting in a blocking operation - /// i: the client is waiting for a VM I/O (deprecated) - /// d: a watched keys has been modified - EXEC will fail - /// c: connection to be closed after writing entire reply - /// u: the client is unblocked - /// A: connection to be closed ASAP - /// N: no specific flag set - /// - [Flags] - public enum ClientFlags : long - { - /// - /// no specific flag set - /// - None = 0, - /// - /// the client is a slave in MONITOR mode - /// - SlaveMonitor = 1, - /// - /// the client is a normal slave server - /// - Slave = 2, - /// - /// the client is a master - /// - Master = 4, - /// - /// the client is in a MULTI/EXEC context - /// - Transaction = 8, - /// - /// the client is waiting in a blocking operation - /// - Blocked = 16, - /// - /// a watched keys has been modified - EXEC will fail - /// - TransactionDoomed = 32, - /// - /// connection to be closed after writing entire reply - /// - Closing = 64, - /// - /// the client is unblocked - /// - Unblocked = 128, - /// - /// connection to be closed ASAP - /// - CloseASAP = 256, - - } - - -} diff --git a/StackExchange.Redis/StackExchange/Redis/ClientInfo.cs b/StackExchange.Redis/StackExchange/Redis/ClientInfo.cs deleted file mode 100644 index 6596fcf53..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ClientInfo.cs +++ /dev/null @@ -1,214 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Net; - -namespace StackExchange.Redis -{ - /// - /// Represents the state of an individual client connection to redis - /// - public sealed class ClientInfo - { - internal static readonly ResultProcessor Processor = new ClientInfoProcessor(); - - /// - /// Address (host and port) of the client - /// - public EndPoint Address { get; private set; } - - /// - /// total duration of the connection in seconds - /// - public int AgeSeconds { get; private set; } - - /// - /// current database ID - /// - public int Database { get; private set; } - - /// - /// The flags associated with this connection - /// - public ClientFlags Flags { get; private set; } - - /// - /// The client flags can be a combination of: - /// O: the client is a slave in MONITOR mode - /// S: the client is a normal slave server - /// M: the client is a master - /// x: the client is in a MULTI/EXEC context - /// b: the client is waiting in a blocking operation - /// i: the client is waiting for a VM I/O (deprecated) - /// d: a watched keys has been modified - EXEC will fail - /// c: connection to be closed after writing entire reply - /// u: the client is unblocked - /// A: connection to be closed ASAP - /// N: no specific flag set - /// - public string FlagsRaw { get; private set; } - - /// - /// The host of the client (typically an IP address) - /// - public string Host - { - get - { - string host; - int port; - return Format.TryGetHostPort(Address, out host, out port) ? host : null; - } - } - - /// - /// idle time of the connection in seconds - /// - public int IdleSeconds { get; private set; } - - /// - /// last command played - /// - public string LastCommand { get; private set; } - - /// - /// The name allocated to this connection, if any - /// - public string Name { get; private set; } - - /// - /// number of pattern matching subscriptions - /// - public int PatternSubscriptionCount { get; private set; } - - /// - /// The port of the client - /// - public int Port - { - get - { - string host; - int port; - return Format.TryGetHostPort(Address, out host, out port) ? port : 0; - } - } - /// - /// The raw content from redis - /// - public string Raw { get; private set; } - - /// - /// number of channel subscriptions - /// - public int SubscriptionCount { get; private set; } - - /// - /// number of commands in a MULTI/EXEC context - /// - public int TransactionCommandLength { get; private set; } - - /// - /// an unique 64-bit client ID (introduced in Redis 2.8.12). - /// - public long Id { get;private set; } - - /// - /// Format the object as a string - /// - public override string ToString() - { - string addr = Format.ToString(Address); - return string.IsNullOrWhiteSpace(Name) ? addr : (addr + " - " + Name); - } - - /// - /// The class of the connection - /// - public ClientType ClientType - { - get - { - if (SubscriptionCount != 0 || PatternSubscriptionCount != 0) return ClientType.PubSub; - if ((Flags & ClientFlags.Slave) != 0) return ClientType.Slave; - return ClientType.Normal; - } - } - - internal static ClientInfo[] Parse(string input) - { - if (input == null) return null; - - var clients = new List(); - using (var reader = new StringReader(input)) - { - string line; - while ((line = reader.ReadLine()) != null) - { - var client = new ClientInfo(); - client.Raw = line; - string[] tokens = line.Split(StringSplits.Space); - for (int i = 0; i < tokens.Length; i++) - { - string tok = tokens[i]; - int idx = tok.IndexOf('='); - if (idx < 0) continue; - string key = tok.Substring(0, idx), value = tok.Substring(idx + 1); - - switch (key) - { - case "addr": client.Address = Format.TryParseEndPoint(value); break; - case "age": client.AgeSeconds = Format.ParseInt32(value); break; - case "idle": client.IdleSeconds = Format.ParseInt32(value); break; - case "db": client.Database = Format.ParseInt32(value); break; - case "name": client.Name = value; break; - case "sub": client.SubscriptionCount = Format.ParseInt32(value); break; - case "psub": client.PatternSubscriptionCount = Format.ParseInt32(value); break; - case "multi": client.TransactionCommandLength = Format.ParseInt32(value); break; - case "cmd": client.LastCommand = value; break; - case "flags": - client.FlagsRaw = value; - ClientFlags flags = ClientFlags.None; - AddFlag(ref flags, value, ClientFlags.SlaveMonitor, 'O'); - AddFlag(ref flags, value, ClientFlags.Slave, 'S'); - AddFlag(ref flags, value, ClientFlags.Master, 'M'); - AddFlag(ref flags, value, ClientFlags.Transaction, 'x'); - AddFlag(ref flags, value, ClientFlags.Blocked, 'b'); - AddFlag(ref flags, value, ClientFlags.TransactionDoomed, 'd'); - AddFlag(ref flags, value, ClientFlags.Closing, 'c'); - AddFlag(ref flags, value, ClientFlags.Unblocked, 'u'); - AddFlag(ref flags, value, ClientFlags.CloseASAP, 'A'); - client.Flags = flags; - break; - case "id": client.Id = Format.ParseInt64(value); break; - } - } - clients.Add(client); - } - } - - return clients.ToArray(); - } - - static void AddFlag(ref ClientFlags value, string raw, ClientFlags toAdd, char token) - { - if (raw.IndexOf(token) >= 0) value |= toAdd; - } - - private class ClientInfoProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch(result.Type) - { - case ResultType.BulkString: - - var raw = result.GetString(); - var clients = Parse(raw); - SetResult(message, clients); - return true; - } - return false; - } - } - } -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/ClientType.cs b/StackExchange.Redis/StackExchange/Redis/ClientType.cs deleted file mode 100644 index 03888c0fa..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ClientType.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace StackExchange.Redis -{ - /// - /// The class of the connection - /// - public enum ClientType - { - /// - /// Regular connections, including MONITOR connections - /// - Normal, - /// - /// Replication connections - /// - Slave, - /// - /// Subscription connections - /// - PubSub - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ClusterConfiguration.cs b/StackExchange.Redis/StackExchange/Redis/ClusterConfiguration.cs deleted file mode 100644 index 7d1a03386..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ClusterConfiguration.cs +++ /dev/null @@ -1,519 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; - -namespace StackExchange.Redis -{ - /// - /// Indicates a range of slots served by a cluster node - /// - public struct SlotRange : IEquatable, IComparable, IComparable - { - private readonly short from, to; - - /// - /// Create a new SlotRange value - /// - public SlotRange(int from, int to) - { - checked - { - this.from = (short)from; - this.to = (short)to; - } - } - - private SlotRange(short from, short to) - { - this.from = from; - this.to = to; - } - /// - /// The start of the range (inclusive) - /// - public int From => from; - - /// - /// The end of the range (inclusive) - /// - public int To => to; - - /// - /// Indicates whether two ranges are not equal - /// - public static bool operator !=(SlotRange x, SlotRange y) - { - return x.from != y.from || x.to != y.to; - } - - /// - /// Indicates whether two ranges are equal - /// - public static bool operator ==(SlotRange x, SlotRange y) - { - return x.from == y.from && x.to == y.to; - } - - /// - /// Try to parse a string as a range - /// - public static bool TryParse(string range, out SlotRange value) - { - if (string.IsNullOrWhiteSpace(range)) - { - value = default(SlotRange); - return false; - } - int i = range.IndexOf('-'); - short from, to; - if (i < 0) - { - if (TryParseInt16(range, 0, range.Length, out from)) - { - value = new SlotRange(from, from); - return true; - } - } - else - { - if (TryParseInt16(range, 0, i++, out from) && TryParseInt16(range, i, range.Length - i, out to)) - { - value = new SlotRange(from, to); - return true; - } - } - value = default(SlotRange); - return false; - } - - /// - /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. - /// - public int CompareTo(SlotRange other) - { - int delta = (int)this.from - (int)other.from; - return delta == 0 ? (int)this.to - (int)other.to : delta; - } - - /// - /// See Object.Equals - /// - public override bool Equals(object obj) - { - if (obj is SlotRange) - { - return Equals((SlotRange)obj); - } - return false; - } - /// - /// Indicates whether two ranges are equal - /// - public bool Equals(SlotRange range) - { - return range.from == this.from && range.to == this.to; - } - - /// - /// See Object.GetHashCode() - /// - public override int GetHashCode() - { - int x = from, y = to; // makes CS0675 a little happier - return x | (y << 16); - } - - /// - /// See Object.ToString() - /// - public override string ToString() - { - return from == to ? from.ToString() : (from + "-" + to); - } - internal bool Includes(int hashSlot) - { - return hashSlot >= from && hashSlot <= to; - } - - static bool TryParseInt16(string s, int offset, int count, out short value) - { - checked - { - value = 0; - int tmp = 0; - for (int i = 0; i < count; i++) - { - char c = s[offset + i]; - if (c < '0' || c > '9') return false; - tmp = (tmp * 10) + (c - '0'); - } - value = (short)tmp; - return true; - } - } - - int IComparable.CompareTo(object obj) - { - return obj is SlotRange ? CompareTo((SlotRange)obj) : -1; - } - } - - /// - /// Describes the state of the cluster as reported by a single node - /// - public sealed class ClusterConfiguration - { - private readonly Dictionary nodeLookup = new Dictionary(); - - private readonly ServerSelectionStrategy serverSelectionStrategy; - internal ClusterConfiguration(ServerSelectionStrategy serverSelectionStrategy, string nodes, EndPoint origin) - { - // Beware: Any exception thrown here will wreak silent havoc like inability to connect to cluster nodes or non returning calls - this.serverSelectionStrategy = serverSelectionStrategy; - this.Origin = origin; - using (var reader = new StringReader(nodes)) - { - string line; - while ((line = reader.ReadLine()) != null) - { - if (string.IsNullOrWhiteSpace(line)) continue; - var node = new ClusterNode(this, line, origin); - - // Be resilient to ":0 {master,slave},fail,noaddr" nodes, and nodes where the endpoint doesn't parse - if (node.IsNoAddr || node.EndPoint == null) - continue; - - // Override the origin value with the endpoint advertised with the target node to - // make sure that things like clusterConfiguration[clusterConfiguration.Origin] - // will work as expected. - if (node.IsMyself) - this.Origin = node.EndPoint; - - if (nodeLookup.ContainsKey(node.EndPoint)) - { - // Deal with conflicting node entries for the same endpoint - // This can happen in dynamic environments when a node goes down and a new one is created - // to replace it. - if (!node.IsConnected) - { - // The node we're trying to add is probably about to become stale. Ignore it. - continue; - } - else if (!nodeLookup[node.EndPoint].IsConnected) - { - // The node we registered previously is probably stale. Replace it with a known good node. - nodeLookup[node.EndPoint] = node; - } - else - { - // We have conflicting connected nodes. There's nothing much we can do other than - // wait for the cluster state to converge and refresh on the next pass. - // The same is true if we have multiple disconnected nodes. - } - } - else - { - nodeLookup.Add(node.EndPoint, node); - } - } - } - } - - /// - /// Gets all nodes contained in the configuration - /// - /// - public ICollection Nodes => nodeLookup.Values; - - /// - /// The node that was asked for the configuration - /// - public EndPoint Origin { get; } - - /// - /// Obtain the node relating to a specified endpoint - /// - public ClusterNode this[EndPoint endpoint] - { - get - { - ClusterNode result; - return endpoint == null ? null - : nodeLookup.TryGetValue(endpoint, out result) ? result : null; - } - } - - internal ClusterNode this[string nodeId] - { - get - { - if (string.IsNullOrWhiteSpace(nodeId)) return null; - foreach (var pair in nodeLookup) - { - if (pair.Value.NodeId == nodeId) return pair.Value; - } - return null; - } - } - - /// - /// Gets the node that serves the specified slot - /// - public ClusterNode GetBySlot(int slot) - { - foreach(var node in Nodes) - { - if (!node.IsSlave && node.ServesSlot(slot)) return node; - } - return null; - } - /// - /// Gets the node that serves the specified slot - /// - public ClusterNode GetBySlot(RedisKey key) - { - return GetBySlot(serverSelectionStrategy.HashSlot(key)); - } - } - - - /// - /// Represents the configuration of a single node in a cluster configuration - /// - public sealed class ClusterNode : IEquatable, IComparable, IComparable - { - private static readonly ClusterNode Dummy = new ClusterNode(); - - private static readonly IList NoNodes = new ClusterNode[0]; - - private static readonly IList NoSlots = new SlotRange[0]; - - private readonly ClusterConfiguration configuration; - - private IList children; - - private ClusterNode parent; - - private string toString; - - internal ClusterNode() { } - internal ClusterNode(ClusterConfiguration configuration, string raw, EndPoint origin) - { - // http://redis.io/commands/cluster-nodes - this.configuration = configuration; - this.Raw = raw; - var parts = raw.Split(StringSplits.Space); - - var flags = parts[2].Split(StringSplits.Comma); - - // redis 4 changes the format of "cluster nodes" - adds @... to the endpoint - var ep = parts[1]; - int at = ep.IndexOf('@'); - if (at >= 0) ep = ep.Substring(0, at); - - EndPoint = Format.TryParseEndPoint(ep); - if (flags.Contains("myself")) - { - IsMyself = true; - if (EndPoint == null) - { - // Unconfigured cluster nodes might report themselves as endpoint ":{port}", - // hence the origin fallback value to make sure that we can address them - EndPoint = origin; - } - } - - NodeId = parts[0]; - IsSlave = flags.Contains("slave"); - IsNoAddr = flags.Contains("noaddr"); - ParentNodeId = string.IsNullOrWhiteSpace(parts[3]) ? null : parts[3]; - - List slots = null; - - for (int i = 8; i < parts.Length; i++) - { - SlotRange range; - if (SlotRange.TryParse(parts[i], out range)) - { - if(slots == null) slots = new List(parts.Length - i); - slots.Add(range); - } - } - this.Slots = slots?.AsReadOnly() ?? NoSlots; - this.IsConnected = parts[7] == "connected"; // Can be "connected" or "disconnected" - } - /// - /// Gets all child nodes of the current node - /// - public IList Children - { - get - { - if (children != null) return children; - - List nodes = null; - foreach (var node in configuration.Nodes) - { - if (node.ParentNodeId == this.NodeId) - { - if (nodes == null) nodes = new List(); - nodes.Add(node); - } - } - children = nodes?.AsReadOnly() ?? NoNodes; - return children; - } - } - - /// - /// Gets the endpoint of the current node - /// - public EndPoint EndPoint { get; } - - /// - /// Gets whether this is the node which responded to the CLUSTER NODES request - /// - public bool IsMyself { get; } - - /// - /// Gets whether this node is a slave - /// - public bool IsSlave { get; } - - /// - /// Gets whether this node is flagged as noaddr - /// - public bool IsNoAddr { get; } - - /// - /// Gets the node's connection status - /// - public bool IsConnected { get; } - - /// - /// Gets the unique node-id of the current node - /// - public string NodeId { get; } - - /// - /// Gets the parent node of the current node - /// - public ClusterNode Parent - { - get - { - if (parent != null) return parent == Dummy ? null : parent; - ClusterNode found = configuration[ParentNodeId]; - parent = found ?? Dummy; - return found; - } - } - - /// - /// Gets the unique node-id of the parent of the current node - /// - public string ParentNodeId { get; } - - /// - /// The configuration as reported by the server - /// - public string Raw { get; } - - /// - /// The slots owned by this server - /// - public IList Slots { get; } - - /// - /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. - /// - public int CompareTo(ClusterNode other) - { - if (other == null) return -1; - - if (this.IsSlave != other.IsSlave) return IsSlave ? 1 : -1; // masters first - - if (IsSlave) // both slaves? compare by parent, so we get masters A, B, C and then slaves of A, B, C - { - int i = string.CompareOrdinal(this.ParentNodeId, other.ParentNodeId); - if (i != 0) return i; - } - return string.CompareOrdinal(this.NodeId, other.NodeId); - - } - - /// - /// See Object.Equals - /// - public override bool Equals(object obj) - { - return Equals(obj as ClusterNode); - } - - /// - /// Indicates whether two ClusterNode instances are equivalent - /// - public bool Equals(ClusterNode node) - { - if (node == null) return false; - - return this.ToString() == node.ToString(); // lazy, but effective - plus only computes once - } - - /// - /// See object.GetHashCode() - /// - public override int GetHashCode() - { - return ToString().GetHashCode(); - } - - /// - /// See Object.ToString() - /// - public override string ToString() - { - if (toString != null) return toString; - var sb = new StringBuilder().Append(NodeId).Append(" at ").Append(EndPoint); - if(IsSlave) - { - sb.Append(", slave of ").Append(ParentNodeId); - var parent = Parent; - if (parent != null) sb.Append(" at ").Append(parent.EndPoint); - } - var childCount = Children.Count; - switch(childCount) - { - case 0: break; - case 1: sb.Append(", 1 slave"); break; - default: sb.Append(", ").Append(childCount).Append(" slaves"); break; - } - if(Slots.Count != 0) - { - sb.Append(", slots: "); - foreach(var slot in Slots) - { - sb.Append(slot).Append(' '); - } - sb.Length -= 1; // remove tailing space - } - return toString = sb.ToString(); - } - internal bool ServesSlot(int hashSlot) - { - foreach (var slot in Slots) - { - if (slot.Includes(hashSlot)) return true; - } - return false; - } - - int IComparable.CompareTo(object obj) - { - return CompareTo(obj as ClusterNode); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/CommandFlags.cs b/StackExchange.Redis/StackExchange/Redis/CommandFlags.cs deleted file mode 100644 index 1a17a888b..000000000 --- a/StackExchange.Redis/StackExchange/Redis/CommandFlags.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; - -namespace StackExchange.Redis -{ - /// - /// Behaviour markers associated with a given command - /// - [Flags] - public enum CommandFlags - { - /// - /// Default behaviour. - /// - None = 0, - /// - /// This command may jump regular-priority commands that have not yet been written to the redis stream. - /// - HighPriority = 1, - /// - /// The caller is not interested in the result; the caller will immediately receive a default-value - /// of the expected return type (this value is not indicative of anything at the server). - /// - FireAndForget = 2, - - - /// - /// This operation should be performed on the master if it is available, but read operations may - /// be performed on a slave if no master is available. This is the default option. - /// - PreferMaster = 0, - - /// - /// This operation should only be performed on the master. - /// - DemandMaster = 4, - - /// - /// This operation should be performed on the slave if it is available, but will be performed on - /// a master if no slaves are available. Suitable for read operations only. - /// - PreferSlave = 8, - - /// - /// This operation should only be performed on a slave. Suitable for read operations only. - /// - DemandSlave = 12, - - // 16: reserved for additional "demand/prefer" options - - // 32: used for "asking" flag; never user-specified, so not visible on the public API - - /// - /// Indicates that this operation should not be forwarded to other servers as a result of an ASK or MOVED response - /// - NoRedirect = 64, - - // 128: used for "internal call"; never user-specified, so not visible on the public API - - // 256: used for "script unavailable"; never user-specified, so not visible on the public API - - /// - /// Indicates that script-related operations should use EVAL, not SCRIPT LOAD + EVALSHA - /// - NoScriptCache = 512, - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/CommandMap.cs b/StackExchange.Redis/StackExchange/Redis/CommandMap.cs deleted file mode 100644 index 8133350f6..000000000 --- a/StackExchange.Redis/StackExchange/Redis/CommandMap.cs +++ /dev/null @@ -1,242 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; - -namespace StackExchange.Redis -{ - /// - /// Represents the commands mapped on a particular configuration - /// - public sealed class CommandMap - { - private readonly byte[][] map; - - internal CommandMap(byte[][] map) - { - this.map = map; - } - /// - /// The default commands specified by redis - /// - public static CommandMap Default { get; } = CreateImpl(null, null); - - /// - /// The commands available to https://github.com/twitter/twemproxy - /// - /// https://github.com/twitter/twemproxy/blob/master/notes/redis.md - public static CommandMap Twemproxy { get; } = CreateImpl(null, exclusions: new HashSet - { - // see https://github.com/twitter/twemproxy/blob/master/notes/redis.md - RedisCommand.KEYS, RedisCommand.MIGRATE, RedisCommand.MOVE, RedisCommand.OBJECT, RedisCommand.RANDOMKEY, - RedisCommand.RENAME, RedisCommand.RENAMENX, RedisCommand.SORT, RedisCommand.SCAN, - - RedisCommand.BITOP, RedisCommand.MSET, RedisCommand.MSETNX, - - RedisCommand.HSCAN, - - RedisCommand.BLPOP, RedisCommand.BRPOP, RedisCommand.BRPOPLPUSH, // yeah, me neither! - - RedisCommand.SSCAN, - - RedisCommand.ZSCAN, - - RedisCommand.PSUBSCRIBE, RedisCommand.PUBLISH, RedisCommand.PUNSUBSCRIBE, RedisCommand.SUBSCRIBE, RedisCommand.UNSUBSCRIBE, - - RedisCommand.DISCARD, RedisCommand.EXEC, RedisCommand.MULTI, RedisCommand.UNWATCH, RedisCommand.WATCH, - - RedisCommand.SCRIPT, - - RedisCommand.ECHO, RedisCommand.PING, RedisCommand.QUIT, RedisCommand.SELECT, - - RedisCommand.BGREWRITEAOF, RedisCommand.BGSAVE, RedisCommand.CLIENT, RedisCommand.CLUSTER, RedisCommand.CONFIG, RedisCommand.DBSIZE, - RedisCommand.DEBUG, RedisCommand.FLUSHALL, RedisCommand.FLUSHDB, RedisCommand.INFO, RedisCommand.LASTSAVE, RedisCommand.MONITOR, RedisCommand.SAVE, - RedisCommand.SHUTDOWN, RedisCommand.SLAVEOF, RedisCommand.SLOWLOG, RedisCommand.SYNC, RedisCommand.TIME - }); - - /// - /// The commands available to http://www.ideawu.com/ssdb/ - /// - /// http://www.ideawu.com/ssdb/docs/redis-to-ssdb.html - public static CommandMap SSDB { get; } = Create(new HashSet { - // see http://www.ideawu.com/ssdb/docs/redis-to-ssdb.html - "ping", - "get", "set", "del", "incr", "incrby", "mget", "mset", "keys", "getset", "setnx", - "hget", "hset", "hdel", "hincrby", "hkeys", "hvals", "hmget", "hmset", "hlen", - "zscore", "zadd", "zrem", "zrange", "zrangebyscore", "zincrby", "zdecrby", "zcard", - "llen", "lpush", "rpush", "lpop", "rpop", "lrange", "lindex" - }, true); - - /// - /// The commands available to http://redis.io/topics/sentinel - /// - /// http://redis.io/topics/sentinel - public static CommandMap Sentinel { get; } = Create(new HashSet { - // see http://redis.io/topics/sentinel - "ping", "info", "sentinel", "subscribe", "psubscribe", "unsubscribe", "punsubscribe" }, true); - - /// - /// Create a new CommandMap, customizing some commands - /// - public static CommandMap Create(Dictionary overrides) - { - if (overrides == null || overrides.Count == 0) return Default; - - if (ReferenceEquals(overrides.Comparer, StringComparer.OrdinalIgnoreCase)) - { - // that's ok; we're happy with ordinal/invariant case-insensitive - // (but not culture-specific insensitive; completely untested) - } - else - { - // need case insensitive - overrides = new Dictionary(overrides, StringComparer.OrdinalIgnoreCase); - } - return CreateImpl(overrides, null); - } - - /// - /// Creates a CommandMap by specifying which commands are available or unavailable - /// - public static CommandMap Create(HashSet commands, bool available = true) - { - - if (available) - { - var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); - // nix everything - foreach (RedisCommand command in Enum.GetValues(typeof(RedisCommand))) - { - dictionary[command.ToString()] = null; - } - if (commands != null) - { - // then include (by removal) the things that are available - foreach (string command in commands) - { - dictionary.Remove(command); - } - } - return CreateImpl(dictionary, null); - } - else - { - HashSet exclusions = null; - if (commands != null) - { - // nix the things that are specified - foreach (var command in commands) - { - RedisCommand parsed; - if (Enum.TryParse(command, true, out parsed)) - { - (exclusions ?? (exclusions = new HashSet())).Add(parsed); - } - } - } - if (exclusions == null || exclusions.Count == 0) return Default; - return CreateImpl(null, exclusions); - } - - } - - /// - /// See Object.ToString() - /// - public override string ToString() - { - var sb = new StringBuilder(); - AppendDeltas(sb); - return sb.ToString(); - } - - internal void AppendDeltas(StringBuilder sb) - { - for (int i = 0; i < map.Length; i++) - { - var key = ((RedisCommand)i).ToString(); - var value = map[i] == null ? "" : Encoding.UTF8.GetString(map[i]); - if (key != value) - { - if (sb.Length != 0) sb.Append(','); - sb.Append('$').Append(key).Append('=').Append(value); - } - } - } - - internal void AssertAvailable(RedisCommand command) - { - if (map[(int)command] == null) throw ExceptionFactory.CommandDisabled(false, command, null, null); - } - - internal byte[] GetBytes(RedisCommand command) - { - return map[(int)command]; - } - internal byte[] GetBytes(string command) - { - if (command == null) return null; - if(Enum.TryParse(command, true, out RedisCommand cmd)) - { // we know that one! - return map[(int)cmd]; - } - var bytes = (byte[])_unknownCommands[command]; - if(bytes == null) - { - lock(_unknownCommands) - { // double-checked - bytes = (byte[])_unknownCommands[command]; - if(bytes == null) - { - bytes = Encoding.ASCII.GetBytes(command); - _unknownCommands[command] = bytes; - } - } - } - return bytes; - } - static readonly Hashtable _unknownCommands = new Hashtable(); - - internal bool IsAvailable(RedisCommand command) - { - return map[(int)command] != null; - } - - private static CommandMap CreateImpl(Dictionary caseInsensitiveOverrides, HashSet exclusions) - { - var commands = (RedisCommand[])Enum.GetValues(typeof(RedisCommand)); - - byte[][] map = new byte[commands.Length][]; - bool haveDelta = false; - for (int i = 0; i < commands.Length; i++) - { - int idx = (int)commands[i]; - string name = commands[i].ToString(), value = name; - - if (exclusions != null && exclusions.Contains(commands[i])) - { - map[idx] = null; - } - else - { - if (caseInsensitiveOverrides != null) - { - string tmp; - if (caseInsensitiveOverrides.TryGetValue(name, out tmp)) - { - value = tmp; - } - } - if (value != name) haveDelta = true; - // TODO: bug? - haveDelta = true; - byte[] val = string.IsNullOrWhiteSpace(value) ? null : Encoding.UTF8.GetBytes(value); - map[idx] = val; - } - } - if (!haveDelta && Default != null) return Default; - - return new CommandMap(map); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/CommandStatus.cs b/StackExchange.Redis/StackExchange/Redis/CommandStatus.cs deleted file mode 100644 index 550e2e8aa..000000000 --- a/StackExchange.Redis/StackExchange/Redis/CommandStatus.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace StackExchange.Redis -{ - /// - /// track status of a command while communicating with Redis - /// - public enum CommandStatus - { - /// - /// command status unknown - /// - Unknown, - /// - /// ConnectionMultiplexer has not yet started writing this command to redis - /// - WaitingToBeSent, - /// - /// command has been sent to Redis - /// - Sent, - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/CommandTrace.cs b/StackExchange.Redis/StackExchange/Redis/CommandTrace.cs deleted file mode 100644 index 6e5fefb33..000000000 --- a/StackExchange.Redis/StackExchange/Redis/CommandTrace.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; - -namespace StackExchange.Redis -{ - /// - /// Represents the information known about long-running commands - /// - public sealed class CommandTrace - { - internal static readonly ResultProcessor Processor = new CommandTraceProcessor(); - - internal CommandTrace(long uniqueId, long time, long duration, RedisValue[] arguments) - { - UniqueId = uniqueId; - Time = RedisBase.UnixEpoch.AddSeconds(time); - // duration = The amount of time needed for its execution, in microseconds. - // A tick is equal to 100 nanoseconds, or one ten-millionth of a second. - // So 1 microsecond = 10 ticks - Duration = TimeSpan.FromTicks(duration * 10); - Arguments = arguments; - } - - /// - /// The array composing the arguments of the command. - /// - public RedisValue[] Arguments { get; private set; } - - /// - /// The amount of time needed for its execution - /// - public TimeSpan Duration { get; private set; } - - /// - /// The time at which the logged command was processed. - /// - public DateTime Time { get; private set; } - - /// - /// A unique progressive identifier for every slow log entry. - /// - /// The entry's unique ID can be used in order to avoid processing slow log entries multiple times (for instance you may have a script sending you an email alert for every new slow log entry). The ID is never reset in the course of the Redis server execution, only a server restart will reset it. - public long UniqueId { get; private set; } - - /// - /// Deduces a link to the redis documentation about the specified command - /// - public string GetHelpUrl() - { - if (Arguments == null || Arguments.Length == 0) return null; - - const string BaseUrl = "http://redis.io/commands/"; - - string encoded0 = Uri.EscapeUriString(((string)Arguments[0]).ToLowerInvariant()); - - if (Arguments.Length > 1) - { - - switch (encoded0) - { - case "script": - case "client": - case "cluster": - case "config": - case "debug": - case "pubsub": - string encoded1 = Uri.EscapeUriString(((string)Arguments[1]).ToLowerInvariant()); - return BaseUrl + encoded0 + "-" + encoded1; - } - } - return BaseUrl + encoded0; - } - - private class CommandTraceProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch(result.Type) - { - case ResultType.MultiBulk: - var parts = result.GetItems(); - CommandTrace[] arr = new CommandTrace[parts.Length]; - for (int i = 0; i < parts.Length; i++) - { - - var subParts = parts[i].GetItems(); - long uniqueid, time, duration; - if (!subParts[0].TryGetInt64(out uniqueid) || !subParts[1].TryGetInt64(out time) || !subParts[2].TryGetInt64(out duration)) - return false; - arr[i] = new CommandTrace(uniqueid, time, duration, subParts[3].GetItemsAsValues()); - } - SetResult(message, arr); - return true; - } - return false; - } - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/Compat/ConvertHelper.cs b/StackExchange.Redis/StackExchange/Redis/Compat/ConvertHelper.cs deleted file mode 100644 index 966f5c9f6..000000000 --- a/StackExchange.Redis/StackExchange/Redis/Compat/ConvertHelper.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; - -namespace StackExchange.Redis -{ - /// - /// Helper for Array.ConvertAll() as it's missing on .Net Core. - /// - public static class ConvertHelper - { - /// - /// Converts array of one type to an array of another type. - /// - /// Input type - /// Output type - /// source - /// selector - /// - public static TOutput[] ConvertAll(TInput[] source, Func selector) - { -#if CORE_CLR - TOutput[] arr = new TOutput[source.Length]; - for(int i = 0 ; i < arr.Length ; i++) - arr[i] = selector(source[i]); - return arr; -#else - return Array.ConvertAll(source, item => selector(item)); -#endif - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/Compat/VolatileWrapper.cs b/StackExchange.Redis/StackExchange/Redis/Compat/VolatileWrapper.cs deleted file mode 100644 index b86fb7a25..000000000 --- a/StackExchange.Redis/StackExchange/Redis/Compat/VolatileWrapper.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace StackExchange.Redis -{ - internal static class VolatileWrapper - { - public static int Read(ref int location) - { -#if !CORE_CLR - return System.Threading.Thread.VolatileRead(ref location); -#else - return System.Threading.Volatile.Read(ref location); -#endif - } - - public static void Write(ref int address, int value) - { -#if !CORE_CLR - System.Threading.Thread.VolatileWrite(ref address, value); -#else - System.Threading.Volatile.Write(ref address, value); -#endif - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/CompletedDefaultTask.cs b/StackExchange.Redis/StackExchange/Redis/CompletedDefaultTask.cs deleted file mode 100644 index e1775c497..000000000 --- a/StackExchange.Redis/StackExchange/Redis/CompletedDefaultTask.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - internal static class CompletedTask - { - private static readonly Task @default = FromResult(default(T), null); - - public static Task Default(object asyncState) - { - return asyncState == null ? @default : FromResult(default(T), asyncState); - } - public static Task FromResult(T value, object asyncState) - { - // note we do not need to deny exec-sync here; the value will be known - // before we hand it to them - var tcs = TaskSource.Create(asyncState); - tcs.SetResult(value); - return tcs.Task; - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/CompletionManager.cs b/StackExchange.Redis/StackExchange/Redis/CompletionManager.cs deleted file mode 100644 index 018009685..000000000 --- a/StackExchange.Redis/StackExchange/Redis/CompletionManager.cs +++ /dev/null @@ -1,195 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; - -namespace StackExchange.Redis -{ - sealed partial class CompletionManager - { - private static readonly WaitCallback processAsyncCompletionQueue = ProcessAsyncCompletionQueue, - anyOrderCompletionHandler = AnyOrderCompletionHandler; - - private readonly Queue asyncCompletionQueue = new Queue(); - - private readonly ConnectionMultiplexer multiplexer; - - private readonly string name; - - int activeAsyncWorkerThread = 0; - long completedSync, completedAsync, failedAsync; - public CompletionManager(ConnectionMultiplexer multiplexer, string name) - { - this.multiplexer = multiplexer; - this.name = name; - } - public void CompleteSyncOrAsync(ICompletable operation) - { - if (operation == null) return; - if (operation.TryComplete(false)) - { - multiplexer.Trace("Completed synchronously: " + operation, name); - Interlocked.Increment(ref completedSync); - return; - } - else - { - if (multiplexer.PreserveAsyncOrder) - { - multiplexer.Trace("Queueing for asynchronous completion", name); - bool startNewWorker; - lock (asyncCompletionQueue) - { - asyncCompletionQueue.Enqueue(operation); - startNewWorker = asyncCompletionQueue.Count == 1; - } - if (startNewWorker) - { - multiplexer.Trace("Starting new async completion worker", name); - OnCompletedAsync(); - ThreadPool.QueueUserWorkItem(processAsyncCompletionQueue, this); - } - } else - { - multiplexer.Trace("Using thread-pool for asynchronous completion", name); - ThreadPool.QueueUserWorkItem(anyOrderCompletionHandler, operation); - Interlocked.Increment(ref completedAsync); // k, *technically* we haven't actually completed this yet, but: close enough - } - } - } - internal void GetCounters(ConnectionCounters counters) - { - lock (asyncCompletionQueue) - { - counters.ResponsesAwaitingAsyncCompletion = asyncCompletionQueue.Count; - } - counters.CompletedSynchronously = Interlocked.Read(ref completedSync); - counters.CompletedAsynchronously = Interlocked.Read(ref completedAsync); - counters.FailedAsynchronously = Interlocked.Read(ref failedAsync); - } - - internal int GetOutstandingCount() - { - lock(asyncCompletionQueue) - { - return asyncCompletionQueue.Count; - } - } - - internal void GetStormLog(StringBuilder sb) - { - lock(asyncCompletionQueue) - { - if (asyncCompletionQueue.Count == 0) return; - sb.Append("Response awaiting completion: ").Append(asyncCompletionQueue.Count).AppendLine(); - int total = 0; - foreach(var item in asyncCompletionQueue) - { - if (++total >= 500) break; - item.AppendStormLog(sb); - sb.AppendLine(); - } - } - } - - private static void AnyOrderCompletionHandler(object state) - { - try - { - ConnectionMultiplexer.TraceWithoutContext("Completing async (any order): " + state); - ((ICompletable)state).TryComplete(true); - } - catch (Exception ex) - { - ConnectionMultiplexer.TraceWithoutContext("Async completion error: " + ex.Message); - } - } - - private static void ProcessAsyncCompletionQueue(object state) - { - ((CompletionManager)state).ProcessAsyncCompletionQueueImpl(); - } - - partial void OnCompletedAsync(); - private void ProcessAsyncCompletionQueueImpl() - { -#if NET40 - int currentThread = Thread.CurrentThread.ManagedThreadId; -#else - int currentThread = Environment.CurrentManagedThreadId; -#endif - - try - { - while (Interlocked.CompareExchange(ref activeAsyncWorkerThread, currentThread, 0) != 0) - { - // if we don't win the lock, check whether there is still work; if there is we - // need to retry to prevent a nasty race condition - lock(asyncCompletionQueue) - { - if (asyncCompletionQueue.Count == 0) return; // another thread drained it; can exit - } - Thread.Sleep(1); - } - int total = 0; - do - { - ICompletable next; - lock (asyncCompletionQueue) - { - next = asyncCompletionQueue.Count == 0 ? null - : asyncCompletionQueue.Dequeue(); - } - if (next == null) - { - // give it a moment and try again, noting that we might lose the battle - // when we pause - Interlocked.CompareExchange(ref activeAsyncWorkerThread, 0, currentThread); - if (SpinWait() && Interlocked.CompareExchange(ref activeAsyncWorkerThread, currentThread, 0) == 0) - { - // we paused, and we got the lock back; anything else? - lock (asyncCompletionQueue) - { - next = asyncCompletionQueue.Count == 0 ? null - : asyncCompletionQueue.Dequeue(); - } - } - } - if (next == null) break; // nothing to do <===== exit point - try - { - multiplexer.Trace("Completing async (ordered): " + next, name); - next.TryComplete(true); - Interlocked.Increment(ref completedAsync); - } - catch (Exception ex) - { - multiplexer.Trace("Async completion error: " + ex.Message, name); - Interlocked.Increment(ref failedAsync); - } - total++; - } while (true); - multiplexer.Trace("Async completion worker processed " + total + " operations", name); - } - finally - { - Interlocked.CompareExchange(ref activeAsyncWorkerThread, 0, currentThread); - } - } - - private bool SpinWait() - { - var sw = new SpinWait(); - byte maxSpins = 128; - do - { - if (sw.NextSpinWillYield) - return true; - maxSpins--; - } - while (maxSpins > 0); - - return false; - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ConcurrentProfileStorageCollection.cs b/StackExchange.Redis/StackExchange/Redis/ConcurrentProfileStorageCollection.cs deleted file mode 100644 index 80cbde52c..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ConcurrentProfileStorageCollection.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System.Collections.Generic; -using System.Threading; - -namespace StackExchange.Redis -{ - /// - /// A collection of IProfiledCommands. - /// - /// This is a very light weight data structure, only supporting enumeration. - /// - /// While it implements IEnumerable, it there are fewer allocations if one uses - /// it's explicit GetEnumerator() method. Using `foreach` does this automatically. - /// - /// This type is not threadsafe. - /// - public struct ProfiledCommandEnumerable : IEnumerable - { - /// - /// Implements IEnumerator for ProfiledCommandEnumerable. - /// This implementation is comparable to List.Enumerator and Dictionary.Enumerator, - /// and is provided to reduce allocations in the common (ie. foreach) case. - /// - /// This type is not threadsafe. - /// - public struct Enumerator : IEnumerator - { - ProfileStorage Head; - ProfileStorage CurrentBacker; - - bool IsEmpty => Head == null; - bool IsUnstartedOrFinished => CurrentBacker == null; - - internal Enumerator(ProfileStorage head) - { - Head = head; - CurrentBacker = null; - } - - /// - /// The current element. - /// - public IProfiledCommand Current => CurrentBacker; - - object System.Collections.IEnumerator.Current => CurrentBacker; - - /// - /// Advances the enumeration, returning true if there is a new element to consume and false - /// if enumeration is complete. - /// - public bool MoveNext() - { - if (IsEmpty) return false; - - if (IsUnstartedOrFinished) - { - CurrentBacker = Head; - } - else - { - CurrentBacker = CurrentBacker.NextElement; - } - - return CurrentBacker != null; - } - - /// - /// Resets the enumeration. - /// - public void Reset() - { - CurrentBacker = null; - } - - /// - /// Disposes the enumeration. - /// subsequent attempts to enumerate results in undefined behavior. - /// - public void Dispose() - { - CurrentBacker = Head = null; - } - } - - ProfileStorage Head; - - internal ProfiledCommandEnumerable(ProfileStorage head) - { - Head = head; - } - - /// - /// Returns an implementor of IEnumerator that, provided it isn't accessed - /// though an interface, avoids allocations. - /// - /// `foreach` will automatically use this method. - /// - public Enumerator GetEnumerator() - { - return new Enumerator(Head); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - /// - /// A thread-safe collection tailored to the "always append, with high contention, then enumerate once with no contention" - /// behavior of our profiling. - /// - /// Performs better than ConcurrentBag, which is important since profiling code shouldn't impact timings. - /// - sealed class ConcurrentProfileStorageCollection - { - // internal for test purposes - internal static int AllocationCount = 0; - - // It is, by definition, impossible for an element to be in 2 intrusive collections - // and we force Enumeration to release any reference to the collection object - // so we can **always** pool these. - const int PoolSize = 64; - static ConcurrentProfileStorageCollection[] Pool = new ConcurrentProfileStorageCollection[PoolSize]; - - volatile ProfileStorage Head; - - private ConcurrentProfileStorageCollection() { } - - // for testing purposes only - internal static int CountInPool() - { - var ret = 0; - - for (var i = 0; i < PoolSize; i++) - { - var inPool = Pool[i]; - if (inPool != null) ret++; - } - - return ret; - } - - /// - /// This method is thread-safe. - /// - /// Adds an element to the bag. - /// - /// Order is not preserved. - /// - /// The element can only be a member of *one* bag. - /// - public void Add(ProfileStorage command) - { - do - { - var cur = Head; - command.NextElement = cur; - - // Interlocked references to volatile fields are perfectly cromulent -#pragma warning disable 420 - var got = Interlocked.CompareExchange(ref Head, command, cur); -#pragma warning restore 420 - - if (object.ReferenceEquals(got, cur)) break; - } while (true); - } - - /// - /// This method returns an enumerable view of the bag, and returns it to - /// an internal pool for reuse by GetOrCreate(). - /// - /// It is not thread safe. - /// - /// It should only be called once the bag is finished being mutated. - /// - public ProfiledCommandEnumerable EnumerateAndReturnForReuse() - { - var ret = new ProfiledCommandEnumerable(Head); - - ReturnForReuse(); - - return ret; - } - - /// - /// This returns the ConcurrentProfileStorageCollection to an internal pool for reuse by GetOrCreate(). - /// - public void ReturnForReuse() - { - // no need for interlocking, this isn't a thread safe method - Head = null; - - for (var i = 0; i < PoolSize; i++) - { - if (Interlocked.CompareExchange(ref Pool[i], this, null) == null) break; - } - } - - /// - /// Returns a ConcurrentProfileStorageCollection to use. - /// - /// It *may* have allocated a new one, or it may return one that has previously been released. - /// To return the collection, call EnumerateAndReturnForReuse() - /// - public static ConcurrentProfileStorageCollection GetOrCreate() - { - ConcurrentProfileStorageCollection found; - for (int i = 0; i < PoolSize; i++) - { - if ((found = Interlocked.Exchange(ref Pool[i], null)) != null) - { - return found; - } - } - - Interlocked.Increment(ref AllocationCount); - found = new ConcurrentProfileStorageCollection(); - - return found; - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/Condition.cs b/StackExchange.Redis/StackExchange/Redis/Condition.cs deleted file mode 100644 index 425681e11..000000000 --- a/StackExchange.Redis/StackExchange/Redis/Condition.cs +++ /dev/null @@ -1,621 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace StackExchange.Redis -{ - - /// - /// Describes a pre-condition used in a redis transaction - /// - public abstract class Condition - { - internal abstract Condition MapKeys(Func map); - - private Condition() { } - - /// - /// Enforces that the given hash-field must have the specified value - /// - public static Condition HashEqual(RedisKey key, RedisValue hashField, RedisValue value) - { - if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField)); - if (value.IsNull) return HashNotExists(key, hashField); - return new EqualsCondition(key, hashField, true, value); - } - - /// - /// Enforces that the given hash-field must exist - /// - public static Condition HashExists(RedisKey key, RedisValue hashField) - { - if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField)); - return new ExistsCondition(key, hashField, true); - } - - /// - /// Enforces that the given hash-field must not have the specified value - /// - public static Condition HashNotEqual(RedisKey key, RedisValue hashField, RedisValue value) - { - if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField)); - if (value.IsNull) return HashExists(key, hashField); - return new EqualsCondition(key, hashField, false, value); - } - - /// - /// Enforces that the given hash-field must not exist - /// - public static Condition HashNotExists(RedisKey key, RedisValue hashField) - { - if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField)); - return new ExistsCondition(key, hashField, false); - } - - /// - /// Enforces that the given key must exist - /// - public static Condition KeyExists(RedisKey key) - { - return new ExistsCondition(key, RedisValue.Null, true); - } - - /// - /// Enforces that the given key must not exist - /// - public static Condition KeyNotExists(RedisKey key) - { - return new ExistsCondition(key, RedisValue.Null, false); - } - - /// - /// Enforces that the given list index must have the specified value - /// - public static Condition ListIndexEqual(RedisKey key, long index, RedisValue value) - { - return new ListCondition(key, index, true, value); - } - - /// - /// Enforces that the given list index must exist - /// - public static Condition ListIndexExists(RedisKey key, long index) - { - return new ListCondition(key, index, true, null); - } - - /// - /// Enforces that the given list index must not have the specified value - /// - public static Condition ListIndexNotEqual(RedisKey key, long index, RedisValue value) - { - return new ListCondition(key, index, false, value); - } - - /// - /// Enforces that the given list index must not exist - /// - public static Condition ListIndexNotExists(RedisKey key, long index) - { - return new ListCondition(key, index, false, null); - } - - /// - /// Enforces that the given key must have the specified value - /// - public static Condition StringEqual(RedisKey key, RedisValue value) - { - if (value.IsNull) return KeyNotExists(key); - return new EqualsCondition(key, RedisValue.Null, true, value); - } - - /// - /// Enforces that the given key must not have the specified value - /// - public static Condition StringNotEqual(RedisKey key, RedisValue value) - { - if (value.IsNull) return KeyExists(key); - return new EqualsCondition(key, RedisValue.Null, false, value); - } - - /// - /// Enforces that the given hash length is a certain value - /// - public static Condition HashLengthEqual(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.Hash, 0, length); - } - - /// - /// Enforces that the given hash length is less than a certain value - /// - public static Condition HashLengthLessThan(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.Hash, 1, length); - } - - /// - /// Enforces that the given hash length is greater than a certain value - /// - public static Condition HashLengthGreaterThan(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.Hash, -1, length); - } - - /// - /// Enforces that the given string length is a certain value - /// - public static Condition StringLengthEqual(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.String, 0, length); - } - - /// - /// Enforces that the given string length is less than a certain value - /// - public static Condition StringLengthLessThan(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.String, 1, length); - } - - /// - /// Enforces that the given string length is greater than a certain value - /// - public static Condition StringLengthGreaterThan(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.String, -1, length); - } - - /// - /// Enforces that the given list length is a certain value - /// - public static Condition ListLengthEqual(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.List, 0, length); - } - - /// - /// Enforces that the given list length is less than a certain value - /// - public static Condition ListLengthLessThan(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.List, 1, length); - } - - /// - /// Enforces that the given list length is greater than a certain value - /// - public static Condition ListLengthGreaterThan(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.List, -1, length); - } - - /// - /// Enforces that the given set cardinality is a certain value - /// - public static Condition SetLengthEqual(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.Set, 0, length); - } - - /// - /// Enforces that the given set cardinality is less than a certain value - /// - public static Condition SetLengthLessThan(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.Set, 1, length); - } - - /// - /// Enforces that the given set cardinality is greater than a certain value - /// - public static Condition SetLengthGreaterThan(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.Set, -1, length); - } - - /// - /// Enforces that the given sorted set cardinality is a certain value - /// - public static Condition SortedSetLengthEqual(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.SortedSet, 0, length); - } - - /// - /// Enforces that the given sorted set cardinality is less than a certain value - /// - public static Condition SortedSetLengthLessThan(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.SortedSet, 1, length); - } - - /// - /// Enforces that the given sorted set cardinality is greater than a certain value - /// - public static Condition SortedSetLengthGreaterThan(RedisKey key, long length) - { - return new LengthCondition(key, RedisType.SortedSet, -1, length); - } - - internal abstract void CheckCommands(CommandMap commandMap); - - internal abstract IEnumerable CreateMessages(int db, ResultBox resultBox); - - internal abstract int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy); - internal abstract bool TryValidate(RawResult result, out bool value); - - internal sealed class ConditionProcessor : ResultProcessor - { - public static readonly ConditionProcessor Default = new ConditionProcessor(); - - public static Message CreateMessage(Condition condition, int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue value = default(RedisValue)) - { - return new ConditionMessage(condition, db, flags, command, key, value); - } - - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - var msg = message as ConditionMessage; - var condition = msg?.Condition; - bool final; - if (condition != null && condition.TryValidate(result, out final)) - { - SetResult(message, final); - return true; - } - return false; - } - - private class ConditionMessage : Message.CommandKeyBase - { - public readonly Condition Condition; - private RedisValue value; - - public ConditionMessage(Condition condition, int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue value) - : base(db, flags, command, key) - { - this.Condition = condition; - this.value = value; // note no assert here - } - - internal override void WriteImpl(PhysicalConnection physical) - { - if (value.IsNull) - { - physical.WriteHeader(command, 1); - physical.Write(Key); - } - else - { - physical.WriteHeader(command, 2); - physical.Write(Key); - physical.Write(value); - } - } - } - } - - internal class ExistsCondition : Condition - { - private readonly bool expectedResult; - private readonly RedisValue hashField; - private readonly RedisKey key; - - internal override Condition MapKeys(Func map) - { - return new ExistsCondition(map(key), hashField, expectedResult); - } - public ExistsCondition(RedisKey key, RedisValue hashField, bool expectedResult) - { - if (key.IsNull) throw new ArgumentException("key"); - this.key = key; - this.hashField = hashField; - this.expectedResult = expectedResult; - } - - public override string ToString() - { - return (hashField.IsNull ? key.ToString() : ((string)key) + " > " + hashField) - + (expectedResult ? " exists" : " does not exists"); - } - - internal override void CheckCommands(CommandMap commandMap) - { - commandMap.AssertAvailable(hashField.IsNull ? RedisCommand.EXISTS : RedisCommand.HEXISTS); - } - - internal override IEnumerable CreateMessages(int db, ResultBox resultBox) - { - yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); - - var cmd = hashField.IsNull ? RedisCommand.EXISTS : RedisCommand.HEXISTS; - var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key, hashField); - message.SetSource(ConditionProcessor.Default, resultBox); - yield return message; - } - - internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - return serverSelectionStrategy.HashSlot(key); - } - internal override bool TryValidate(RawResult result, out bool value) - { - bool parsed; - if (ResultProcessor.DemandZeroOrOneProcessor.TryGet(result, out parsed)) - { - value = parsed == expectedResult; - ConnectionMultiplexer.TraceWithoutContext("exists: " + parsed + "; expected: " + expectedResult + "; voting: " + value); - return true; - } - value = false; - return false; - } - } - - internal class EqualsCondition : Condition - { - - internal override Condition MapKeys(Func map) - { - return new EqualsCondition(map(key), hashField, expectedEqual, expectedValue); - } - private readonly bool expectedEqual; - private readonly RedisValue hashField, expectedValue; - private readonly RedisKey key; - public EqualsCondition(RedisKey key, RedisValue hashField, bool expectedEqual, RedisValue expectedValue) - { - if (key.IsNull) throw new ArgumentException("key"); - this.key = key; - this.hashField = hashField; - this.expectedEqual = expectedEqual; - this.expectedValue = expectedValue; - } - - public override string ToString() - { - return (hashField.IsNull ? key.ToString() : ((string)key) + " > " + hashField) - + (expectedEqual ? " == " : " != ") - + expectedValue; - } - - internal override void CheckCommands(CommandMap commandMap) - { - commandMap.AssertAvailable(hashField.IsNull ? RedisCommand.GET : RedisCommand.HGET); - } - - internal sealed override IEnumerable CreateMessages(int db, ResultBox resultBox) - { - yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); - - var cmd = hashField.IsNull ? RedisCommand.GET : RedisCommand.HGET; - var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key, hashField); - message.SetSource(ConditionProcessor.Default, resultBox); - yield return message; - } - - internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - return serverSelectionStrategy.HashSlot(key); - } - internal override bool TryValidate(RawResult result, out bool value) - { - switch (result.Type) - { - case ResultType.BulkString: - case ResultType.SimpleString: - case ResultType.Integer: - var parsed = result.AsRedisValue(); - value = (parsed == expectedValue) == expectedEqual; - ConnectionMultiplexer.TraceWithoutContext("actual: " + (string)parsed + "; expected: " + (string)expectedValue + - "; wanted: " + (expectedEqual ? "==" : "!=") + "; voting: " + value); - return true; - } - value = false; - return false; - } - } - - internal class ListCondition : Condition - { - internal override Condition MapKeys(Func map) - { - return new ListCondition(map(key), index, expectedResult, expectedValue); - } - private readonly bool expectedResult; - private readonly long index; - private readonly RedisValue? expectedValue; - private readonly RedisKey key; - public ListCondition(RedisKey key, long index, bool expectedResult, RedisValue? expectedValue) - { - if (key.IsNull) throw new ArgumentException(nameof(key)); - this.key = key; - this.index = index; - this.expectedResult = expectedResult; - this.expectedValue = expectedValue; - } - - public override string ToString() - { - return ((string)key) + "[" + index.ToString() + "]" - + (expectedValue.HasValue ? (expectedResult ? " == " : " != ") + expectedValue.Value : (expectedResult ? " exists" : " does not exist")); - } - - internal override void CheckCommands(CommandMap commandMap) - { - commandMap.AssertAvailable(RedisCommand.LINDEX); - } - - internal sealed override IEnumerable CreateMessages(int db, ResultBox resultBox) - { - yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); - - var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, RedisCommand.LINDEX, key, index); - message.SetSource(ConditionProcessor.Default, resultBox); - yield return message; - } - - internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - return serverSelectionStrategy.HashSlot(key); - } - internal override bool TryValidate(RawResult result, out bool value) - { - switch (result.Type) - { - case ResultType.BulkString: - case ResultType.SimpleString: - case ResultType.Integer: - var parsed = result.AsRedisValue(); - if (expectedValue.HasValue) - { - value = (parsed == expectedValue.Value) == expectedResult; - ConnectionMultiplexer.TraceWithoutContext("actual: " + (string)parsed + "; expected: " + (string)expectedValue.Value + - "; wanted: " + (expectedResult ? "==" : "!=") + "; voting: " + value); - } - else - { - value = (parsed.IsNull != expectedResult); - ConnectionMultiplexer.TraceWithoutContext("exists: " + parsed + "; expected: " + expectedResult + "; voting: " + value); - } - return true; - } - value = false; - return false; - } - } - - internal class LengthCondition : Condition - { - internal override Condition MapKeys(Func map) - { - return new LengthCondition(map(key), type, compareToResult, expectedLength); - } - private readonly int compareToResult; - private readonly long expectedLength; - private readonly RedisKey key; - private readonly RedisType type; - private readonly RedisCommand cmd; - - public LengthCondition(RedisKey key, RedisType type, int compareToResult, long expectedLength) - { - if (key.IsNull) throw new ArgumentException(nameof(key)); - this.key = key; - this.compareToResult = compareToResult; - this.expectedLength = expectedLength; - this.type = type; - switch (type) { - case RedisType.Hash: - cmd = RedisCommand.HLEN; - break; - - case RedisType.Set: - cmd = RedisCommand.SCARD; - break; - - case RedisType.List: - cmd = RedisCommand.LLEN; - break; - - case RedisType.SortedSet: - cmd = RedisCommand.ZCARD; - break; - - case RedisType.String: - cmd = RedisCommand.STRLEN; - break; - - default: - throw new ArgumentException(nameof(type)); - } - } - - public override string ToString() - { - return ((string)key) + " " + type + " length" + GetComparisonString() + expectedLength; - } - - private string GetComparisonString() - { - return compareToResult == 0 ? " == " : (compareToResult < 0 ? " > " : " < "); - } - - internal override void CheckCommands(CommandMap commandMap) - { - commandMap.AssertAvailable(cmd); - } - - internal sealed override IEnumerable CreateMessages(int db, ResultBox resultBox) - { - yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); - - var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key); - message.SetSource(ConditionProcessor.Default, resultBox); - yield return message; - } - - internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - return serverSelectionStrategy.HashSlot(key); - } - internal override bool TryValidate(RawResult result, out bool value) - { - switch (result.Type) - { - case ResultType.BulkString: - case ResultType.SimpleString: - case ResultType.Integer: - var parsed = result.AsRedisValue(); - value = parsed.IsInteger && (expectedLength.CompareTo((long) parsed) == compareToResult); - ConnectionMultiplexer.TraceWithoutContext("actual: " + (string)parsed + "; expected: " + expectedLength + - "; wanted: " + GetComparisonString() + "; voting: " + value); - return true; - } - value = false; - return false; - } - } - - } - - /// - /// Indicates the status of a condition as part of a transaction - /// - public sealed class ConditionResult - { - internal readonly Condition Condition; - - private ResultBox resultBox; - - private volatile bool wasSatisfied; - - internal ConditionResult(Condition condition) - { - this.Condition = condition; - resultBox = ResultBox.Get(condition); - } - - /// - /// Indicates whether the condition was satisfied - /// - public bool WasSatisfied => wasSatisfied; - - internal IEnumerable CreateMessages(int db) - { - return Condition.CreateMessages(db, resultBox); - } - - internal ResultBox GetBox() { return resultBox; } - internal bool UnwrapBox() - { - if (resultBox != null) - { - Exception ex; - bool val; - ResultBox.UnwrapAndRecycle(resultBox, false, out val, out ex); - resultBox = null; - wasSatisfied = ex == null && val; - } - return wasSatisfied; - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ConfigurationOptions.cs b/StackExchange.Redis/StackExchange/Redis/ConfigurationOptions.cs deleted file mode 100644 index 9455e0e7f..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ConfigurationOptions.cs +++ /dev/null @@ -1,726 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Security; -using System.Security.Authentication; -using System.Text; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - - /// - /// Specifies the proxy that is being used to communicate to redis - /// - public enum Proxy - { - /// - /// Direct communication to the redis server(s) - /// - None, - /// - /// Communication via twemproxy - /// - Twemproxy - } - - /// - /// The options relevant to a set of redis connections - /// - public sealed class ConfigurationOptions -#if !CORE_CLR - : ICloneable -#endif - { - internal const string DefaultTieBreaker = "__Booksleeve_TieBreak", DefaultConfigurationChannel = "__Booksleeve_MasterChanged"; - - private static class OptionKeys - { - public static int ParseInt32(string key, string value, int minValue = int.MinValue, int maxValue = int.MaxValue) - { - int tmp; - if (!Format.TryParseInt32(value, out tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires an integer value"); - if (tmp < minValue) throw new ArgumentOutOfRangeException("Keyword '" + key + "' has a minimum value of " + minValue); - if (tmp > maxValue) throw new ArgumentOutOfRangeException("Keyword '" + key + "' has a maximum value of " + maxValue); - return tmp; - } - - internal static bool ParseBoolean(string key, string value) - { - bool tmp; - if (!Format.TryParseBoolean(value, out tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a boolean value"); - return tmp; - } - internal static Version ParseVersion(string key, string value) - { - Version tmp; - if (!System.Version.TryParse(value, out tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a version value"); - return tmp; - } - internal static Proxy ParseProxy(string key, string value) - { - Proxy tmp; - if (!Enum.TryParse(value, true, out tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a proxy value"); - return tmp; - } - - internal static SslProtocols ParseSslProtocols(string key, string value) - { - SslProtocols tmp; - //Flags expect commas as separators, but we need to use '|' since commas are already used in the connection string to mean something else - value = value?.Replace("|", ","); - - if (!Enum.TryParse(value, true, out tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires an SslProtocol value (multiple values separated by '|')."); - - return tmp; - } - - internal static void Unknown(string key) - { - throw new ArgumentException("Keyword '" + key + "' is not supported"); - } - - internal const string AllowAdmin = "allowAdmin", SyncTimeout = "syncTimeout", - ServiceName = "serviceName", ClientName = "name", KeepAlive = "keepAlive", - Version = "version", ConnectTimeout = "connectTimeout", Password = "password", - TieBreaker = "tiebreaker", WriteBuffer = "writeBuffer", Ssl = "ssl", SslHost = "sslHost", HighPrioritySocketThreads = "highPriorityThreads", - ConfigChannel = "configChannel", AbortOnConnectFail = "abortConnect", ResolveDns = "resolveDns", - ChannelPrefix = "channelPrefix", Proxy = "proxy", ConnectRetry = "connectRetry", - ConfigCheckSeconds = "configCheckSeconds", ResponseTimeout = "responseTimeout", DefaultDatabase = "defaultDatabase"; - internal const string SslProtocols = "sslProtocols"; - - private static readonly Dictionary normalizedOptions = new[] - { - AllowAdmin, SyncTimeout, - ServiceName, ClientName, KeepAlive, - Version, ConnectTimeout, Password, - TieBreaker, WriteBuffer, Ssl, SslHost, HighPrioritySocketThreads, - ConfigChannel, AbortOnConnectFail, ResolveDns, - ChannelPrefix, Proxy, ConnectRetry, - ConfigCheckSeconds, DefaultDatabase, - SslProtocols, - }.ToDictionary(x => x, StringComparer.OrdinalIgnoreCase); - - public static string TryNormalize(string value) - { - string tmp; - if(value != null && normalizedOptions.TryGetValue(value, out tmp)) - { - return tmp ?? ""; - } - return value ?? ""; - } - } - - - private readonly EndPointCollection endpoints = new EndPointCollection(); - - private bool? allowAdmin, abortOnConnectFail, highPrioritySocketThreads, resolveDns, ssl; - - private string clientName, serviceName, password, tieBreaker, sslHost, configChannel; - - private CommandMap commandMap; - - private Version defaultVersion; - - private int? keepAlive, syncTimeout, connectTimeout, responseTimeout, writeBuffer, connectRetry, configCheckSeconds, defaultDatabase; - - private Proxy? proxy; - - private IReconnectRetryPolicy reconnectRetryPolicy; - - /// - /// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication; note - /// that this cannot be specified in the configuration-string. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")] - public event LocalCertificateSelectionCallback CertificateSelection; - - /// - /// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party; note - /// that this cannot be specified in the configuration-string. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")] - public event RemoteCertificateValidationCallback CertificateValidation; - - /// - /// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException - /// - public bool AbortOnConnectFail { get { return abortOnConnectFail ?? GetDefaultAbortOnConnectFailSetting(); } set { abortOnConnectFail = value; } } - - /// - /// Indicates whether admin operations should be allowed - /// - public bool AllowAdmin { get { return allowAdmin.GetValueOrDefault(); } set { allowAdmin = value; } } - - /// - /// Indicates whether the connection should be encrypted - /// - [Obsolete("Please use .Ssl instead of .UseSsl"), -#if !CORE_CLR - Browsable(false), -#endif - EditorBrowsable(EditorBrowsableState.Never)] - public bool UseSsl { get { return Ssl; } set { Ssl = value; } } - - /// - /// Indicates whether the connection should be encrypted - /// - public bool Ssl { get { return ssl.GetValueOrDefault(); } set { ssl = value; } } - - /// - /// Configures which Ssl/TLS protocols should be allowed. If not set, defaults are chosen by the .NET framework. - /// - public SslProtocols? SslProtocols { get; set; } - - /// - /// Automatically encodes and decodes channels - /// - public RedisChannel ChannelPrefix { get;set; } - /// - /// The client name to use for all connections - /// - public string ClientName { get { return clientName; } set { clientName = value; } } - - /// - /// The number of times to repeat the initial connect cycle if no servers respond promptly - /// - public int ConnectRetry { get { return connectRetry ?? 3; } set { connectRetry = value; } } - - /// - /// The command-map associated with this configuration - /// - public CommandMap CommandMap - { - get - { - if (commandMap != null) return commandMap; - switch (Proxy) - { - case Proxy.Twemproxy: - return CommandMap.Twemproxy; - default: - return CommandMap.Default; - } - } - set - { - if (value == null) throw new ArgumentNullException(nameof(value)); - commandMap = value; - } - } - - /// - /// Channel to use for broadcasting and listening for configuration change notification - /// - public string ConfigurationChannel { get { return configChannel ?? DefaultConfigurationChannel; } set { configChannel = value; } } - - /// - /// Specifies the time in milliseconds that should be allowed for connection (defaults to 5 seconds unless SyncTimeout is higher) - /// - public int ConnectTimeout { - get { - if (connectTimeout.HasValue) return connectTimeout.GetValueOrDefault(); - return Math.Max(5000, SyncTimeout); - } - set { connectTimeout = value; } - } - - /// - /// The retry policy to be used for connection reconnects - /// - public IReconnectRetryPolicy ReconnectRetryPolicy { get { return reconnectRetryPolicy ?? (reconnectRetryPolicy = new LinearRetry(ConnectTimeout)); } set { reconnectRetryPolicy = value; } } - - - /// - /// The server version to assume - /// - public Version DefaultVersion { get { return defaultVersion ?? (IsAzureEndpoint() ? RedisFeatures.v3_0_0 : RedisFeatures.v2_0_0); } set { defaultVersion = value; } } - - /// - /// The endpoints defined for this configuration - /// - public EndPointCollection EndPoints => endpoints; - - /// - /// Use ThreadPriority.AboveNormal for SocketManager reader and writer threads (true by default). If false, ThreadPriority.Normal will be used. - /// - public bool HighPrioritySocketThreads { get { return highPrioritySocketThreads ?? true; } set { highPrioritySocketThreads = value; } } - - /// - /// Specifies the time in seconds at which connections should be pinged to ensure validity - /// - public int KeepAlive { get { return keepAlive.GetValueOrDefault(-1); } set { keepAlive = value; } } - - /// - /// The password to use to authenticate with the server - /// - public string Password { get { return password; } set { password = value; } } - - /// - /// Type of proxy to use (if any); for example Proxy.Twemproxy - /// - public Proxy Proxy { get { return proxy.GetValueOrDefault(); } set { proxy = value; } } - - /// - /// Indicates whether endpoints should be resolved via DNS before connecting. - /// If enabled the ConnectionMultiplexer will not re-resolve DNS - /// when attempting to re-connect after a connection failure. - /// - public bool ResolveDns { get { return resolveDns.GetValueOrDefault(); } set { resolveDns = value; } } - - /// - /// The service name used to resolve a service via sentinel - /// - public string ServiceName { get { return serviceName; } set { serviceName = value; } } - - /// - /// Gets or sets the SocketManager instance to be used with these options; if this is null a per-multiplexer - /// SocketManager is created automatically. - /// - public SocketManager SocketManager { get;set; } - /// - /// The target-host to use when validating SSL certificate; setting a value here enables SSL mode - /// - public string SslHost { get { return sslHost ?? InferSslHostFromEndpoints(); } set { sslHost = value; } } - - /// - /// Specifies the time in milliseconds that the system should allow for synchronous operations (defaults to 1 second) - /// - public int SyncTimeout { get { return syncTimeout.GetValueOrDefault(1000); } set { syncTimeout = value; } } - - /// - /// Specifies the time in milliseconds that the system should allow for responses before concluding that the socket is unhealthy - /// (defaults to SyncTimeout) - /// - public int ResponseTimeout { get { return responseTimeout ?? SyncTimeout; } set { responseTimeout = value; } } - - /// - /// Tie-breaker used to choose between masters (must match the endpoint exactly) - /// - public string TieBreaker { get { return tieBreaker ?? DefaultTieBreaker; } set { tieBreaker = value; } } - /// - /// The size of the output buffer to use - /// - public int WriteBuffer { get { return writeBuffer.GetValueOrDefault(4096); } set { writeBuffer = value; } } - - /// - /// Specifies the default database to be used when calling ConnectionMultiplexer.GetDatabase() without any parameters - /// - public int? DefaultDatabase { get { return defaultDatabase; } set { defaultDatabase = value; } } - - internal LocalCertificateSelectionCallback CertificateSelectionCallback { get { return CertificateSelection; } private set { CertificateSelection = value; } } - - // these just rip out the underlying handlers, bypassing the event accessors - needed when creating the SSL stream - internal RemoteCertificateValidationCallback CertificateValidationCallback { get { return CertificateValidation; } private set { CertificateValidation = value; } } - - /// - /// Check configuration every n seconds (every minute by default) - /// - public int ConfigCheckSeconds { get { return configCheckSeconds.GetValueOrDefault(60); } set { configCheckSeconds = value; } } - - /// - /// Parse the configuration from a comma-delimited configuration string - /// - /// is null. - /// is empty. - public static ConfigurationOptions Parse(string configuration) - { - var options = new ConfigurationOptions(); - options.DoParse(configuration, false); - return options; - } - /// - /// Parse the configuration from a comma-delimited configuration string - /// - /// is null. - /// is empty. - public static ConfigurationOptions Parse(string configuration, bool ignoreUnknown) - { - var options = new ConfigurationOptions(); - options.DoParse(configuration, ignoreUnknown); - return options; - } - - /// - /// Create a copy of the configuration - /// - public ConfigurationOptions Clone() - { - var options = new ConfigurationOptions - { - clientName = clientName, - serviceName = serviceName, - keepAlive = keepAlive, - syncTimeout = syncTimeout, - allowAdmin = allowAdmin, - defaultVersion = defaultVersion, - connectTimeout = connectTimeout, - password = password, - tieBreaker = tieBreaker, - writeBuffer = writeBuffer, - ssl = ssl, - sslHost = sslHost, - highPrioritySocketThreads = highPrioritySocketThreads, - configChannel = configChannel, - abortOnConnectFail = abortOnConnectFail, - resolveDns = resolveDns, - proxy = proxy, - commandMap = commandMap, - CertificateValidationCallback = CertificateValidationCallback, - CertificateSelectionCallback = CertificateSelectionCallback, - ChannelPrefix = ChannelPrefix.Clone(), - SocketManager = SocketManager, - connectRetry = connectRetry, - configCheckSeconds = configCheckSeconds, - responseTimeout = responseTimeout, - defaultDatabase = defaultDatabase, - ReconnectRetryPolicy = reconnectRetryPolicy, -#if !CORE_CLR - SslProtocols = SslProtocols, -#endif - }; - foreach (var item in endpoints) - options.endpoints.Add(item); - return options; - - } - - /// - /// Resolve the default port for any endpoints that did not have a port explicitly specified - /// - public void SetDefaultPorts() - { - endpoints.SetDefaultPorts(Ssl ? 6380 : 6379); - } - - /// - /// Returns the effective configuration string for this configuration, including Redis credentials. - /// - public override string ToString() - { - // include password to allow generation of configuration strings - // used for connecting multiplexer - return ToString(includePassword: true); - } - - /// - /// Returns the effective configuration string for this configuration - /// with the option to include or exclude the password from the string. - /// - public string ToString(bool includePassword) - { - var sb = new StringBuilder(); - foreach (var endpoint in endpoints) - { - Append(sb, Format.ToString(endpoint)); - } - Append(sb, OptionKeys.ClientName, clientName); - Append(sb, OptionKeys.ServiceName, serviceName); - Append(sb, OptionKeys.KeepAlive, keepAlive); - Append(sb, OptionKeys.SyncTimeout, syncTimeout); - Append(sb, OptionKeys.AllowAdmin, allowAdmin); - Append(sb, OptionKeys.Version, defaultVersion); - Append(sb, OptionKeys.ConnectTimeout, connectTimeout); - Append(sb, OptionKeys.Password, includePassword ? password : "*****"); - Append(sb, OptionKeys.TieBreaker, tieBreaker); - Append(sb, OptionKeys.WriteBuffer, writeBuffer); - Append(sb, OptionKeys.Ssl, ssl); - Append(sb, OptionKeys.SslHost, sslHost); - Append(sb, OptionKeys.HighPrioritySocketThreads, highPrioritySocketThreads); - Append(sb, OptionKeys.ConfigChannel, configChannel); - Append(sb, OptionKeys.AbortOnConnectFail, abortOnConnectFail); - Append(sb, OptionKeys.ResolveDns, resolveDns); - Append(sb, OptionKeys.ChannelPrefix, (string)ChannelPrefix); - Append(sb, OptionKeys.ConnectRetry, connectRetry); - Append(sb, OptionKeys.Proxy, proxy); - Append(sb, OptionKeys.ConfigCheckSeconds, configCheckSeconds); - Append(sb, OptionKeys.ResponseTimeout, responseTimeout); - Append(sb, OptionKeys.DefaultDatabase, defaultDatabase); - commandMap?.AppendDeltas(sb); - return sb.ToString(); - } - - internal bool HasDnsEndPoints() - { - foreach (var endpoint in endpoints) if (endpoint is DnsEndPoint) return true; - return false; - } - -#pragma warning disable 1998 // NET40 is sync, not async, currently - internal async Task ResolveEndPointsAsync(ConnectionMultiplexer multiplexer, TextWriter log) - { - Dictionary cache = new Dictionary(StringComparer.OrdinalIgnoreCase); - for (int i = 0; i < endpoints.Count; i++) - { - var dns = endpoints[i] as DnsEndPoint; - if (dns != null) - { - try - { - IPAddress ip; - if (dns.Host == ".") - { - endpoints[i] = new IPEndPoint(IPAddress.Loopback, dns.Port); - } - else if (cache.TryGetValue(dns.Host, out ip)) - { // use cache - endpoints[i] = new IPEndPoint(ip, dns.Port); - } - else - { - multiplexer.LogLocked(log, "Using DNS to resolve '{0}'...", dns.Host); -#if NET40 - var ips = Dns.GetHostAddresses(dns.Host); -#else - var ips = await Dns.GetHostAddressesAsync(dns.Host).ObserveErrors().ForAwait(); -#endif - if (ips.Length == 1) - { - ip = ips[0]; - multiplexer.LogLocked(log, "'{0}' => {1}", dns.Host, ip); - cache[dns.Host] = ip; - endpoints[i] = new IPEndPoint(ip, dns.Port); - } - } - } - catch (Exception ex) - { - multiplexer.OnInternalError(ex); - multiplexer.LogLocked(log, ex.Message); - } - } - } - } -#pragma warning restore 1998 - static void Append(StringBuilder sb, object value) - { - if (value == null) return; - string s = Format.ToString(value); - if (!string.IsNullOrWhiteSpace(s)) - { - if (sb.Length != 0) sb.Append(','); - sb.Append(s); - } - } - - static void Append(StringBuilder sb, string prefix, object value) - { - string s = value?.ToString(); - if (!string.IsNullOrWhiteSpace(s)) - { - if (sb.Length != 0) sb.Append(','); - if(!string.IsNullOrEmpty(prefix)) - { - sb.Append(prefix).Append('='); - } - sb.Append(s); - } - } - -#if !CORE_CLR - static bool IsOption(string option, string prefix) - { - return option.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase); - } -#endif - - void Clear() - { - clientName = serviceName = password = tieBreaker = sslHost = configChannel = null; - keepAlive = syncTimeout = connectTimeout = writeBuffer = connectRetry = configCheckSeconds = defaultDatabase = null; - allowAdmin = abortOnConnectFail = highPrioritySocketThreads = resolveDns = ssl = null; - defaultVersion = null; - endpoints.Clear(); - commandMap = null; - - CertificateSelection = null; - CertificateValidation = null; - ChannelPrefix = default(RedisChannel); - SocketManager = null; - } - -#if !CORE_CLR - object ICloneable.Clone() { return Clone(); } -#endif - - private void DoParse(string configuration, bool ignoreUnknown) - { - if (configuration == null) - { - throw new ArgumentNullException(nameof(configuration)); - } - - if (string.IsNullOrWhiteSpace(configuration)) - { - throw new ArgumentException("is empty", configuration); - } - - Clear(); - - // break it down by commas - var arr = configuration.Split(StringSplits.Comma); - Dictionary map = null; - foreach (var paddedOption in arr) - { - var option = paddedOption.Trim(); - - if (string.IsNullOrWhiteSpace(option)) continue; - - // check for special tokens - int idx = option.IndexOf('='); - if (idx > 0) - { - var key = option.Substring(0, idx).Trim(); - var value = option.Substring(idx + 1).Trim(); - - switch (OptionKeys.TryNormalize(key)) - { - case OptionKeys.SyncTimeout: - SyncTimeout = OptionKeys.ParseInt32(key, value, minValue: 1); - break; - case OptionKeys.AllowAdmin: - AllowAdmin = OptionKeys.ParseBoolean(key, value); - break; - case OptionKeys.AbortOnConnectFail: - AbortOnConnectFail = OptionKeys.ParseBoolean(key, value); - break; - case OptionKeys.ResolveDns: - ResolveDns = OptionKeys.ParseBoolean(key, value); - break; - case OptionKeys.ServiceName: - ServiceName = value; - break; - case OptionKeys.ClientName: - ClientName = value; - break; - case OptionKeys.ChannelPrefix: - ChannelPrefix = value; - break; - case OptionKeys.ConfigChannel: - ConfigurationChannel = value; - break; - case OptionKeys.KeepAlive: - KeepAlive = OptionKeys.ParseInt32(key, value); - break; - case OptionKeys.ConnectTimeout: - ConnectTimeout = OptionKeys.ParseInt32(key, value); - break; - case OptionKeys.ConnectRetry: - ConnectRetry = OptionKeys.ParseInt32(key, value); - break; - case OptionKeys.ConfigCheckSeconds: - ConfigCheckSeconds = OptionKeys.ParseInt32(key, value); - break; - case OptionKeys.Version: - DefaultVersion = OptionKeys.ParseVersion(key, value); - break; - case OptionKeys.Password: - Password = value; - break; - case OptionKeys.TieBreaker: - TieBreaker = value; - break; - case OptionKeys.Ssl: - Ssl = OptionKeys.ParseBoolean(key, value); - break; - case OptionKeys.SslHost: - SslHost = value; - break; - case OptionKeys.HighPrioritySocketThreads: - HighPrioritySocketThreads = OptionKeys.ParseBoolean(key, value); - break; - case OptionKeys.WriteBuffer: - WriteBuffer = OptionKeys.ParseInt32(key, value); - break; - case OptionKeys.Proxy: - Proxy = OptionKeys.ParseProxy(key, value); - break; - case OptionKeys.ResponseTimeout: - ResponseTimeout = OptionKeys.ParseInt32(key, value, minValue: 1); - break; - case OptionKeys.DefaultDatabase: - defaultDatabase = OptionKeys.ParseInt32(key, value); - break; -#if !CORE_CLR - case OptionKeys.SslProtocols: - SslProtocols = OptionKeys.ParseSslProtocols(key, value); - break; -#endif - default: - if (!string.IsNullOrEmpty(key) && key[0] == '$') - { - RedisCommand cmd; - var cmdName = option.Substring(1, idx - 1); - if (Enum.TryParse(cmdName, true, out cmd)) - { - if (map == null) map = new Dictionary(StringComparer.OrdinalIgnoreCase); - map[cmdName] = value; - } - } - else - { - if(!ignoreUnknown) OptionKeys.Unknown(key); - } - break; - } - } - else - { - var ep = Format.TryParseEndPoint(option); - if (ep != null && !endpoints.Contains(ep)) endpoints.Add(ep); - } - } - if (map != null && map.Count != 0) - { - CommandMap = CommandMap.Create(map); - } - } - - private bool GetDefaultAbortOnConnectFailSetting() - { - // Microsoft Azure team wants abortConnect=false by default - if (IsAzureEndpoint()) - return false; - - return true; - } - - private bool IsAzureEndpoint() - { - var result = false; - var dnsEndpoints = endpoints.Select(endpoint => endpoint as DnsEndPoint).Where(ep => ep != null); - foreach(var ep in dnsEndpoints) - { - int firstDot = ep.Host.IndexOf('.'); - if (firstDot >= 0) - { - var domain = ep.Host.Substring(firstDot).ToLowerInvariant(); - switch(domain) - { - case ".redis.cache.windows.net": - case ".redis.cache.chinacloudapi.cn": - case ".redis.cache.usgovcloudapi.net": - case ".redis.cache.cloudapi.de": - return true; - } - } - } - - return result; - } - - private string InferSslHostFromEndpoints() { - var dnsEndpoints = endpoints.Select(endpoint => endpoint as DnsEndPoint); - string dnsHost = dnsEndpoints.FirstOrDefault()?.Host; - if (dnsEndpoints.All(dnsEndpoint => (dnsEndpoint != null && dnsEndpoint.Host == dnsHost))) { - return dnsHost; - } - - return null; - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ConnectionFailedEventArgs.cs b/StackExchange.Redis/StackExchange/Redis/ConnectionFailedEventArgs.cs deleted file mode 100644 index b575549c6..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ConnectionFailedEventArgs.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Net; -using System.Text; - -namespace StackExchange.Redis -{ - /// - /// Contains information about a server connection failure - /// - public sealed class ConnectionFailedEventArgs : EventArgs, ICompletable - { - private readonly EventHandler handler; - private readonly object sender; - internal ConnectionFailedEventArgs(EventHandler handler, object sender, EndPoint endPoint, ConnectionType connectionType, ConnectionFailureType failureType, Exception exception) - { - this.handler = handler; - this.sender = sender; - EndPoint = endPoint; - ConnectionType = connectionType; - Exception = exception; - FailureType = failureType; - } - - /// - /// Gets the connection-type of the failing connection - /// - public ConnectionType ConnectionType { get; } - - /// - /// Gets the failing server-endpoint - /// - public EndPoint EndPoint { get; } - - /// - /// Gets the exception if available (this can be null) - /// - public Exception Exception { get; } - - /// - /// The type of failure - /// - public ConnectionFailureType FailureType { get; } - - void ICompletable.AppendStormLog(StringBuilder sb) - { - sb.Append("event, connection-failed: "); - if (EndPoint == null) sb.Append("n/a"); - else sb.Append(Format.ToString(EndPoint)); - } - - bool ICompletable.TryComplete(bool isAsync) - { - return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ConnectionFailureType.cs b/StackExchange.Redis/StackExchange/Redis/ConnectionFailureType.cs deleted file mode 100644 index 9213de8b0..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ConnectionFailureType.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace StackExchange.Redis -{ - /// - /// The known types of connection failure - /// - public enum ConnectionFailureType - { - /// - /// This event is not a failure - /// - None, - /// - /// No viable connections were available for this operation - /// - UnableToResolvePhysicalConnection, - /// - /// The socket for this connection failed - /// - SocketFailure, - /// - /// Either SSL Stream or Redis authentication failed - /// - AuthenticationFailure, - /// - /// An unexpected response was received from the server - /// - ProtocolFailure, - /// - /// An unknown internal error occurred - /// - InternalFailure, - /// - /// The socket was closed - /// - SocketClosed, - /// - /// The socket was closed - /// - ConnectionDisposed, - /// - /// The database is loading and is not available for use - /// - Loading, - /// - /// It has not been possible to create an intial connection to the redis server(s) - /// - UnableToConnect - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ConnectionMultiplexer.Profiling.cs b/StackExchange.Redis/StackExchange/Redis/ConnectionMultiplexer.Profiling.cs deleted file mode 100644 index 199f598c1..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ConnectionMultiplexer.Profiling.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; - -namespace StackExchange.Redis -{ - partial class ConnectionMultiplexer - { - private IProfiler profiler; - - // internal for test purposes - internal ProfileContextTracker profiledCommands; - - /// - /// Sets an IProfiler instance for this ConnectionMultiplexer. - /// - /// An IProfiler instances is used to determine which context to associate an - /// IProfiledCommand with. See BeginProfiling(object) and FinishProfiling(object) - /// for more details. - /// - public void RegisterProfiler(IProfiler profiler) - { - if (profiler == null) throw new ArgumentNullException(nameof(profiler)); - if (this.profiler != null) throw new InvalidOperationException("IProfiler already registered for this ConnectionMultiplexer"); - - this.profiler = profiler; - this.profiledCommands = new ProfileContextTracker(); - } - - /// - /// Begins profiling for the given context. - /// - /// If the same context object is returned by the registered IProfiler, the IProfiledCommands - /// will be associated with each other. - /// - /// Call FinishProfiling with the same context to get the assocated commands. - /// - /// Note that forContext cannot be a WeakReference or a WeakReference<T> - /// - public void BeginProfiling(object forContext) - { - if (profiler == null) throw new InvalidOperationException("Cannot begin profiling if no IProfiler has been registered with RegisterProfiler"); - if (forContext == null) throw new ArgumentNullException(nameof(forContext)); - if (forContext is WeakReference) throw new ArgumentException("Context object cannot be a WeakReference", nameof(forContext)); - - if (!profiledCommands.TryCreate(forContext)) - { - throw ExceptionFactory.BeganProfilingWithDuplicateContext(forContext); - } - } - - /// - /// Stops profiling for the given context, returns all IProfiledCommands associated. - /// - /// By default this may do a sweep for dead profiling contexts, you can disable this by passing "allowCleanupSweep: false". - /// - public ProfiledCommandEnumerable FinishProfiling(object forContext, bool allowCleanupSweep = true) - { - if (profiler == null) throw new InvalidOperationException("Cannot begin profiling if no IProfiler has been registered with RegisterProfiler"); - if (forContext == null) throw new ArgumentNullException(nameof(forContext)); - - ProfiledCommandEnumerable ret; - if (!profiledCommands.TryRemove(forContext, out ret)) - { - throw ExceptionFactory.FinishedProfilingWithInvalidContext(forContext); - } - - // conditional, because it could hurt and that may sometimes be unacceptable - if (allowCleanupSweep) - { - profiledCommands.TryCleanup(); - } - - return ret; - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ConnectionMultiplexer.ReaderWriter.cs b/StackExchange.Redis/StackExchange/Redis/ConnectionMultiplexer.ReaderWriter.cs deleted file mode 100644 index 224a503f4..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ConnectionMultiplexer.ReaderWriter.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace StackExchange.Redis -{ - partial class ConnectionMultiplexer - { - internal SocketManager SocketManager => socketManager; - - private SocketManager socketManager; - private bool ownsSocketManager; - - partial void OnCreateReaderWriter(ConfigurationOptions configuration) - { - ownsSocketManager = configuration.SocketManager == null; - socketManager = configuration.SocketManager ?? new SocketManager(ClientName, configuration.HighPrioritySocketThreads); - } - - partial void OnCloseReaderWriter() - { - if (ownsSocketManager) socketManager?.Dispose(); - socketManager = null; - } - - internal void RequestWrite(PhysicalBridge bridge, bool forced) - { - if (bridge == null) return; - var tmp = SocketManager; - if (tmp != null) - { - Trace("Requesting write: " + bridge.Name); - tmp.RequestWrite(bridge, forced); - } - } - partial void OnWriterCreated(); - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ConnectionMultiplexer.cs b/StackExchange.Redis/StackExchange/Redis/ConnectionMultiplexer.cs deleted file mode 100644 index e194b4306..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ConnectionMultiplexer.cs +++ /dev/null @@ -1,2245 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Reflection; -#if NET40 -using Microsoft.Runtime.CompilerServices; -#else -using System.IO.Compression; -using System.Runtime.CompilerServices; -#endif - -namespace StackExchange.Redis -{ - internal static partial class TaskExtensions - { - private static readonly Action observeErrors = ObverveErrors; - private static void ObverveErrors(this Task task) - { - if (task != null) GC.KeepAlive(task.Exception); - } - - public static Task ObserveErrors(this Task task) - { - task?.ContinueWith(observeErrors, TaskContinuationOptions.OnlyOnFaulted); - return task; - } - public static Task ObserveErrors(this Task task) - { - task?.ContinueWith(observeErrors, TaskContinuationOptions.OnlyOnFaulted); - return task; - } - - public static ConfiguredTaskAwaitable ForAwait(this Task task) - { - return task.ConfigureAwait(false); - } - public static ConfiguredTaskAwaitable ForAwait(this Task task) - { - return task.ConfigureAwait(false); - } - } - - /// - /// Represents an inter-related group of connections to redis servers - /// - public sealed partial class ConnectionMultiplexer : IConnectionMultiplexer, IDisposable - { - private static readonly string timeoutHelpLink = "http://stackexchange.github.io/StackExchange.Redis/Timeouts"; - - private static TaskFactory _factory = null; - - /// - /// Provides a way of overriding the default Task Factory. If not set, it will use the default Task.Factory. - /// Useful when top level code sets it's own factory which may interfere with Redis queries. - /// - public static TaskFactory Factory - { - get - { - return _factory ?? Task.Factory; - } - set - { - _factory = value; - - } - } - - /// - /// Get summary statistics associates with this server - /// - public ServerCounters GetCounters() - { - var snapshot = serverSnapshot; - - var counters = new ServerCounters(null); - for (int i = 0; i < snapshot.Length; i++) - { - counters.Add(snapshot[i].GetCounters()); - } - unprocessableCompletionManager.GetCounters(counters.Other); - return counters; - } - - /// - /// Gets the client-name that will be used on all new connections - /// - public string ClientName => configuration.ClientName ?? ConnectionMultiplexer.GetDefaultClientName(); - - private static string defaultClientName; - private static string GetDefaultClientName() - { - if (defaultClientName == null) - { - defaultClientName = TryGetAzureRoleInstanceIdNoThrow() ?? Environment.GetEnvironmentVariable("ComputerName"); - } - return defaultClientName; - } - - /// Tries to get the Roleinstance Id if Microsoft.WindowsAzure.ServiceRuntime is loaded. - /// In case of any failure, swallows the exception and returns null - internal static string TryGetAzureRoleInstanceIdNoThrow() - { - string roleInstanceId = null; - // TODO: CoreCLR port pending https://github.com/dotnet/coreclr/issues/919 -#if !CORE_CLR - try - { - Assembly asm = null; - foreach (var asmb in AppDomain.CurrentDomain.GetAssemblies()) - { - if (asmb.GetName().Name.Equals("Microsoft.WindowsAzure.ServiceRuntime")) - { - asm = asmb; - break; - } - } - if (asm == null) - return null; - - var type = asm.GetType("Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment"); - - // https://msdn.microsoft.com/en-us/library/microsoft.windowsazure.serviceruntime.roleenvironment.isavailable.aspx if (!(bool)type.GetProperty("IsAvailable").GetValue(null, null)) return null; - - var currentRoleInstanceProp = type.GetProperty("CurrentRoleInstance"); - var currentRoleInstanceId = currentRoleInstanceProp.GetValue(null, null); - roleInstanceId = currentRoleInstanceId.GetType().GetProperty("Id").GetValue(currentRoleInstanceId, null).ToString(); - - if (String.IsNullOrEmpty(roleInstanceId)) - { - roleInstanceId = null; - } - } - catch (Exception) - { - //silently ignores the exception - roleInstanceId = null; - } -#endif - return roleInstanceId; - } - - /// - /// Gets the configuration of the connection - /// - public string Configuration => configuration.ToString(); - - internal void OnConnectionFailed(EndPoint endpoint, ConnectionType connectionType, ConnectionFailureType failureType, Exception exception, bool reconfigure) - { - if (isDisposed) return; - var handler = ConnectionFailed; - if (handler != null) - { - unprocessableCompletionManager.CompleteSyncOrAsync( - new ConnectionFailedEventArgs(handler, this, endpoint, connectionType, failureType, exception) - ); - } - if (reconfigure) - { - ReconfigureIfNeeded(endpoint, false, "connection failed"); - } - } - internal void OnInternalError(Exception exception, EndPoint endpoint = null, ConnectionType connectionType = ConnectionType.None, [System.Runtime.CompilerServices.CallerMemberName] string origin = null) - { - try - { - Trace("Internal error: " + origin + ", " + exception == null ? "unknown" : exception.Message); - if (isDisposed) return; - var handler = InternalError; - if (handler != null) - { - unprocessableCompletionManager.CompleteSyncOrAsync( - new InternalErrorEventArgs(handler, this, endpoint, connectionType, exception, origin) - ); - } - } - catch - { // our internal error event failed; whatcha gonna do, exactly? - } - } - - internal void OnConnectionRestored(EndPoint endpoint, ConnectionType connectionType) - { - if (isDisposed) return; - var handler = ConnectionRestored; - if (handler != null) - { - unprocessableCompletionManager.CompleteSyncOrAsync( - new ConnectionFailedEventArgs(handler, this, endpoint, connectionType, ConnectionFailureType.None, null) - ); - } - ReconfigureIfNeeded(endpoint, false, "connection restored"); - } - - - private void OnEndpointChanged(EndPoint endpoint, EventHandler handler) - { - if (isDisposed) return; - if (handler != null) - { - unprocessableCompletionManager.CompleteSyncOrAsync( - new EndPointEventArgs(handler, this, endpoint) - ); - } - } - internal void OnConfigurationChanged(EndPoint endpoint) - { - OnEndpointChanged(endpoint, ConfigurationChanged); - } - internal void OnConfigurationChangedBroadcast(EndPoint endpoint) - { - OnEndpointChanged(endpoint, ConfigurationChangedBroadcast); - } - - /// - /// A server replied with an error message; - /// - public event EventHandler ErrorMessage; - internal void OnErrorMessage(EndPoint endpoint, string message) - { - if (isDisposed) return; - var handler = ErrorMessage; - if (handler != null) - { - unprocessableCompletionManager.CompleteSyncOrAsync( - new RedisErrorEventArgs(handler, this, endpoint, message) - ); - } - } - -#if !NET40 - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] - static void Write(ZipArchive zip, string name, Task task, Action callback) - { - var entry = zip.CreateEntry(name, -#if __MonoCS__ - CompressionLevel.Fastest -#else - CompressionLevel.Optimal -#endif - ); - using (var stream = entry.Open()) - using (var writer = new StreamWriter(stream)) - { - TaskStatus status = task.Status; - switch (status) - { - case TaskStatus.RanToCompletion: - T val = ((Task)task).Result; - callback(val, writer); - break; - case TaskStatus.Faulted: - writer.WriteLine(string.Join(", ", task.Exception.InnerExceptions.Select(x => x.Message))); - break; - default: - writer.WriteLine(status.ToString()); - break; - } - } - } - /// - /// Write the configuration of all servers to an output stream - /// - public void ExportConfiguration(Stream destination, ExportOptions options = ExportOptions.All) - { - if (destination == null) throw new ArgumentNullException(nameof(destination)); - - // what is possible, given the command map? - ExportOptions mask = 0; - if (CommandMap.IsAvailable(RedisCommand.INFO)) mask |= ExportOptions.Info; - if (CommandMap.IsAvailable(RedisCommand.CONFIG)) mask |= ExportOptions.Config; - if (CommandMap.IsAvailable(RedisCommand.CLIENT)) mask |= ExportOptions.Client; - if (CommandMap.IsAvailable(RedisCommand.CLUSTER)) mask |= ExportOptions.Cluster; - options &= mask; - - using (var zip = new ZipArchive(destination, ZipArchiveMode.Create, true)) - { - var arr = serverSnapshot; - foreach (var server in arr) - { - const CommandFlags flags = CommandFlags.None; - if (!server.IsConnected) continue; - var api = GetServer(server.EndPoint); - - List tasks = new List(); - if ((options & ExportOptions.Info) != 0) - { - tasks.Add(api.InfoRawAsync(flags: flags)); - } - if ((options & ExportOptions.Config) != 0) - { - tasks.Add(api.ConfigGetAsync(flags: flags)); - } - if ((options & ExportOptions.Client) != 0) - { - tasks.Add(api.ClientListAsync(flags: flags)); - } - if ((options & ExportOptions.Cluster) != 0) - { - tasks.Add(api.ClusterNodesRawAsync(flags: flags)); - } - - WaitAllIgnoreErrors(tasks.ToArray()); - - int index = 0; - var prefix = Format.ToString(server.EndPoint); - if ((options & ExportOptions.Info) != 0) - { - Write(zip, prefix + "/info.txt", tasks[index++], WriteNormalizingLineEndings); - } - if ((options & ExportOptions.Config) != 0) - { - Write[]>(zip, prefix + "/config.txt", tasks[index++], (settings, writer) => - { - foreach (var setting in settings) - { - writer.WriteLine("{0}={1}", setting.Key, setting.Value); - } - }); - } - if ((options & ExportOptions.Client) != 0) - { - Write(zip, prefix + "/clients.txt", tasks[index++], (clients, writer) => - { - foreach (var client in clients) - { - writer.WriteLine(client.Raw); - } - }); - } - if ((options & ExportOptions.Cluster) != 0) - { - Write(zip, prefix + "/nodes.txt", tasks[index++], WriteNormalizingLineEndings); - } - } - } - } -#endif - - internal void MakeMaster(ServerEndPoint server, ReplicationChangeOptions options, TextWriter log) - { - CommandMap.AssertAvailable(RedisCommand.SLAVEOF); - if (!configuration.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(IncludeDetailInExceptions, RedisCommand.SLAVEOF, null, server); - - if (server == null) throw new ArgumentNullException(nameof(server)); - var srv = new RedisServer(this, server, null); - if (!srv.IsConnected) throw ExceptionFactory.NoConnectionAvailable(IncludeDetailInExceptions, IncludePerformanceCountersInExceptions, RedisCommand.SLAVEOF, null, server, GetServerSnapshot()); - - if (log == null) log = TextWriter.Null; - CommandMap.AssertAvailable(RedisCommand.SLAVEOF); - - const CommandFlags flags = CommandFlags.NoRedirect | CommandFlags.HighPriority; - Message msg; - - LogLocked(log, "Checking {0} is available...", Format.ToString(srv.EndPoint)); - try - { - srv.Ping(flags); // if it isn't happy, we're not happy - } catch (Exception ex) - { - LogLocked(log, "Operation failed on {0}, aborting: {1}", Format.ToString(srv.EndPoint), ex.Message); - throw; - } - - var nodes = serverSnapshot; - RedisValue newMaster = Format.ToString(server.EndPoint); - - RedisKey tieBreakerKey = default(RedisKey); - // try and write this everywhere; don't worry if some folks reject our advances - if ((options & ReplicationChangeOptions.SetTiebreaker) != 0 && !string.IsNullOrWhiteSpace(configuration.TieBreaker) - && CommandMap.IsAvailable(RedisCommand.SET)) - { - tieBreakerKey = configuration.TieBreaker; - - foreach (var node in nodes) - { - if (!node.IsConnected) continue; - LogLocked(log, "Attempting to set tie-breaker on {0}...", Format.ToString(node.EndPoint)); - msg = Message.Create(0, flags, RedisCommand.SET, tieBreakerKey, newMaster); - node.QueueDirectFireAndForget(msg, ResultProcessor.DemandOK); - } - } - - // deslave... - LogLocked(log, "Making {0} a master...", Format.ToString(srv.EndPoint)); - try - { - srv.SlaveOf(null, flags); - } catch (Exception ex) - { - LogLocked(log, "Operation failed on {0}, aborting: {1}", Format.ToString(srv.EndPoint), ex.Message); - throw; - } - - // also, in case it was a slave a moment ago, and hasn't got the tie-breaker yet, we re-send the tie-breaker to this one - if (!tieBreakerKey.IsNull) - { - LogLocked(log, "Resending tie-breaker to {0}...", Format.ToString(server.EndPoint)); - msg = Message.Create(0, flags, RedisCommand.SET, tieBreakerKey, newMaster); - server.QueueDirectFireAndForget(msg, ResultProcessor.DemandOK); - } - - - - // try and broadcast this everywhere, to catch the maximum audience - if ((options & ReplicationChangeOptions.Broadcast) != 0 && ConfigurationChangedChannel != null - && CommandMap.IsAvailable(RedisCommand.PUBLISH)) - { - RedisValue channel = ConfigurationChangedChannel; - foreach (var node in nodes) - { - if (!node.IsConnected) continue; - LogLocked(log, "Broadcasting via {0}...", Format.ToString(node.EndPoint)); - msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, newMaster); - node.QueueDirectFireAndForget(msg, ResultProcessor.Int64); - } - } - - - if ((options & ReplicationChangeOptions.EnslaveSubordinates) != 0) - { - foreach (var node in nodes) - { - if (node == server || node.ServerType != ServerType.Standalone) continue; - - LogLocked(log, "Enslaving {0}...", Format.ToString(node.EndPoint)); - msg = RedisServer.CreateSlaveOfMessage(server.EndPoint, flags); - node.QueueDirectFireAndForget(msg, ResultProcessor.DemandOK); - } - } - - // and reconfigure the muxer - LogLocked(log, "Reconfiguring all endpoints..."); - if (!ReconfigureAsync(false, true, log, srv.EndPoint, "make master").ObserveErrors().Wait(5000)) - { - LogLocked(log, "Verifying the configuration was incomplete; please verify"); - } - } - - /// - /// Used internally to synchronize loggine without depending on locking the log instance - /// - private object LogSyncLock => UniqueId; - -// we know this has strong identity: readonly and unique to us - - internal void LogLocked(TextWriter log, string line) - { - if (log != null) lock (LogSyncLock) { log.WriteLine(line); } - } - internal void LogLocked(TextWriter log, string line, object arg) - { - if (log != null) lock (LogSyncLock) { log.WriteLine(line, arg); } - } - internal void LogLocked(TextWriter log, string line, object arg0, object arg1) - { - if (log != null) lock (LogSyncLock) { log.WriteLine(line, arg0, arg1); } - } - internal void LogLocked(TextWriter log, string line, object arg0, object arg1, object arg2) - { - if (log != null) lock (LogSyncLock) { log.WriteLine(line, arg0, arg1, arg2); } - } - internal void LogLocked(TextWriter log, string line, params object[] args) - { - if (log != null) lock (LogSyncLock) { log.WriteLine(line, args); } - } - - internal void CheckMessage(Message message) - { - if (!configuration.AllowAdmin && message.IsAdmin) - throw ExceptionFactory.AdminModeNotEnabled(IncludeDetailInExceptions, message.Command, message, null); - CommandMap.AssertAvailable(message.Command); - } - - static void WriteNormalizingLineEndings(string source, StreamWriter writer) - { - using (var reader = new StringReader(source)) - { - string line; - while ((line = reader.ReadLine()) != null) - writer.WriteLine(line); // normalize line endings - } - } - - /// - /// Raised whenever a physical connection fails - /// - public event EventHandler ConnectionFailed; - - /// - /// Raised whenever an internal error occurs (this is primarily for debugging) - /// - public event EventHandler InternalError; - - /// - /// Raised whenever a physical connection is established - /// - public event EventHandler ConnectionRestored; - - /// - /// Raised when configuration changes are detected - /// - public event EventHandler ConfigurationChanged; - - /// - /// Raised when nodes are explicitly requested to reconfigure via broadcast; - /// this usually means master/slave changes - /// - public event EventHandler ConfigurationChangedBroadcast; - - /// - /// Gets the timeout associated with the connections - /// - public int TimeoutMilliseconds => timeoutMilliseconds; - - /// - /// Gets all endpoints defined on the server - /// - /// - public EndPoint[] GetEndPoints(bool configuredOnly = false) - { - if (configuredOnly) return configuration.EndPoints.ToArray(); - - return ConvertHelper.ConvertAll(serverSnapshot, x => x.EndPoint); - } - - private readonly int timeoutMilliseconds; - - private readonly ConfigurationOptions configuration; - - - internal bool TryResend(int hashSlot, Message message, EndPoint endpoint, bool isMoved) - { - return serverSelectionStrategy.TryResend(hashSlot, message, endpoint, isMoved); - } - - - /// - /// Wait for a given asynchronous operation to complete (or timeout) - /// - public void Wait(Task task) - { - if (task == null) throw new ArgumentNullException(nameof(task)); - if (!task.Wait(timeoutMilliseconds)) throw new TimeoutException(); - } - - /// - /// Wait for a given asynchronous operation to complete (or timeout) - /// - - public T Wait(Task task) - { - if (task == null) throw new ArgumentNullException(nameof(task)); - if (!task.Wait(timeoutMilliseconds)) throw new TimeoutException(); - return task.Result; - } - /// - /// Wait for the given asynchronous operations to complete (or timeout) - /// - public void WaitAll(params Task[] tasks) - { - if (tasks == null) throw new ArgumentNullException(nameof(tasks)); - if (tasks.Length == 0) return; - if (!Task.WaitAll(tasks, timeoutMilliseconds)) throw new TimeoutException(); - } - - private bool WaitAllIgnoreErrors(Task[] tasks) - { - return WaitAllIgnoreErrors(tasks, timeoutMilliseconds); - } - private static bool WaitAllIgnoreErrors(Task[] tasks, int timeout) - { - if (tasks == null) throw new ArgumentNullException(nameof(tasks)); - if (tasks.Length == 0) return true; - var watch = Stopwatch.StartNew(); - try - { - // if none error, great - if (Task.WaitAll(tasks, timeout)) return true; - } - catch - { } - // if we get problems, need to give the non-failing ones time to finish - // to be fair and reasonable - for (int i = 0; i < tasks.Length; i++) - { - var task = tasks[i]; - if (!task.IsCanceled && !task.IsCompleted && !task.IsFaulted) - { - var remaining = timeout - checked((int)watch.ElapsedMilliseconds); - if (remaining <= 0) return false; - try - { - task.Wait(remaining); - } - catch - { } - } - } - return false; - } - -#if !CORE_CLR - private void LogLockedWithThreadPoolStats(TextWriter log, string message, out int busyWorkerCount) - { - busyWorkerCount = 0; - if(log != null) - { - var sb = new StringBuilder(); - sb.Append(message); - string iocp, worker; - busyWorkerCount = GetThreadPoolStats(out iocp, out worker); - sb.Append(", IOCP: ").Append(iocp).Append(", WORKER: ").Append(worker); - LogLocked(log, sb.ToString()); - } - } -#endif - - static bool AllComplete(Task[] tasks) - { - for(int i = 0 ; i < tasks.Length ; i++) - { - var task = tasks[i]; - if (!task.IsCanceled && !task.IsCompleted && !task.IsFaulted) - return false; - } - return true; - } - private async Task WaitAllIgnoreErrorsAsync(Task[] tasks, int timeoutMilliseconds, TextWriter log) - { - if (tasks == null) throw new ArgumentNullException(nameof(tasks)); - if (tasks.Length == 0) - { - LogLocked(log, "No tasks to await"); - return true; - } - - if (AllComplete(tasks)) - { - LogLocked(log, "All tasks are already complete"); - return true; - } - - var watch = Stopwatch.StartNew(); -#if !CORE_CLR - int busyWorkerCount; - LogLockedWithThreadPoolStats(log, "Awaiting task completion", out busyWorkerCount); -#endif - try - { - // if none error, great - var remaining = timeoutMilliseconds - checked((int)watch.ElapsedMilliseconds); - if (remaining <= 0) - { -#if !CORE_CLR - LogLockedWithThreadPoolStats(log, "Timeout before awaiting for tasks", out busyWorkerCount); -#endif - return false; - } - -#if NET40 - var allTasks = TaskEx.WhenAll(tasks).ObserveErrors(); - var any = TaskEx.WhenAny(allTasks, TaskEx.Delay(remaining)).ObserveErrors(); -#else - var allTasks = Task.WhenAll(tasks).ObserveErrors(); - var any = Task.WhenAny(allTasks, Task.Delay(remaining)).ObserveErrors(); -#endif - bool all = await any.ForAwait() == allTasks; -#if !CORE_CLR - LogLockedWithThreadPoolStats(log, all ? "All tasks completed cleanly" : "Not all tasks completed cleanly", out busyWorkerCount); -#endif - return all; - } - catch - { } - - // if we get problems, need to give the non-failing ones time to finish - // to be fair and reasonable - for (int i = 0; i < tasks.Length; i++) - { - var task = tasks[i]; - if (!task.IsCanceled && !task.IsCompleted && !task.IsFaulted) - { - var remaining = timeoutMilliseconds - checked((int)watch.ElapsedMilliseconds); - if (remaining <= 0) - { -#if !CORE_CLR - LogLockedWithThreadPoolStats(log, "Timeout awaiting tasks", out busyWorkerCount); -#endif - return false; - } - try - { -#if NET40 - var any = TaskEx.WhenAny(task, TaskEx.Delay(remaining)).ObserveErrors(); -#else - var any = Task.WhenAny(task, Task.Delay(remaining)).ObserveErrors(); -#endif - await any.ForAwait(); - } - catch - { } - } - } -#if !CORE_CLR - LogLockedWithThreadPoolStats(log, "Finished awaiting tasks", out busyWorkerCount); -#endif - return false; - } - - - /// - /// Raised when a hash-slot has been relocated - /// - public event EventHandler HashSlotMoved; - - internal void OnHashSlotMoved(int hashSlot, EndPoint old, EndPoint @new) - { - var handler = HashSlotMoved; - if (handler != null) - { - unprocessableCompletionManager.CompleteSyncOrAsync( - new HashSlotMovedEventArgs(handler, this, hashSlot, old, @new) - ); - } - } - - /// - /// Compute the hash-slot of a specified key - /// - public int HashSlot(RedisKey key) - { - return serverSelectionStrategy.HashSlot(key); - } - - internal ServerEndPoint AnyConnected(ServerType serverType, uint startOffset, RedisCommand command, CommandFlags flags) - { - var tmp = serverSnapshot; - int len = tmp.Length; - ServerEndPoint fallback = null; - for (int i = 0; i < len; i++) - { - var server = tmp[(int)(((uint)i + startOffset) % len)]; - if (server != null && server.ServerType == serverType && server.IsSelectable(command)) - { - if (server.IsSlave) - { - switch (flags) - { - case CommandFlags.DemandSlave: - case CommandFlags.PreferSlave: - return server; - case CommandFlags.PreferMaster: - fallback = server; - break; - } - } else - { - switch (flags) - { - case CommandFlags.DemandMaster: - case CommandFlags.PreferMaster: - return server; - case CommandFlags.PreferSlave: - fallback = server; - break; - } - } - } - } - return fallback; - } - - volatile bool isDisposed; - internal bool IsDisposed => isDisposed; - - /// - /// Create a new ConnectionMultiplexer instance - /// - public static async Task ConnectAsync(string configuration, TextWriter log = null) - { - IDisposable killMe = null; - try - { - var muxer = CreateMultiplexer(configuration); - killMe = muxer; - bool configured = await muxer.ReconfigureAsync(true, false, log, null, "connect").ObserveErrors().ForAwait(); - if (!configured) - { - throw ExceptionFactory.UnableToConnect(muxer.RawConfig.AbortOnConnectFail, muxer.failureMessage); - } - killMe = null; - return muxer; - } finally - { - if (killMe != null) try { killMe.Dispose(); } catch { } - } - } - - /// - /// Create a new ConnectionMultiplexer instance - /// - public static async Task ConnectAsync(ConfigurationOptions configuration, TextWriter log = null) - { - IDisposable killMe = null; - try - { - var muxer = CreateMultiplexer(configuration); - killMe = muxer; - bool configured = await muxer.ReconfigureAsync(true, false, log, null, "connect").ObserveErrors().ForAwait(); - if (!configured) - { - throw ExceptionFactory.UnableToConnect(muxer.RawConfig.AbortOnConnectFail, muxer.failureMessage); - } - killMe = null; - return muxer; - } finally - { - if (killMe != null) try { killMe.Dispose(); } catch { } - } - } - - static ConnectionMultiplexer CreateMultiplexer(object configuration) - { - if (configuration == null) throw new ArgumentNullException(nameof(configuration)); - ConfigurationOptions config; - if (configuration is string) - { - config = ConfigurationOptions.Parse((string)configuration); - } else if (configuration is ConfigurationOptions) - { - config = ((ConfigurationOptions)configuration).Clone(); - } else - { - throw new ArgumentException("configuration"); - } - if (config.EndPoints.Count == 0) throw new ArgumentException("No endpoints specified", nameof(configuration)); - config.SetDefaultPorts(); - return new ConnectionMultiplexer(config); - } - /// - /// Create a new ConnectionMultiplexer instance - /// - public static ConnectionMultiplexer Connect(string configuration, TextWriter log = null) - { - return ConnectImpl(() => CreateMultiplexer(configuration), log); - } - - /// - /// Create a new ConnectionMultiplexer instance - /// - public static ConnectionMultiplexer Connect(ConfigurationOptions configuration, TextWriter log = null) - { - return ConnectImpl(() => CreateMultiplexer(configuration), log); - } - - private static ConnectionMultiplexer ConnectImpl(Func multiplexerFactory, TextWriter log) - { - IDisposable killMe = null; - try - { - var muxer = multiplexerFactory(); - killMe = muxer; - // note that task has timeouts internally, so it might take *just over* the regular timeout - var task = muxer.ReconfigureAsync(true, false, log, null, "connect"); - - if (!task.Wait(muxer.SyncConnectTimeout(true))) - { - task.ObserveErrors(); - if (muxer.RawConfig.AbortOnConnectFail) - { - throw ExceptionFactory.UnableToConnect(muxer.RawConfig.AbortOnConnectFail, "ConnectTimeout"); - } - else - { - muxer.LastException = ExceptionFactory.UnableToConnect(muxer.RawConfig.AbortOnConnectFail, "ConnectTimeout"); - } - } - if (!task.Result) throw ExceptionFactory.UnableToConnect(muxer.RawConfig.AbortOnConnectFail, muxer.failureMessage); - killMe = null; - return muxer; - } - finally - { - if (killMe != null) try { killMe.Dispose(); } catch { } - } - } - - private string failureMessage; - private readonly Hashtable servers = new Hashtable(); - private volatile ServerEndPoint[] serverSnapshot = NilServers; - - private static readonly ServerEndPoint[] NilServers = new ServerEndPoint[0]; - - internal ServerEndPoint GetServerEndPoint(EndPoint endpoint) - { - if (endpoint == null) return null; - var server = (ServerEndPoint)servers[endpoint]; - if (server == null) - { - lock (servers) - { - server = (ServerEndPoint)servers[endpoint]; - if (server == null) - { - if (isDisposed) throw new ObjectDisposedException(ToString()); - - server = new ServerEndPoint(this, endpoint, null); - // ^^ this could indirectly cause servers to become changes, so treble-check! - if (!servers.ContainsKey(endpoint)) - { - servers.Add(endpoint, server); - } - - var newSnapshot = serverSnapshot; - Array.Resize(ref newSnapshot, newSnapshot.Length + 1); - newSnapshot[newSnapshot.Length - 1] = server; - serverSnapshot = newSnapshot; - } - - } - } - return server; - } - - internal readonly CommandMap CommandMap; - private ConnectionMultiplexer(ConfigurationOptions configuration) - { - if (configuration == null) throw new ArgumentNullException(nameof(configuration)); - IncludeDetailInExceptions = true; - IncludePerformanceCountersInExceptions = false; - - this.configuration = configuration; - - var map = CommandMap = configuration.CommandMap; - if (!string.IsNullOrWhiteSpace(configuration.Password)) map.AssertAvailable(RedisCommand.AUTH); - - if(!map.IsAvailable(RedisCommand.ECHO) && !map.IsAvailable(RedisCommand.PING) && !map.IsAvailable(RedisCommand.TIME)) - { // I mean really, give me a CHANCE! I need *something* to check the server is available to me... - // see also: SendTracer (matching logic) - map.AssertAvailable(RedisCommand.EXISTS); - } - - PreserveAsyncOrder = true; // safest default - timeoutMilliseconds = configuration.SyncTimeout; - - OnCreateReaderWriter(configuration); - unprocessableCompletionManager = new CompletionManager(this, "multiplexer"); - serverSelectionStrategy = new ServerSelectionStrategy(this); - - var configChannel = configuration.ConfigurationChannel; - if (!string.IsNullOrWhiteSpace(configChannel)) - { - ConfigurationChangedChannel = Encoding.UTF8.GetBytes(configChannel); - } - lastHeartbeatTicks = Environment.TickCount; - } - - partial void OnCreateReaderWriter(ConfigurationOptions configuration); - - internal const int MillisecondsPerHeartbeat = 1000; - - private static readonly TimerCallback heartbeat = state => - { - ((ConnectionMultiplexer)state).OnHeartbeat(); - }; - - private int _activeHeartbeatErrors; - private void OnHeartbeat() - { - try - { - int now = Environment.TickCount; - Interlocked.Exchange(ref lastHeartbeatTicks, now); - Interlocked.Exchange(ref lastGlobalHeartbeatTicks, now); - Trace("heartbeat"); - - var tmp = serverSnapshot; - for (int i = 0; i < tmp.Length; i++) - tmp[i].OnHeartbeat(); - } - catch (Exception ex) - { - if (Interlocked.CompareExchange(ref _activeHeartbeatErrors, 1, 0) == 0) - { - try - { - OnInternalError(ex); - } - finally - { - Interlocked.Exchange(ref _activeHeartbeatErrors, 0); - } - } - } - } - - private int lastHeartbeatTicks; - private static int lastGlobalHeartbeatTicks = Environment.TickCount; - internal long LastHeartbeatSecondsAgo { - get { - if (pulse == null) return -1; - return unchecked(Environment.TickCount - VolatileWrapper.Read(ref lastHeartbeatTicks)) / 1000; - } - } - - internal Exception LastException { get; set; } - - internal static long LastGlobalHeartbeatSecondsAgo => unchecked(Environment.TickCount - VolatileWrapper.Read(ref lastGlobalHeartbeatTicks)) / 1000; - - internal CompletionManager UnprocessableCompletionManager => unprocessableCompletionManager; - - /// - /// Obtain a pub/sub subscriber connection to the specified server - /// - public ISubscriber GetSubscriber(object asyncState = null) - { - if (RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("The pub/sub API is not available via twemproxy"); - return new RedisSubscriber(this, asyncState); - } - /// - /// Obtain an interactive connection to a database inside redis - /// - public IDatabase GetDatabase(int db = -1, object asyncState = null) - { - if (db == -1) - db = configuration.DefaultDatabase ?? 0; - - if (db < 0) throw new ArgumentOutOfRangeException(nameof(db)); - if (db != 0 && RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("Twemproxy only supports database 0"); - - // if there's no async-state, and the DB is suitable, we can hand out a re-used instance - return (asyncState == null && db <= MaxCachedDatabaseInstance) - ? GetCachedDatabaseInstance(db) : new RedisDatabase(this, db, asyncState); - } - - // DB zero is stored separately, since 0-only is a massively common use-case - const int MaxCachedDatabaseInstance = 16; // 17 items - [0,16] - // side note: "databases 16" is the default in redis.conf; happy to store one extra to get nice alignment etc - private IDatabase dbCacheZero; - private IDatabase[] dbCacheLow; - private IDatabase GetCachedDatabaseInstance(int db) // note that we already trust db here; only caller checks range - { - // note we don't need to worry about *always* returning the same instance - // - if two threads ask for db 3 at the same time, it is OK for them to get - // different instances, one of which (arbitrarily) ends up cached for later use - if(db == 0) - { - return dbCacheZero ?? (dbCacheZero = new RedisDatabase(this, 0, null)); - } - var arr = dbCacheLow ?? (dbCacheLow = new IDatabase[MaxCachedDatabaseInstance]); - return arr[db - 1] ?? (arr[db - 1] = new RedisDatabase(this, db, null)); - } - - /// - /// Obtain a configuration API for an individual server - /// - public IServer GetServer(string host, int port, object asyncState = null) - { - return GetServer(Format.ParseEndPoint(host, port), asyncState); - } - /// - /// Obtain a configuration API for an individual server - /// - public IServer GetServer(string hostAndPort, object asyncState = null) - { - return GetServer(Format.TryParseEndPoint(hostAndPort), asyncState); - } - /// - /// Obtain a configuration API for an individual server - /// - public IServer GetServer(IPAddress host, int port) - { - return GetServer(new IPEndPoint(host, port)); - } - - /// - /// Obtain a configuration API for an individual server - /// - public IServer GetServer(EndPoint endpoint, object asyncState = null) - { - if (endpoint == null) throw new ArgumentNullException(nameof(endpoint)); - if (RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("The server API is not available via twemproxy"); - var server = (ServerEndPoint)servers[endpoint]; - if (server == null) throw new ArgumentException("The specified endpoint is not defined", nameof(endpoint)); - return new RedisServer(this, server, asyncState); - } - - - [Conditional("VERBOSE")] - internal void Trace(string message, [System.Runtime.CompilerServices.CallerMemberName] string category = null) - { - OnTrace(message, category); - } - [Conditional("VERBOSE")] - internal void Trace(bool condition, string message, [System.Runtime.CompilerServices.CallerMemberName] string category = null) - { - if (condition) OnTrace(message, category); - } - - partial void OnTrace(string message, string category); - static partial void OnTraceWithoutContext(string message, string category); - - [Conditional("VERBOSE")] - internal static void TraceWithoutContext(string message, [System.Runtime.CompilerServices.CallerMemberName] string category = null) - { - OnTraceWithoutContext(message, category); - } - [Conditional("VERBOSE")] - internal static void TraceWithoutContext(bool condition, string message, [System.Runtime.CompilerServices.CallerMemberName] string category = null) - { - if(condition) OnTraceWithoutContext(message, category); - } - - private readonly CompletionManager unprocessableCompletionManager; - - /// - /// The number of operations that have been performed on all connections - /// - public long OperationCount { - get - { - long total = 0; - var snapshot = serverSnapshot; - for (int i = 0; i < snapshot.Length; i++) total += snapshot[i].OperationCount; - return total; - } - } - - string activeConfigCause; - - internal bool ReconfigureIfNeeded(EndPoint blame, bool fromBroadcast, string cause, bool publishReconfigure = false, CommandFlags flags = CommandFlags.None) - { - if (fromBroadcast) - { - OnConfigurationChangedBroadcast(blame); - } - string activeCause = Interlocked.CompareExchange(ref activeConfigCause, null, null); - if (activeCause == null) - { - bool reconfigureAll = fromBroadcast || publishReconfigure; - Trace("Configuration change detected; checking nodes", "Configuration"); - ReconfigureAsync(false, reconfigureAll, null, blame, cause, publishReconfigure, flags).ObserveErrors(); - return true; - } else - { - Trace("Configuration change skipped; already in progress via " + activeCause, "Configuration"); - return false; - } - } - - /// - /// Reconfigure the current connections based on the existing configuration - /// - public Task ConfigureAsync(TextWriter log = null) - { - return ReconfigureAsync(false, true, log, null, "configure").ObserveErrors(); - } - /// - /// Reconfigure the current connections based on the existing configuration - /// - public bool Configure(TextWriter log = null) - { - // note we expect ReconfigureAsync to internally allow [n] duration, - // so to avoid near misses, here we wait 2*[n] - var task = ReconfigureAsync(false, true, log, null, "configure"); - if (!task.Wait(SyncConnectTimeout(false))) - { - task.ObserveErrors(); - if (configuration.AbortOnConnectFail) - { - throw new TimeoutException(); - } - else - { - LastException = new TimeoutException("ConnectTimeout"); - } - return false; - } - return task.Result; - } - - internal int SyncConnectTimeout(bool forConnect) - { - int retryCount = forConnect ? RawConfig.ConnectRetry : 1; - if (retryCount <= 0) retryCount = 1; - - int timeout = configuration.ConnectTimeout; - if (timeout >= int.MaxValue / retryCount) return int.MaxValue; - - timeout *= retryCount; - if (timeout >= int.MaxValue - 500) return int.MaxValue; - return timeout + Math.Min(500, timeout); - } - /// - /// Provides a text overview of the status of all connections - /// - public string GetStatus() - { - using(var sw = new StringWriter()) - { - GetStatus(sw); - return sw.ToString(); - } - } - /// - /// Provides a text overview of the status of all connections - /// - public void GetStatus(TextWriter log) - { - if (log == null) return; - - var tmp = serverSnapshot; - foreach (var server in tmp) - { - LogLocked(log, server.Summary()); - LogLocked(log, server.GetCounters().ToString()); - LogLocked(log, server.GetProfile()); - } - LogLocked(log, "Sync timeouts: {0}; fire and forget: {1}; last heartbeat: {2}s ago", - Interlocked.Read(ref syncTimeouts), Interlocked.Read(ref fireAndForgets), LastHeartbeatSecondsAgo); - } - internal async Task ReconfigureAsync(bool first, bool reconfigureAll, TextWriter log, EndPoint blame, string cause, bool publishReconfigure = false, CommandFlags publishReconfigureFlags = CommandFlags.None) - { - if (isDisposed) throw new ObjectDisposedException(ToString()); - bool showStats = true; - - if (log == null) - { - log = TextWriter.Null; - showStats = false; - } - bool ranThisCall = false; - try - { // note that "activeReconfigs" starts at one; we don't need to set it the first time - ranThisCall = first || Interlocked.CompareExchange(ref activeConfigCause, cause, null) == null; - - if (!ranThisCall) - { - LogLocked(log, "Reconfiguration was already in progress"); - return false; - } - Trace("Starting reconfiguration..."); - Trace(blame != null, "Blaming: " + Format.ToString(blame)); - - LogLocked(log, configuration.ToString(includePassword: false)); - LogLocked(log, ""); - - - if (first) - { - if (configuration.ResolveDns && configuration.HasDnsEndPoints()) - { - var dns = configuration.ResolveEndPointsAsync(this, log).ObserveErrors(); -#if NET40 - var any = TaskEx.WhenAny(dns, TaskEx.Delay(timeoutMilliseconds)); -#else - var any = Task.WhenAny(dns, Task.Delay(timeoutMilliseconds)); -#endif - if ((await any.ForAwait()) != dns) - { - throw new TimeoutException("Timeout resolving endpoints"); - } - } - int index = 0; - lock (servers) - { - serverSnapshot = new ServerEndPoint[configuration.EndPoints.Count]; - foreach (var endpoint in configuration.EndPoints) - { - var server = (ServerEndPoint)servers[endpoint]; - if (server == null) - { - server = new ServerEndPoint(this, endpoint, log); - // ^^ this could indirectly cause servers to become changes, so treble-check! - if (!servers.ContainsKey(endpoint)) - { - servers.Add(endpoint, server); - } - } - serverSnapshot[index++] = server; - } - } - foreach (var server in serverSnapshot) - { - server.Activate(ConnectionType.Interactive, log); - if (CommandMap.IsAvailable(RedisCommand.SUBSCRIBE)) - { - server.Activate(ConnectionType.Subscription, null); // no need to log the SUB stuff - } - } - } - int attemptsLeft = first ? configuration.ConnectRetry : 1; - - bool healthy = false; - do - { - if (first) - { - attemptsLeft--; - } - int standaloneCount = 0, clusterCount = 0, sentinelCount = 0; - var endpoints = configuration.EndPoints; - LogLocked(log, "{0} unique nodes specified", endpoints.Count); - - if (endpoints.Count == 0) - { - throw new InvalidOperationException("No nodes to consider"); - } - - const CommandFlags flags = CommandFlags.NoRedirect | CommandFlags.HighPriority; - List masters = new List(endpoints.Count); - bool useTieBreakers = !string.IsNullOrWhiteSpace(configuration.TieBreaker); - - ServerEndPoint[] servers = null; - Task[] tieBreakers = null; - bool encounteredConnectedClusterServer = false; - Stopwatch watch = null; - - int iterCount = first ? 2 : 1; - // this is fix for https://github.com/StackExchange/StackExchange.Redis/issues/300 - // auto discoverability of cluster nodes is made synchronous. - // we try to connect to endpoints specified inside the user provided configuration - // and when we encounter one such endpoint to which we are able to successfully connect, - // we get the list of cluster nodes from this endpoint and try to proactively connect - // to these nodes instead of relying on auto configure - for (int iter = 0; iter < iterCount; ++iter) - { - if (endpoints == null) break; - - var available = new Task[endpoints.Count]; - tieBreakers = useTieBreakers ? new Task[endpoints.Count] : null; - servers = new ServerEndPoint[available.Length]; - - RedisKey tieBreakerKey = useTieBreakers ? (RedisKey)configuration.TieBreaker : default(RedisKey); - - for (int i = 0; i < available.Length; i++) - { - Trace("Testing: " + Format.ToString(endpoints[i])); - var server = GetServerEndPoint(endpoints[i]); - //server.ReportNextFailure(); - servers[i] = server; - if (reconfigureAll && server.IsConnected) - { - LogLocked(log, "Refreshing {0}...", Format.ToString(server.EndPoint)); - // note that these will be processed synchronously *BEFORE* the tracer is processed, - // so we know that the configuration will be up to date if we see the tracer - server.AutoConfigure(null); - } - available[i] = server.SendTracer(log); - if (useTieBreakers) - { - LogLocked(log, "Requesting tie-break from {0} > {1}...", Format.ToString(server.EndPoint), configuration.TieBreaker); - Message msg = Message.Create(0, flags, RedisCommand.GET, tieBreakerKey); - msg.SetInternalCall(); - msg = LoggingMessage.Create(log, msg); - tieBreakers[i] = server.QueueDirectAsync(msg, ResultProcessor.String); - } - } - - watch = watch ?? Stopwatch.StartNew(); - var remaining = configuration.ConnectTimeout - checked((int)watch.ElapsedMilliseconds); - LogLocked(log, "Allowing endpoints {0} to respond...", TimeSpan.FromMilliseconds(remaining)); - Trace("Allowing endpoints " + TimeSpan.FromMilliseconds(remaining) + " to respond..."); - await WaitAllIgnoreErrorsAsync(available, remaining, log).ForAwait(); - - EndPointCollection updatedClusterEndpointCollection = null; - for (int i = 0; i < available.Length; i++) - { - var task = available[i]; - Trace(Format.ToString(endpoints[i]) + ": " + task.Status); - if (task.IsFaulted) - { - servers[i].SetUnselectable(UnselectableFlags.DidNotRespond); - var aex = task.Exception; - foreach (var ex in aex.InnerExceptions) - { - LogLocked(log, "{0} faulted: {1}", Format.ToString(endpoints[i]), ex.Message); - failureMessage = ex.Message; - } - } - else if (task.IsCanceled) - { - servers[i].SetUnselectable(UnselectableFlags.DidNotRespond); - LogLocked(log, "{0} was canceled", Format.ToString(endpoints[i])); - } - else if (task.IsCompleted) - { - var server = servers[i]; - if (task.Result) - { - servers[i].ClearUnselectable(UnselectableFlags.DidNotRespond); - LogLocked(log, "{0} returned with success", Format.ToString(endpoints[i])); - - // count the server types - switch (server.ServerType) - { - case ServerType.Twemproxy: - case ServerType.Standalone: - standaloneCount++; - break; - case ServerType.Sentinel: - sentinelCount++; - break; - case ServerType.Cluster: - clusterCount++; - break; - } - - if (clusterCount > 0 && !encounteredConnectedClusterServer) - { - // we have encountered a connected server with clustertype for the first time. - // so we will get list of other nodes from this server using "CLUSTER NODES" command - // and try to connect to these other nodes in the next iteration - encounteredConnectedClusterServer = true; - updatedClusterEndpointCollection = await GetEndpointsFromClusterNodes(server, log).ForAwait(); - } - - // set the server UnselectableFlags and update masters list - switch (server.ServerType) - { - case ServerType.Twemproxy: - case ServerType.Sentinel: - case ServerType.Standalone: - case ServerType.Cluster: - servers[i].ClearUnselectable(UnselectableFlags.ServerType); - if (server.IsSlave) - { - servers[i].ClearUnselectable(UnselectableFlags.RedundantMaster); - } - else - { - masters.Add(server); - } - break; - default: - servers[i].SetUnselectable(UnselectableFlags.ServerType); - break; - } - } - else - { - servers[i].SetUnselectable(UnselectableFlags.DidNotRespond); - LogLocked(log, "{0} returned, but incorrectly", Format.ToString(endpoints[i])); - } - } - else - { - servers[i].SetUnselectable(UnselectableFlags.DidNotRespond); - LogLocked(log, "{0} did not respond", Format.ToString(endpoints[i])); - } - } - - if (encounteredConnectedClusterServer) - { - endpoints = updatedClusterEndpointCollection; - } - else - { - break; // we do not want to repeat the second iteration - } - } - - if (clusterCount == 0) - { - // set the serverSelectionStrategy - if (RawConfig.Proxy == Proxy.Twemproxy) - { - serverSelectionStrategy.ServerType = ServerType.Twemproxy; - } - else if (standaloneCount == 0 && sentinelCount > 0) - { - serverSelectionStrategy.ServerType = ServerType.Sentinel; - } - else - { - serverSelectionStrategy.ServerType = ServerType.Standalone; - } - var preferred = await NominatePreferredMaster(log, servers, useTieBreakers, tieBreakers, masters).ObserveErrors().ForAwait(); - foreach (var master in masters) - { - if (master == preferred) - { - master.ClearUnselectable(UnselectableFlags.RedundantMaster); - } - else - { - master.SetUnselectable(UnselectableFlags.RedundantMaster); - } - } - } - else - { - serverSelectionStrategy.ServerType = ServerType.Cluster; - long coveredSlots = serverSelectionStrategy.CountCoveredSlots(); - LogLocked(log, "Cluster: {0} of {1} slots covered", - coveredSlots, serverSelectionStrategy.TotalSlots); - - } - if (!first) - { - long subscriptionChanges = ValidateSubscriptions(); - if (subscriptionChanges == 0) - { - LogLocked(log, "No subscription changes necessary"); - } - else - { - LogLocked(log, "Subscriptions reconfigured: {0}", subscriptionChanges); - } - } - if (showStats) - { - GetStatus(log); - } - - string stormLog = GetStormLog(); - if (!string.IsNullOrWhiteSpace(stormLog)) - { - LogLocked(log, ""); - LogLocked(log, stormLog); - } - healthy = standaloneCount != 0 || clusterCount != 0 || sentinelCount != 0; - if (first && !healthy && attemptsLeft > 0) - { - LogLocked(log, "resetting failing connections to retry..."); - ResetAllNonConnected(); - LogLocked(log, "retrying; attempts left: " + attemptsLeft + "..."); - } - //WTF("?: " + attempts); - } while (first && !healthy && attemptsLeft > 0); - - if(first && configuration.AbortOnConnectFail && !healthy) - { - return false; - } - if (first) - { - LogLocked(log, "Starting heartbeat..."); - pulse = new Timer(heartbeat, this, MillisecondsPerHeartbeat, MillisecondsPerHeartbeat); - } - if(publishReconfigure) - { - try - { - LogLocked(log, "Broadcasting reconfigure..."); - PublishReconfigureImpl(publishReconfigureFlags); - } - catch - { } - } - return true; - - } catch (Exception ex) - { - Trace(ex.Message); - throw; - } - finally - { - Trace("Exiting reconfiguration..."); - OnTraceLog(log); - if (ranThisCall) Interlocked.Exchange(ref activeConfigCause, null); - if (!first) OnConfigurationChanged(blame); - Trace("Reconfiguration exited"); - } - } - - private async Task GetEndpointsFromClusterNodes(ServerEndPoint server, TextWriter log) - { - var message = Message.Create(-1, CommandFlags.None, RedisCommand.CLUSTER, RedisLiterals.NODES); - try - { - var clusterConfig = await ExecuteAsyncImpl(message, ResultProcessor.ClusterNodes, null, server).ForAwait(); - return new EndPointCollection(clusterConfig.Nodes.Select(node => node.EndPoint).ToList()); - } - catch (Exception ex) - { - LogLocked(log, "Encountered error while updating cluster config: " + ex.Message); - return null; - } - } - - - private void ResetAllNonConnected() - { - var snapshot = serverSnapshot; - foreach(var server in snapshot) - { - server.ResetNonConnected(); - } - } - - partial void OnTraceLog(TextWriter log, [System.Runtime.CompilerServices.CallerMemberName] string caller = null); - private async Task NominatePreferredMaster(TextWriter log, ServerEndPoint[] servers, bool useTieBreakers, Task[] tieBreakers, List masters) - { - Dictionary uniques = null; - if (useTieBreakers) - { // count the votes - uniques = new Dictionary(StringComparer.OrdinalIgnoreCase); - await WaitAllIgnoreErrorsAsync(tieBreakers, 50, log).ForAwait(); - for (int i = 0; i < tieBreakers.Length; i++) - { - var ep = servers[i].EndPoint; - var status = tieBreakers[i].Status; - switch (status) - { - case TaskStatus.RanToCompletion: - string s = tieBreakers[i].Result; - if (string.IsNullOrWhiteSpace(s)) - { - LogLocked(log, "{0} had no tiebreaker set", Format.ToString(ep)); - } - else - { - LogLocked(log, "{0} nominates: {1}", Format.ToString(ep), s); - int count; - if (!uniques.TryGetValue(s, out count)) count = 0; - uniques[s] = count + 1; - } - break; - case TaskStatus.Faulted: - LogLocked(log, "{0} failed to nominate ({1})", Format.ToString(ep), status); - foreach (var ex in tieBreakers[i].Exception.InnerExceptions) - { - if (ex.Message.StartsWith("MOVED ") || ex.Message.StartsWith("ASK ")) continue; - LogLocked(log, "> {0}", ex.Message); - } - break; - default: - LogLocked(log, "{0} failed to nominate ({1})", Format.ToString(ep), status); - break; - } - } - } - - - switch (masters.Count) - { - case 0: - LogLocked(log, "No masters detected"); - return null; - case 1: - LogLocked(log, "Single master detected: " + Format.ToString(masters[0].EndPoint)); - return masters[0]; - default: - LogLocked(log, "Multiple masters detected..."); - if (useTieBreakers && uniques != null) - { - switch (uniques.Count) - { - case 0: - LogLocked(log, "nobody nominated a tie-breaker"); - break; - case 1: - string unanimous = uniques.Keys.Single(); - LogLocked(log, "tie-break is unanimous at {0}", unanimous); - var found = SelectServerByElection(servers, unanimous, log); - if (found != null) - { - LogLocked(log, "Elected: {0}", Format.ToString(found.EndPoint)); - return found; - } - break; - default: - LogLocked(log, "tie-break is contested:"); - ServerEndPoint highest = null; - bool arbitrary = false; - foreach (var pair in uniques.OrderByDescending(x => x.Value)) - { - LogLocked(log, "{0} has {1} votes", pair.Key, pair.Value); - if (highest == null) - { - highest = SelectServerByElection(servers, pair.Key, log); - if (highest != null) - { - // any more with this vote? if so: arbitrary - arbitrary = uniques.Where(x => x.Value == pair.Value).Skip(1).Any(); - } - } - } - if (highest != null) - { - if (arbitrary) - { - LogLocked(log, "Choosing master arbitrarily: {0}", Format.ToString(highest.EndPoint)); - } - else - { - LogLocked(log, "Elected: {0}", Format.ToString(highest.EndPoint)); - } - return highest; - } - break; - - } - - } - break; - } - - LogLocked(log, "Choosing master arbitrarily: {0}", Format.ToString(masters[0].EndPoint)); - return masters[0]; - - } - - private ServerEndPoint SelectServerByElection(ServerEndPoint[] servers, string endpoint, TextWriter log) - { - if (servers == null || string.IsNullOrWhiteSpace(endpoint)) return null; - for (int i = 0; i < servers.Length; i++) - { - if (string.Equals(Format.ToString(servers[i].EndPoint), endpoint, StringComparison.OrdinalIgnoreCase)) - return servers[i]; - } - LogLocked(log, "...but we couldn't find that"); - var deDottedEndpoint = DeDotifyHost(endpoint); - for (int i = 0; i < servers.Length; i++) - { - if (string.Equals(DeDotifyHost(Format.ToString(servers[i].EndPoint)), deDottedEndpoint, StringComparison.OrdinalIgnoreCase)) - { - LogLocked(log, "...but we did find instead: {0}", deDottedEndpoint); - return servers[i]; - } - } - return null; - } - - static string DeDotifyHost(string input) - { - if (string.IsNullOrWhiteSpace(input)) return input; // GIGO - - if (!char.IsLetter(input[0])) return input; // need first char to be alpha for this to work - - int periodPosition = input.IndexOf('.'); - if (periodPosition <= 0) return input; // no period or starts with a period? nothing useful to split - - int colonPosition = input.IndexOf(':'); - if (colonPosition > 0) - { // has a port specifier - return input.Substring(0, periodPosition) + input.Substring(colonPosition); - } - else - { - return input.Substring(0, periodPosition); - } - } - - internal void UpdateClusterRange(ClusterConfiguration configuration) - { - if (configuration == null) return; - foreach (var node in configuration.Nodes) - { - if (node.IsSlave || node.Slots.Count == 0) continue; - foreach (var slot in node.Slots) - { - var server = GetServerEndPoint(node.EndPoint); - if (server != null) serverSelectionStrategy.UpdateClusterRange(slot.From, slot.To, server); - } - } - } - - private Timer pulse; - - private readonly ServerSelectionStrategy serverSelectionStrategy; - - internal ServerEndPoint[] GetServerSnapshot() - { - var tmp = serverSnapshot; - return tmp; - } - - internal ServerEndPoint SelectServer(Message message) - { - if (message == null) return null; - return serverSelectionStrategy.Select(message); - } - - internal ServerEndPoint SelectServer(int db, RedisCommand command, CommandFlags flags, RedisKey key) - { - return serverSelectionStrategy.Select(db, command, key, flags); - } - private bool TryPushMessageToBridge(Message message, ResultProcessor processor, ResultBox resultBox, ref ServerEndPoint server) - { - message.SetSource(processor, resultBox); - - if (server == null) - { // infer a server automatically - server = SelectServer(message); - } - else // a server was specified; do we trust their choice, though? - { - - if (message.IsMasterOnly() && server.IsSlave) - { - throw ExceptionFactory.MasterOnly(IncludeDetailInExceptions, message.Command, message, server); - } - - switch(server.ServerType) - { - case ServerType.Cluster: - case ServerType.Twemproxy: // strictly speaking twemproxy uses a different hashing algo, but the hash-tag behavior is - // the same, so this does a pretty good job of spotting illegal commands before sending them - if (message.GetHashSlot(ServerSelectionStrategy) == ServerSelectionStrategy.MultipleSlots) - { - throw ExceptionFactory.MultiSlot(IncludeDetailInExceptions, message); - } - break; - } - if (!server.IsConnected) - { - // well, that's no use! - server = null; - } - } - - if (server != null) - { - var profCtx = profiler?.GetContext(); - if (profCtx != null) - { - ConcurrentProfileStorageCollection inFlightForCtx; - if (profiledCommands.TryGetValue(profCtx, out inFlightForCtx)) - { - message.SetProfileStorage(ProfileStorage.NewWithContext(inFlightForCtx, server)); - } - } - - if (message.Db >= 0) - { - int availableDatabases = server.Databases; - if (availableDatabases > 0 && message.Db >= availableDatabases) throw ExceptionFactory.DatabaseOutfRange( - IncludeDetailInExceptions, message.Db, message, server); - } - - Trace("Queueing on server: " + message); - if (server.TryEnqueue(message)) return true; - } - Trace("No server or server unavailable - aborting: " + message); - return false; - } - - - /// - /// See Object.ToString() - /// - public override string ToString() - { - string s = ClientName; - if (string.IsNullOrWhiteSpace(s)) s = GetType().Name; - return s; - } - - internal readonly byte[] ConfigurationChangedChannel; // this gets accessed for every received event; let's make sure we can process it "raw" - internal readonly byte[] UniqueId = Guid.NewGuid().ToByteArray(); // unique identifier used when tracing - - - /// - /// Gets or sets whether asynchronous operations should be invoked in a way that guarantees their original delivery order - /// - public bool PreserveAsyncOrder { get; set; } - - /// - /// Indicates whether any servers are connected - /// - public bool IsConnected - { - get - { - var tmp = serverSnapshot; - for (int i = 0; i < tmp.Length; i++) - if (tmp[i].IsConnected) return true; - return false; - } - } - - internal ConfigurationOptions RawConfig => configuration; - - internal ServerSelectionStrategy ServerSelectionStrategy => serverSelectionStrategy; - - - /// - /// Close all connections and release all resources associated with this object - /// - public void Close(bool allowCommandsToComplete = true) - { - isDisposed = true; - using (var tmp = pulse) - { - pulse = null; - } - - if (allowCommandsToComplete) - { - var quits = QuitAllServers(); - WaitAllIgnoreErrors(quits); - } - DisposeAndClearServers(); - OnCloseReaderWriter(); - } - partial void OnCloseReaderWriter(); - - private void DisposeAndClearServers() - { - lock (servers) - { - var iter = servers.GetEnumerator(); - while (iter.MoveNext()) - { - var server = (ServerEndPoint)iter.Value; - server.Dispose(); - } - servers.Clear(); - } - } - - private Task[] QuitAllServers() - { - Task[] quits = new Task[servers.Count]; - lock (servers) - { - var iter = servers.GetEnumerator(); - int index = 0; - while (iter.MoveNext()) - { - var server = (ServerEndPoint)iter.Value; - quits[index++] = server.Close(); - } - } - return quits; - } - - /// - /// Close all connections and release all resources associated with this object - /// - public async Task CloseAsync(bool allowCommandsToComplete = true) - { - isDisposed = true; - using (var tmp = pulse) - { - pulse = null; - } - - if (allowCommandsToComplete) - { - var quits = QuitAllServers(); - await WaitAllIgnoreErrorsAsync(quits, configuration.SyncTimeout, null).ForAwait(); - } - - DisposeAndClearServers(); - } - - /// - /// Release all resources associated with this object - /// - public void Dispose() - { - Close(!isDisposed); - } - - - internal Task ExecuteAsyncImpl(Message message, ResultProcessor processor, object state, ServerEndPoint server) - { - if (isDisposed) throw new ObjectDisposedException(ToString()); - - if (message == null) - { - return CompletedTask.Default(state); - } - - if (message.IsFireAndForget) - { - TryPushMessageToBridge(message, processor, null, ref server); - return CompletedTask.Default(null); // F+F explicitly does not get async-state - } - else - { - var tcs = TaskSource.CreateDenyExecSync(state); - var source = ResultBox.Get(tcs); - if (!TryPushMessageToBridge(message, processor, source, ref server)) - { - ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(IncludeDetailInExceptions, IncludePerformanceCountersInExceptions, message.Command, message, server, GetServerSnapshot())); - } - return tcs.Task; - } - } - - internal static void ThrowFailed(TaskCompletionSource source, Exception unthrownException) - { - try - { - throw unthrownException; - } catch (Exception ex) - { - source.TrySetException(ex); - GC.KeepAlive(source.Task.Exception); - GC.SuppressFinalize(source.Task); - } - } - internal T ExecuteSyncImpl(Message message, ResultProcessor processor, ServerEndPoint server) - { - if (isDisposed) throw new ObjectDisposedException(ToString()); - - if (message == null) // fire-and forget could involve a no-op, represented by null - for example Increment by 0 - { - return default(T); - } - - if (message.IsFireAndForget) - { - TryPushMessageToBridge(message, processor, null, ref server); - Interlocked.Increment(ref fireAndForgets); - return default(T); - } - else - { - var source = ResultBox.Get(null); - - lock (source) - { - if (!TryPushMessageToBridge(message, processor, source, ref server)) - { - throw ExceptionFactory.NoConnectionAvailable(IncludeDetailInExceptions, IncludePerformanceCountersInExceptions, message.Command, message, server, GetServerSnapshot()); - } - - if (Monitor.Wait(source, timeoutMilliseconds)) - { - Trace("Timeley response to " + message.ToString()); - } - else - { - Trace("Timeout performing " + message.ToString()); - Interlocked.Increment(ref syncTimeouts); - string errMessage; - List> data = null; - if (server == null || !IncludeDetailInExceptions) - { - errMessage = "Timeout performing " + message.Command.ToString(); - } - else - { - int inst, qu, qs, qc, wr, wq, @in, ar; -#if FEATURE_SOCKET_MODE_POLL - var mgrState = socketManager.State; - var lastError = socketManager.LastErrorTimeRelative(); - -#endif - var sb = new StringBuilder("Timeout performing ").Append(message.CommandAndKey); - data = new List> {Tuple.Create("Message", message.CommandAndKey)}; - Action add = (lk, sk, v) => - { - data.Add(Tuple.Create(lk, v)); - sb.Append(", " + sk + ": " + v); - }; - - int queue = server.GetOutstandingCount(message.Command, out inst, out qu, out qs, out qc, out wr, out wq, out @in, out ar); - add("Instantaneous", "inst", inst.ToString()); -#if FEATURE_SOCKET_MODE_POLL - add("Manager-State", "mgr", mgrState.ToString()); - add("Last-Error", "err", lastError); -#endif - add("Queue-Length", "queue", queue.ToString()); - add("Queue-Outstanding", "qu", qu.ToString()); - add("Queue-Awaiting-Response", "qs", qs.ToString()); - add("Queue-Completion-Outstanding", "qc", qc.ToString()); - add("Active-Writers", "wr", wr.ToString()); - add("Write-Queue", "wq", wq.ToString()); - add("Inbound-Bytes", "in", @in.ToString()); - add("Active-Readers", "ar", ar.ToString()); - - add("Client-Name", "clientName", ClientName); - add("Server-Endpoint", "serverEndpoint", server.EndPoint.ToString()); - var hashSlot = message.GetHashSlot(this.ServerSelectionStrategy); - // only add keyslot if its a valid cluster key slot - if (hashSlot != ServerSelectionStrategy.NoSlot) - { - add("Key-HashSlot", "keyHashSlot", message.GetHashSlot(this.ServerSelectionStrategy).ToString()); - } -#if !CORE_CLR - string iocp, worker; - int busyWorkerCount = GetThreadPoolStats(out iocp, out worker); - add("ThreadPool-IO-Completion", "IOCP", iocp); - add("ThreadPool-Workers", "WORKER", worker); - data.Add(Tuple.Create("Busy-Workers", busyWorkerCount.ToString())); - - if (IncludePerformanceCountersInExceptions) - { - add("Local-CPU", "Local-CPU", GetSystemCpuPercent()); - } -#endif - sb.Append(" (Please take a look at this article for some common client-side issues that can cause timeouts: "); - sb.Append(timeoutHelpLink); - sb.Append(")"); - errMessage = sb.ToString(); - if (stormLogThreshold >= 0 && queue >= stormLogThreshold && Interlocked.CompareExchange(ref haveStormLog, 1, 0) == 0) - { - var log = server.GetStormLog(message.Command); - if (string.IsNullOrWhiteSpace(log)) Interlocked.Exchange(ref haveStormLog, 0); - else Interlocked.Exchange(ref stormLogSnapshot, log); - } - } - var timeoutEx = ExceptionFactory.Timeout(IncludeDetailInExceptions, errMessage, message, server); - timeoutEx.HelpLink = timeoutHelpLink; - - if (data != null) - { - foreach (var kv in data) - { - timeoutEx.Data["Redis-" + kv.Item1] = kv.Item2; - } - } - throw timeoutEx; - // very important not to return "source" to the pool here - } - } - // snapshot these so that we can recycle the box - Exception ex; - T val; - ResultBox.UnwrapAndRecycle(source, true, out val, out ex); // now that we aren't locking it... - if (ex != null) throw ex; - Trace(message + " received " + val); - return val; - } - } - -#if !CORE_CLR - internal static string GetThreadPoolAndCPUSummary(bool includePerformanceCounters) - { - string iocp, worker; - GetThreadPoolStats(out iocp, out worker); - var cpu = includePerformanceCounters ? GetSystemCpuPercent() : "n/a"; - return $"IOCP: {iocp}, WORKER: {worker}, Local-CPU: {cpu}"; - } - - private static string GetSystemCpuPercent() - { - float systemCPU; - if (PerfCounterHelper.TryGetSystemCPU(out systemCPU)) - { - return Math.Round(systemCPU, 2) + "%"; - } - return "unavailable"; - } - - private static int GetThreadPoolStats(out string iocp, out string worker) - { - //BusyThreads = TP.GetMaxThreads() –TP.GetAVailable(); - //If BusyThreads >= TP.GetMinThreads(), then threadpool growth throttling is possible. - - int maxIoThreads, maxWorkerThreads; - ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxIoThreads); - - int freeIoThreads, freeWorkerThreads; - ThreadPool.GetAvailableThreads(out freeWorkerThreads, out freeIoThreads); - - int minIoThreads, minWorkerThreads; - ThreadPool.GetMinThreads(out minWorkerThreads, out minIoThreads); - - int busyIoThreads = maxIoThreads - freeIoThreads; - int busyWorkerThreads = maxWorkerThreads - freeWorkerThreads; - - iocp = $"(Busy={busyIoThreads},Free={freeIoThreads},Min={minIoThreads},Max={maxIoThreads})"; - worker = $"(Busy={busyWorkerThreads},Free={freeWorkerThreads},Min={minWorkerThreads},Max={maxWorkerThreads})"; - return busyWorkerThreads; - } -#endif - - /// - /// Should exceptions include identifiable details? (key names, additional .Data annotations) - /// - public bool IncludeDetailInExceptions { get; set; } - - /// - /// Should exceptions include performance counter details? (CPU usage, etc - note that this can be problematic on some platforms) - /// - public bool IncludePerformanceCountersInExceptions { get; set; } - - int haveStormLog = 0, stormLogThreshold = 15; - string stormLogSnapshot; - /// - /// Limit at which to start recording unusual busy patterns (only one log will be retained at a time; - /// set to a negative value to disable this feature) - /// - public int StormLogThreshold { get { return stormLogThreshold; } set { stormLogThreshold = value; } } - /// - /// Obtains the log of unusual busy patterns - /// - public string GetStormLog() - { - var result = Interlocked.CompareExchange(ref stormLogSnapshot, null, null); - return result; - } - /// - /// Resets the log of unusual busy patterns - /// - public void ResetStormLog() - { - Interlocked.Exchange(ref stormLogSnapshot, null); - Interlocked.Exchange(ref haveStormLog, 0); - } - private long syncTimeouts, fireAndForgets; - - /// - /// Request all compatible clients to reconfigure or reconnect - /// - /// The number of instances known to have received the message (however, the actual number can be higher; returns -1 if the operation is pending) - public long PublishReconfigure(CommandFlags flags = CommandFlags.None) - { - byte[] channel = ConfigurationChangedChannel; - if (channel == null) return 0; - if (ReconfigureIfNeeded(null, false, "PublishReconfigure", true, flags)) - { - return -1; - } - else - { - return PublishReconfigureImpl(flags); - } - } - private long PublishReconfigureImpl(CommandFlags flags) - { - byte[] channel = ConfigurationChangedChannel; - if (channel == null) return 0; - return GetSubscriber().Publish(channel, RedisLiterals.Wildcard, flags); - } - - /// - /// Request all compatible clients to reconfigure or reconnect - /// - /// The number of instances known to have received the message (however, the actual number can be higher) - public Task PublishReconfigureAsync(CommandFlags flags = CommandFlags.None) - { - byte[] channel = ConfigurationChangedChannel; - if (channel == null) return CompletedTask.Default(null); - - return GetSubscriber().PublishAsync(channel, RedisLiterals.Wildcard, flags); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ConnectionType.cs b/StackExchange.Redis/StackExchange/Redis/ConnectionType.cs deleted file mode 100644 index efba46677..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ConnectionType.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace StackExchange.Redis -{ - /// - /// The type of a connection - /// - public enum ConnectionType - { - /// - /// Not connection-type related - /// - None = 0, - /// - /// An interactive connection handles request/response commands for accessing data on demand - /// - Interactive, - /// - /// A subscriber connection recieves unsolicted messages from the server as pub/sub events occur - /// - Subscription - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/DebuggingAids.cs b/StackExchange.Redis/StackExchange/Redis/DebuggingAids.cs deleted file mode 100644 index 19d3deb2b..000000000 --- a/StackExchange.Redis/StackExchange/Redis/DebuggingAids.cs +++ /dev/null @@ -1,458 +0,0 @@ -using System; -using System.Diagnostics; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ -#if DEBUG - - partial class ResultBox - { - internal static long allocations; - - public static long GetAllocationCount() - { - return Interlocked.Read(ref allocations); - } - static partial void OnAllocated() - { - Interlocked.Increment(ref allocations); - } - } - partial interface IServer - { - /// - /// Show what is in the pending (unsent) queue - /// - string ListPending(int maxCount); - /// - /// Get the value of key. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values. - /// - /// the value of key, or nil when key does not exist. - /// http://redis.io/commands/get - RedisValue StringGet(int db, RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Get the value of key. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values. - /// - /// the value of key, or nil when key does not exist. - /// http://redis.io/commands/get - Task StringGetAsync(int db, RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Break the connection without mercy or thought - /// - void SimulateConnectionFailure(); - - /// - /// DEBUG SEGFAULT performs an invalid memory access that crashes Redis. It is used to simulate bugs during the development. - /// - /// http://redis.io/commands/debug-segfault - void Crash(); - - /// - /// CLIENT PAUSE is a connections control command able to suspend all the Redis clients for the specified amount of time (in milliseconds). - /// - /// http://redis.io/commands/client-pause - void Hang(TimeSpan duration, CommandFlags flags = CommandFlags.None); - } - partial interface IRedis - { - /// - /// The CLIENT GETNAME returns the name of the current connection as set by CLIENT SETNAME. Since every new connection starts without an associated name, if no name was assigned a null string is returned. - /// - /// http://redis.io/commands/client-getname - /// The connection name, or a null string if no name is set. - string ClientGetName(CommandFlags flags = CommandFlags.None); - - /// - /// Ask the server to close the connection. The connection is closed as soon as all pending replies have been written to the client. - /// - /// http://redis.io/commands/quit - void Quit(CommandFlags flags = CommandFlags.None); - } - - partial interface IRedisAsync - { - /// - /// The CLIENT GETNAME returns the name of the current connection as set by CLIENT SETNAME. Since every new connection starts without an associated name, if no name was assigned a null string is returned. - /// - /// http://redis.io/commands/client-getname - /// The connection name, or a null string if no name is set. - Task ClientGetNameAsync(CommandFlags flags = CommandFlags.None); - } - partial class RedisBase - { - string IRedis.ClientGetName(CommandFlags flags) - { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.GETNAME); - return ExecuteSync(msg, ResultProcessor.String); - } - - Task IRedisAsync.ClientGetNameAsync(CommandFlags flags) - { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.GETNAME); - return ExecuteAsync(msg, ResultProcessor.String); - } - } - - partial class ServerEndPoint - { - - internal void SimulateConnectionFailure() - { - var tmp = interactive; - tmp?.SimulateConnectionFailure(); - tmp = subscription; - tmp?.SimulateConnectionFailure(); - } - internal string ListPending(int maxCount) - { - var sb = new StringBuilder(); - var tmp = interactive; - tmp?.ListPending(sb, maxCount); - tmp = subscription; - tmp?.ListPending(sb, maxCount); - return sb.ToString(); - } - } - - partial class RedisServer - { - void IServer.SimulateConnectionFailure() - { - server.SimulateConnectionFailure(); - } - string IServer.ListPending(int maxCount) - { - return server.ListPending(maxCount); - } - void IServer.Crash() - { - // using DB-0 because we also use "DEBUG OBJECT", which is db-centric - var msg = Message.Create(0, CommandFlags.FireAndForget, RedisCommand.DEBUG, RedisLiterals.SEGFAULT); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - void IServer.Hang(TimeSpan duration, CommandFlags flags) - { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.PAUSE, (long)duration.TotalMilliseconds); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - } - - partial class CompletionManager - { - private static long asyncCompletionWorkerCount; - - partial void OnCompletedAsync() - { - Interlocked.Increment(ref asyncCompletionWorkerCount); - } - internal static long GetAsyncCompletionWorkerCount() - { - return Interlocked.Read(ref asyncCompletionWorkerCount); - } - } - - partial class ConnectionMultiplexer - { - /// - /// Gets how many result-box instances were allocated - /// - public static long GetResultBoxAllocationCount() - { - return ResultBox.GetAllocationCount(); - } - /// - /// Gets how many async completion workers were queueud - /// - public static long GetAsyncCompletionWorkerCount() - { - return CompletionManager.GetAsyncCompletionWorkerCount(); - } - /// - /// For debugging; when not enabled, servers cannot connect - /// - public bool AllowConnect { get { return allowConnect; } set { allowConnect = value; } } - private volatile bool allowConnect = true, ignoreConnect = false; - - /// - /// For debugging; when not enabled, end-connect is silently ignored (to simulate a long-running connect) - /// - public bool IgnoreConnect { get { return ignoreConnect; } set { ignoreConnect = value; } } - } - - partial class SocketManager - { - partial void ShouldIgnoreConnect(ISocketCallback callback, ref bool ignore) - { - ignore = callback.IgnoreConnect; - } - - /// - /// Completion type for BeginConnect call - /// - public static CompletionType ConnectCompletionType { get; set; } - - partial void ShouldForceConnectCompletionType(ref CompletionType completionType) - { - completionType = SocketManager.ConnectCompletionType; - } - } - partial interface ISocketCallback - { - bool IgnoreConnect { get; } - } - - partial class MessageQueue - { - internal void ListPending(StringBuilder sb, int maxCount) - { - lock (regular) - { - foreach (var item in high) - { - if (--maxCount < 0) break; - if (sb.Length != 0) sb.Append(","); - item.AppendStormLog(sb); - } - foreach (var item in regular) - { - if (--maxCount < 0) break; - if (sb.Length != 0) sb.Append(","); - item.AppendStormLog(sb); - } - } - } - } - - partial class PhysicalBridge - { - internal void SimulateConnectionFailure() - { - if (!Multiplexer.RawConfig.AllowAdmin) - { - throw ExceptionFactory.AdminModeNotEnabled(Multiplexer.IncludeDetailInExceptions, RedisCommand.DEBUG, null, ServerEndPoint); // close enough - } - physical?.RecordConnectionFailed(ConnectionFailureType.SocketFailure); - } - internal void ListPending(StringBuilder sb, int maxCount) - { - queue.ListPending(sb, maxCount); - } - } - - partial class PhysicalConnection - { - partial void OnDebugAbort() - { - if (!Multiplexer.AllowConnect) - { - throw new RedisConnectionException(ConnectionFailureType.InternalFailure, "debugging"); - } - } - - bool ISocketCallback.IgnoreConnect => Multiplexer.IgnoreConnect; - - private static volatile bool emulateStaleConnection; - public static bool EmulateStaleConnection - { - get - { - return emulateStaleConnection; - } - set - { - emulateStaleConnection = value; - } - } - - partial void DebugEmulateStaleConnection(ref int firstUnansweredWrite) - { - if (emulateStaleConnection) - { - firstUnansweredWrite = Environment.TickCount - 100000; - } - } - } -#endif - - /// - /// Completion type for CompletionTypeHelper - /// - public enum CompletionType - { - /// - /// Retain original completion type (either sync or async) - /// - Any = 0, - /// - /// Force sync completion - /// - Sync = 1, - /// - /// Force async completion - /// - Async = 2 - } -#if !CORE_CLR - - internal static class PerfCounterHelper - { - static object staticLock = new object(); - static volatile PerformanceCounter _cpu; - static volatile bool _disabled; - - public static bool TryGetSystemCPU(out float value) - { - value = -1; - - try - { - if (!_disabled && _cpu == null) - { - lock (staticLock) - { - if (_cpu == null) - { - _cpu = new PerformanceCounter("Processor", "% Processor Time", "_Total"); - - // First call always returns 0, so get that out of the way. - _cpu.NextValue(); - } - } - } - } - catch (UnauthorizedAccessException) - { - // Some environments don't allow access to Performance Counters, so stop trying. - _disabled = true; - } - catch (Exception) - { - // this shouldn't happen, but just being safe... - } - - if (!_disabled && _cpu != null) - { - value = _cpu.NextValue(); - return true; - } - - return false; - } - } - - internal class CompletionTypeHelper - { - - public static void RunWithCompletionType(Func beginAsync, AsyncCallback callback, CompletionType completionType) - { - AsyncCallback proxyCallback; - if (completionType == CompletionType.Any) - { - proxyCallback = (ar) => - { - if (!ar.CompletedSynchronously) - { - callback(ar); - } - }; - } - else - { - proxyCallback = (ar) => { }; - } - - var result = beginAsync(proxyCallback); - - if (completionType == CompletionType.Any && !result.CompletedSynchronously) - { - return; - } - - result.AsyncWaitHandle.WaitOne(); - - switch (completionType) - { - case CompletionType.Async: - ThreadPool.QueueUserWorkItem((s) => { callback(result); }); - break; - case CompletionType.Any: - case CompletionType.Sync: - callback(result); - break; - } - - return; - } - } -#endif - -#if VERBOSE - - partial class ConnectionMultiplexer - { - private readonly int epoch = Environment.TickCount; - - partial void OnTrace(string message, string category) - { - Debug.WriteLine(message, - ((Environment.TickCount - epoch)).ToString().PadLeft(5, ' ') + "ms on " + - Environment.CurrentManagedThreadId + " ~ " + category); - } - static partial void OnTraceWithoutContext(string message, string category) - { - Debug.WriteLine(message, Environment.CurrentManagedThreadId + " ~ " + category); - } - - partial void OnTraceLog(TextWriter log, string caller) - { - lock (UniqueId) - { - Trace(log.ToString(), caller); // note that this won't always be useful, but we only do it in debug builds anyway - } - } - } -#endif - - -#if LOGOUTPUT - partial class ConnectionMultiplexer - { - /// - /// Dumps a copy of the stream - /// - public static string EchoPath { get; set; } - } - - partial class PhysicalConnection - { - private Stream echo; - partial void OnCreateEcho() - { - if (!string.IsNullOrEmpty(ConnectionMultiplexer.EchoPath)) - { - string fullPath = Path.Combine(ConnectionMultiplexer.EchoPath, - Regex.Replace(physicalName, @"[\-\.\@\#\:]", "_")); - echo = File.Open(Path.ChangeExtension(fullPath, "txt"), FileMode.Create, FileAccess.Write, FileShare.ReadWrite); - } - } - partial void OnCloseEcho() - { - if (echo != null) - { - try { echo.Close(); } catch { } - try { echo.Dispose(); } catch { } - echo = null; - } - } - partial void OnWrapForLogging(ref Stream stream, string name) - { - stream = new LoggingTextStream(stream, physicalName, echo); - } - } -#endif -} diff --git a/StackExchange.Redis/StackExchange/Redis/EndPointCollection.cs b/StackExchange.Redis/StackExchange/Redis/EndPointCollection.cs deleted file mode 100644 index 46fde3eaf..000000000 --- a/StackExchange.Redis/StackExchange/Redis/EndPointCollection.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Net; - -namespace StackExchange.Redis -{ - /// - /// A list of endpoints - /// - public sealed class EndPointCollection : Collection - { - /// - /// Create a new EndPointCollection - /// - public EndPointCollection() : base() - { - } - - /// - /// Create a new EndPointCollection - /// - public EndPointCollection(IList endpoints) : base(endpoints) - { - } - - /// - /// Format an endpoint - /// - public static string ToString(EndPoint endpoint) - { - return Format.ToString(endpoint); - } - - /// - /// Attempt to parse a string into an EndPoint - /// - public static EndPoint TryParse(string endpoint) - { - return Format.TryParseEndPoint(endpoint); - } - /// - /// Adds a new endpoint to the list - /// - public void Add(string hostAndPort) - { - var endpoint = Format.TryParseEndPoint(hostAndPort); - if (endpoint == null) throw new ArgumentException(); - Add(endpoint); - } - - /// - /// Adds a new endpoint to the list - /// - public void Add(string host, int port) - { - Add(Format.ParseEndPoint(host, port)); - } - - /// - /// Adds a new endpoint to the list - /// - public void Add(IPAddress host, int port) - { - Add(new IPEndPoint(host, port)); - } - - /// - /// See Collection<T>.InsertItem() - /// - protected override void InsertItem(int index, EndPoint item) - { - if (item == null) throw new ArgumentNullException(nameof(item)); - if (Contains(item)) throw new ArgumentException("EndPoints must be unique", nameof(item)); - base.InsertItem(index, item); - } - /// - /// See Collection<T>.SetItem() - /// - protected override void SetItem(int index, EndPoint item) - { - if (item == null) throw new ArgumentNullException(nameof(item)); - int existingIndex; - try - { - existingIndex = IndexOf(item); - } catch(NullReferenceException) - { - // mono has a nasty bug in DnsEndPoint.Equals; if they do bad things here: sorry, I can't help - existingIndex = -1; - } - if (existingIndex >= 0 && existingIndex != index) throw new ArgumentException("EndPoints must be unique", nameof(item)); - base.SetItem(index, item); - } - - internal void SetDefaultPorts(int defaultPort) - { - for (int i = 0; i < Count; i++) - { - var endpoint = this[i]; - var dns = endpoint as DnsEndPoint; - if (dns?.Port == 0) - { - this[i] = new DnsEndPoint(dns.Host, defaultPort, dns.AddressFamily); - continue; - } - var ip = endpoint as IPEndPoint; - if (ip?.Port == 0) - { - this[i] = new IPEndPoint(ip.Address, defaultPort); - continue; - } - } - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/EndPointEventArgs.cs b/StackExchange.Redis/StackExchange/Redis/EndPointEventArgs.cs deleted file mode 100644 index b57d11b4b..000000000 --- a/StackExchange.Redis/StackExchange/Redis/EndPointEventArgs.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Net; -using System.Text; - -namespace StackExchange.Redis -{ - /// - /// Event information related to redis endpoints - /// - public class EndPointEventArgs : EventArgs, ICompletable - { - private readonly EventHandler handler; - private readonly object sender; - internal EndPointEventArgs(EventHandler handler, object sender, EndPoint endpoint) - { - this.handler = handler; - this.sender = sender; - EndPoint = endpoint; - } - - /// - /// The endpoint involved in this event (this can be null) - /// - public EndPoint EndPoint { get; } - - void ICompletable.AppendStormLog(StringBuilder sb) - { - sb.Append("event, endpoint: "); - if (EndPoint == null) sb.Append("n/a"); - else sb.Append(Format.ToString(EndPoint)); - } - - bool ICompletable.TryComplete(bool isAsync) - { - return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync); - } - } -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/ExceptionFactory.cs b/StackExchange.Redis/StackExchange/Redis/ExceptionFactory.cs deleted file mode 100644 index 94988a38a..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ExceptionFactory.cs +++ /dev/null @@ -1,242 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Text; - -namespace StackExchange.Redis -{ - internal static class ExceptionFactory - { - const string DataCommandKey = "redis-command", - DataServerKey = "redis-server", - DataServerEndpoint = "server-endpoint", - DataConnectionState = "connection-state", - DataLastFailure = "last-failure", - DataLastInnerException = "last-innerexception", - DataSentStatusKey = "request-sent-status"; - - - internal static Exception AdminModeNotEnabled(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server) - { - string s = GetLabel(includeDetail, command, message); - var ex = new RedisCommandException("This operation is not available unless admin mode is enabled: " + s); - if (includeDetail) AddDetail(ex, message, server, s); - return ex; - } - internal static Exception CommandDisabled(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server) - { - string s = GetLabel(includeDetail, command, message); - var ex = new RedisCommandException("This operation has been disabled in the command-map and cannot be used: " + s); - if (includeDetail) AddDetail(ex, message, server, s); - return ex; - } - internal static Exception TooManyArgs(bool includeDetail, string command, Message message, ServerEndPoint server, int required) - { - string s = GetLabel(includeDetail, command, message); - var ex = new RedisCommandException($"This operation would involve too many arguments ({required} vs the redis limit of {PhysicalConnection.REDIS_MAX_ARGS}): {s}"); - if (includeDetail) AddDetail(ex, message, server, s); - return ex; - } - internal static Exception CommandDisabled(bool includeDetail, string command, Message message, ServerEndPoint server) - { - string s = GetLabel(includeDetail, command, message); - var ex = new RedisCommandException("This operation has been disabled in the command-map and cannot be used: " + s); - if (includeDetail) AddDetail(ex, message, server, s); - return ex; - } - - internal static Exception ConnectionFailure(bool includeDetail, ConnectionFailureType failureType, string message, ServerEndPoint server) - { - var ex = new RedisConnectionException(failureType, message); - if (includeDetail) AddDetail(ex, null, server, null); - return ex; - } - - internal static Exception DatabaseNotRequired(bool includeDetail, RedisCommand command) - { - string s = command.ToString(); - var ex = new RedisCommandException("A target database is not required for " + s); - if (includeDetail) AddDetail(ex, null, null, s); - return ex; - } - - internal static Exception DatabaseOutfRange(bool includeDetail, int targetDatabase, Message message, ServerEndPoint server) - { - var ex = new RedisCommandException("The database does not exist on the server: " + targetDatabase); - if (includeDetail) AddDetail(ex, message, server, null); - return ex; - } - - internal static Exception DatabaseRequired(bool includeDetail, RedisCommand command) - { - string s = command.ToString(); - var ex = new RedisCommandException("A target database is required for " + s); - if (includeDetail) AddDetail(ex, null, null, s); - return ex; - } - - internal static Exception MasterOnly(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server) - { - string s = GetLabel(includeDetail, command, message); - var ex = new RedisCommandException("Command cannot be issued to a slave: " + s); - if (includeDetail) AddDetail(ex, message, server, s); - return ex; - } - - internal static Exception MultiSlot(bool includeDetail, Message message) - { - var ex = new RedisCommandException("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot"); - if (includeDetail) AddDetail(ex, message, null, null); - return ex; - } - - internal static string GetInnerMostExceptionMessage(Exception e) - { - if (e == null) - { - return ""; - } - else - { - while (e.InnerException != null) - { - e = e.InnerException; - } - return e.Message; - } - } - - internal static Exception NoConnectionAvailable(bool includeDetail, bool includePerformanceCounters, RedisCommand command, Message message, ServerEndPoint server, ServerEndPoint[] serverSnapshot) - { - string commandLabel = GetLabel(includeDetail, command, message); - - if (server != null) - { - //if we already have the serverEndpoint for connection failure use that - //otherwise it would output state of all the endpoints - serverSnapshot = new ServerEndPoint[] { server }; - } - - var innerException = PopulateInnerExceptions(serverSnapshot); - - StringBuilder exceptionmessage = new StringBuilder("No connection is available to service this operation: ").Append(commandLabel); - string innermostExceptionstring = GetInnerMostExceptionMessage(innerException); - if (!string.IsNullOrEmpty(innermostExceptionstring)) - { - exceptionmessage.Append("; ").Append(innermostExceptionstring); - } - -#if !CORE_CLR - if (includeDetail) - { - exceptionmessage.Append("; ").Append(ConnectionMultiplexer.GetThreadPoolAndCPUSummary(includePerformanceCounters)); - } -#endif - - var ex = new RedisConnectionException(ConnectionFailureType.UnableToResolvePhysicalConnection, exceptionmessage.ToString(), innerException, message?.Status ?? CommandStatus.Unknown); - - if (includeDetail) - { - AddDetail(ex, message, server, commandLabel); - } - return ex; - } - - internal static Exception PopulateInnerExceptions(ServerEndPoint[] serverSnapshot) - { - List innerExceptions = new List(); - if (serverSnapshot != null) - { - if (serverSnapshot.Length > 0 && serverSnapshot[0].Multiplexer.LastException != null) - { - innerExceptions.Add(serverSnapshot[0].Multiplexer.LastException); - } - - for (int i = 0; i < serverSnapshot.Length; i++) - { - if (serverSnapshot[i].LastException != null) - { - var lastException = serverSnapshot[i].LastException; - innerExceptions.Add(lastException); - } - } - } - if (innerExceptions.Count == 1) - { - return innerExceptions[0]; - } - else if(innerExceptions.Count > 1) - { - return new AggregateException(innerExceptions); - } - return null; - } - - internal static Exception NotSupported(bool includeDetail, RedisCommand command) - { - string s = GetLabel(includeDetail, command, null); - var ex = new RedisCommandException("Command is not available on your server: " + s); - if (includeDetail) AddDetail(ex, null, null, s); - return ex; - } - internal static Exception NoCursor(RedisCommand command) - { - string s = GetLabel(false, command, null); - var ex = new RedisCommandException("Command cannot be used with a cursor: " + s); - return ex; - } - - internal static Exception Timeout(bool includeDetail, string errorMessage, Message message, ServerEndPoint server) - { - var ex = new RedisTimeoutException(errorMessage, message?.Status ?? CommandStatus.Unknown); - if (includeDetail) AddDetail(ex, message, server, null); - return ex; - } - - private static void AddDetail(Exception exception, Message message, ServerEndPoint server, string label) - { - if (exception != null) - { - if (message != null) - { - exception.Data.Add(DataCommandKey, message.CommandAndKey); - exception.Data.Add(DataSentStatusKey, message.Status); - } - else if (label != null) exception.Data.Add(DataCommandKey, label); - - if (server != null) exception.Data.Add(DataServerKey, Format.ToString(server.EndPoint)); - } - } - - static string GetLabel(bool includeDetail, RedisCommand command, Message message) - { - return message == null ? command.ToString() : (includeDetail ? message.CommandAndKey : message.Command.ToString()); - } - static string GetLabel(bool includeDetail, string command, Message message) - { - return message == null ? command : (includeDetail ? message.CommandAndKey : message.Command.ToString()); - } - - internal static Exception UnableToConnect(bool abortOnConnect, string failureMessage=null) - { - var abortOnConnectionFailure = abortOnConnect ? "to create a disconnected multiplexer, disable AbortOnConnectFail. " : ""; - return new RedisConnectionException(ConnectionFailureType.UnableToConnect, - string.Format("It was not possible to connect to the redis server(s); {0}{1}", abortOnConnectionFailure, failureMessage)); - } - - internal static Exception BeganProfilingWithDuplicateContext(object forContext) - { - var exc = new InvalidOperationException("Attempted to begin profiling for the same context twice"); - exc.Data["forContext"] = forContext; - return exc; - } - - internal static Exception FinishedProfilingWithInvalidContext(object forContext) - { - var exc = new InvalidOperationException("Attempted to finish profiling for a context which is no longer valid, or was never begun"); - exc.Data["forContext"] = forContext; - return exc; - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/Exclude.cs b/StackExchange.Redis/StackExchange/Redis/Exclude.cs deleted file mode 100644 index b8a9db0b2..000000000 --- a/StackExchange.Redis/StackExchange/Redis/Exclude.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace StackExchange.Redis -{ - /// - /// When performing a range query, by default the start / stop limits are inclusive; - /// however, both can also be specified separately as exclusive - /// - [Flags] - public enum Exclude - { - /// - /// Both start and stop are inclusive - /// - None = 0, - /// - /// Start is exclusive, stop is inclusive - /// - Start = 1, - /// - /// Start is inclusive, stop is exclusive - /// - Stop = 2, - /// - /// Both start and stop are exclusive - /// - Both = Start | Stop - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ExponentialRetry.cs b/StackExchange.Redis/StackExchange/Redis/ExponentialRetry.cs deleted file mode 100644 index ab954da6b..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ExponentialRetry.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; - -namespace StackExchange.Redis -{ - /// - /// Represents a retry policy that performs retries, using a randomized exponential back off scheme to determine the interval between retries. - /// - public class ExponentialRetry : IReconnectRetryPolicy - { - private int deltaBackOffMilliseconds; - private int maxDeltaBackOffMilliseconds = (int)TimeSpan.FromSeconds(10).TotalMilliseconds; - [ThreadStatic] - private static Random r; - - /// - /// Initializes a new instance using the specified back off interval with default maxDeltaBackOffMilliseconds of 10 seconds - /// - /// time in milliseconds for the back-off interval between retries - public ExponentialRetry(int deltaBackOffMilliseconds) : this(deltaBackOffMilliseconds, (int)TimeSpan.FromSeconds(10).TotalMilliseconds) - { - } - - /// - /// Initializes a new instance using the specified back off interval. - /// - /// time in milliseconds for the back-off interval between retries - /// time in milliseconds for the maximum value that the back-off interval can exponentailly grow upto - public ExponentialRetry(int deltaBackOffMilliseconds, int maxDeltaBackOffMilliseconds) - { - this.deltaBackOffMilliseconds = deltaBackOffMilliseconds; - this.maxDeltaBackOffMilliseconds = maxDeltaBackOffMilliseconds; - } - - /// - /// This method is called by the ConnectionMultiplexer to determine if a reconnect operation can be retried now. - /// - /// The number of times reconnect retries have already been made by the ConnectionMultiplexer while it was in the connecting state - /// Total elapsed time in milliseconds since the last reconnect retry was made - public bool ShouldRetry(long currentRetryCount, int timeElapsedMillisecondsSinceLastRetry) - { - var exponential = (int)Math.Min(maxDeltaBackOffMilliseconds, deltaBackOffMilliseconds * Math.Pow(1.1, currentRetryCount)); - int random; - r = r ?? new Random(); - random = r.Next((int)deltaBackOffMilliseconds, exponential); - return timeElapsedMillisecondsSinceLastRetry >= random; - //exponential backoff with deltaBackOff of 5000ms - //deltabackoff exponential - //5000 5500 - //5000 6050 - //5000 6655 - //5000 8053 - //5000 10718 - //5000 17261 - //5000 37001 - //5000 127738 - } - } -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/ExportOptions.cs b/StackExchange.Redis/StackExchange/Redis/ExportOptions.cs deleted file mode 100644 index c6891b537..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ExportOptions.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -namespace StackExchange.Redis -{ - /// - /// Which settings to export - /// - [Flags] - public enum ExportOptions - { - /// - /// No options - /// - None = 0, - /// - /// The output of INFO - /// - Info = 1, - /// - /// The output of CONFIG GET * - /// - Config = 2, - /// - /// The output of CLIENT LIST - /// - Client = 4, - /// - /// The output of CLUSTER NODES - /// - Cluster = 8, - /// - /// Everything available - /// - All = -1 - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ExtensionMethods.cs b/StackExchange.Redis/StackExchange/Redis/ExtensionMethods.cs deleted file mode 100644 index 67bbafcd7..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ExtensionMethods.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Security; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; - -namespace StackExchange.Redis -{ - /// - /// Utility methods - /// - public static class ExtensionMethods - { - /// - /// Create a dictionary from an array of HashEntry values - /// - public static Dictionary ToStringDictionary(this HashEntry[] hash) - { - if (hash == null) return null; - - var result = new Dictionary(hash.Length, StringComparer.Ordinal); - for(int i = 0; i < hash.Length; i++) - { - result.Add(hash[i].name, hash[i].value); - } - return result; - } - /// - /// Create a dictionary from an array of HashEntry values - /// - public static Dictionary ToDictionary(this HashEntry[] hash) - { - if (hash == null) return null; - - var result = new Dictionary(hash.Length); - for (int i = 0; i < hash.Length; i++) - { - result.Add(hash[i].name, hash[i].value); - } - return result; - } - - /// - /// Create a dictionary from an array of SortedSetEntry values - /// - public static Dictionary ToStringDictionary(this SortedSetEntry[] sortedSet) - { - if (sortedSet == null) return null; - - var result = new Dictionary(sortedSet.Length, StringComparer.Ordinal); - for (int i = 0; i < sortedSet.Length; i++) - { - result.Add(sortedSet[i].element, sortedSet[i].score); - } - return result; - } - - /// - /// Create a dictionary from an array of SortedSetEntry values - /// - public static Dictionary ToDictionary(this SortedSetEntry[] sortedSet) - { - if (sortedSet == null) return null; - - var result = new Dictionary(sortedSet.Length); - for (int i = 0; i < sortedSet.Length; i++) - { - result.Add(sortedSet[i].element, sortedSet[i].score); - } - return result; - } - - /// - /// Create a dictionary from an array of key/value pairs - /// - public static Dictionary ToStringDictionary(this KeyValuePair[] pairs) - { - if (pairs == null) return null; - - var result = new Dictionary(pairs.Length, StringComparer.Ordinal); - for (int i = 0; i < pairs.Length; i++) - { - result.Add(pairs[i].Key, pairs[i].Value); - } - return result; - } - - /// - /// Create a dictionary from an array of key/value pairs - /// - public static Dictionary ToDictionary(this KeyValuePair[] pairs) - { - if (pairs == null) return null; - - var result = new Dictionary(pairs.Length); - for (int i = 0; i < pairs.Length; i++) - { - result.Add(pairs[i].Key, pairs[i].Value); - } - return result; - } - - /// - /// Create a dictionary from an array of string pairs - /// - public static Dictionary ToDictionary(this KeyValuePair[] pairs) - { - if (pairs == null) return null; - - var result = new Dictionary(pairs.Length, StringComparer.Ordinal); - for (int i = 0; i < pairs.Length; i++) - { - result.Add(pairs[i].Key, pairs[i].Value); - } - return result; - } - - static readonly string[] nix = new string[0]; - /// - /// Create an array of strings from an array of values - /// - public static string[] ToStringArray(this RedisValue[] values) - { - if (values == null) return null; - if (values.Length == 0) return nix; - return ConvertHelper.ConvertAll(values, x => (string)x); - } - - internal static void AuthenticateAsClient(this SslStream ssl, string host, SslProtocols? allowedProtocols) - { - if (!allowedProtocols.HasValue) - { - //Default to the sslProtocols defined by the .NET Framework - AuthenticateAsClientUsingDefaultProtocols(ssl, host); - return; - } - - var certificateCollection = new X509CertificateCollection(); - const bool checkCertRevocation = true; -#if CORE_CLR - ssl.AuthenticateAsClientAsync(host, certificateCollection, allowedProtocols.Value, checkCertRevocation) - .GetAwaiter().GetResult(); -#else - ssl.AuthenticateAsClient(host, certificateCollection, allowedProtocols.Value, checkCertRevocation); -#endif - } - - private static void AuthenticateAsClientUsingDefaultProtocols(SslStream ssl, string host) - { -#if CORE_CLR - ssl.AuthenticateAsClientAsync(host).GetAwaiter().GetResult(); -#else - ssl.AuthenticateAsClient(host); -#endif - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/Format.cs b/StackExchange.Redis/StackExchange/Redis/Format.cs deleted file mode 100644 index 3c8a3c6e2..000000000 --- a/StackExchange.Redis/StackExchange/Redis/Format.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Globalization; -using System.Net; - -namespace StackExchange.Redis -{ - internal static class Format - { - public static int ParseInt32(string s) - { - return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo); - } - - public static long ParseInt64(string s) - { - return long.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo); - } - - public static string ToString(int value) - { - return value.ToString(NumberFormatInfo.InvariantInfo); - } - - public static bool TryParseBoolean(string s, out bool value) - { - if (bool.TryParse(s, out value)) return true; - - if (s == "1" || string.Equals(s, "yes", StringComparison.OrdinalIgnoreCase) || string.Equals(s, "on", StringComparison.OrdinalIgnoreCase)) - { - value = true; - return true; - } - if (s == "0" || string.Equals(s, "no", StringComparison.OrdinalIgnoreCase) || string.Equals(s, "off", StringComparison.OrdinalIgnoreCase)) - { - value = false; - return true; - } - value = false; - return false; - } - - public static bool TryParseInt32(string s, out int value) - { - return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out value); - } - internal static EndPoint ParseEndPoint(string host, int port) - { - IPAddress ip; - if (IPAddress.TryParse(host, out ip)) return new IPEndPoint(ip, port); - return new DnsEndPoint(host, port); - } - internal static EndPoint TryParseEndPoint(string host, string port) - { - if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(port)) return null; - int i; - return TryParseInt32(port, out i) ? ParseEndPoint(host, i) : null; - } - - internal static string ToString(long value) - { - return value.ToString(NumberFormatInfo.InvariantInfo); - } - - internal static string ToString(double value) - { - if (double.IsInfinity(value)) - { - if (double.IsPositiveInfinity(value)) return "+inf"; - if (double.IsNegativeInfinity(value)) return "-inf"; - } - return value.ToString("G17", NumberFormatInfo.InvariantInfo); - } - - internal static string ToString(object value) - { - return Convert.ToString(value, CultureInfo.InvariantCulture); - } - - internal static string ToString(EndPoint endpoint) - { - var dns = endpoint as DnsEndPoint; - if (dns != null) - { - if (dns.Port == 0) return dns.Host; - return dns.Host + ":" + Format.ToString(dns.Port); - } - var ip = endpoint as IPEndPoint; - if (ip != null) - { - if (ip.Port == 0) return ip.Address.ToString(); - return ip.Address.ToString() + ":" + Format.ToString(ip.Port); - } - return endpoint?.ToString() ?? ""; - } - internal static string ToStringHostOnly(EndPoint endpoint) - { - var dns = endpoint as DnsEndPoint; - if (dns != null) - { - return dns.Host; - } - var ip = endpoint as IPEndPoint; - if(ip != null) - { - return ip.Address.ToString(); - } - return ""; - } - - internal static bool TryGetHostPort(EndPoint endpoint, out string host, out int port) - { - if (endpoint != null) - { - if (endpoint is IPEndPoint) - { - IPEndPoint ip = (IPEndPoint)endpoint; - host = ip.Address.ToString(); - port = ip.Port; - return true; - } - if (endpoint is DnsEndPoint) - { - DnsEndPoint dns = (DnsEndPoint)endpoint; - host = dns.Host; - port = dns.Port; - return true; - } - } - host = null; - port = 0; - return false; - } - - internal static bool TryParseDouble(string s, out double value) - { - if(string.IsNullOrEmpty(s)) - { - value = 0; - return false; - } - if(s.Length==1 && s[0] >= '0' && s[0] <= '9') - { - value = (int)(s[0] - '0'); - return true; - } - // need to handle these - if(string.Equals("+inf", s, StringComparison.OrdinalIgnoreCase) || string.Equals("inf", s, StringComparison.OrdinalIgnoreCase)) - { - value = double.PositiveInfinity; - return true; - } - if(string.Equals("-inf", s, StringComparison.OrdinalIgnoreCase)) - { - value = double.NegativeInfinity; - return true; - } - return double.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out value); - } - internal static EndPoint TryParseEndPoint(string endpoint) - { - if (string.IsNullOrWhiteSpace(endpoint)) return null; - string host; - int port; - int i = endpoint.IndexOf(':'); - if (i < 0) - { - host = endpoint; - port = 0; - } - else - { - host = endpoint.Substring(0, i); - var portAsString = endpoint.Substring(i + 1); - if (string.IsNullOrEmpty(portAsString)) return null; - if (!Format.TryParseInt32(portAsString, out port)) return null; - } - if (string.IsNullOrWhiteSpace(host)) return null; - - return Format.ParseEndPoint(host, port); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/GeoEntry.cs b/StackExchange.Redis/StackExchange/Redis/GeoEntry.cs deleted file mode 100644 index dd56090d2..000000000 --- a/StackExchange.Redis/StackExchange/Redis/GeoEntry.cs +++ /dev/null @@ -1,242 +0,0 @@ - -using System; - -namespace StackExchange.Redis -{ - /// - /// GeoRadius command options. - /// - [Flags] - public enum GeoRadiusOptions - { - /// - /// No Options - /// - None = 0, - /// - /// Redis will return the coordinates of any results. - /// - WithCoordinates = 1, - /// - /// Redis will return the distance from center for all results. - /// - WithDistance = 2, - /// - /// Redis will return the geo hash value as an integer. (This is the score in the sorted set) - /// - WithGeoHash = 4, - /// - /// Populates the commonly used values from the entry (the integer hash is not returned as it is not commonly useful) - /// - Default = WithCoordinates | GeoRadiusOptions.WithDistance - } - - /// - /// The result of a GeoRadius command. - /// - public struct GeoRadiusResult - { - /// - /// Indicate the member being represented - /// - public override string ToString() => Member.ToString(); - /// - /// The matched member. - /// - public RedisValue Member { get; } - - - /// - /// The distance of the matched member from the center of the geo radius command. - /// - public double? Distance { get; } - - /// - /// The hash value of the matched member as an integer. (The key in the sorted set) - /// - /// Note that this is not the same as the hash returned from GeoHash - public long? Hash { get; } - - /// - /// The coordinates of the matched member. - /// - public GeoPosition? Position { get; } - - /// - /// Returns a new GeoRadiusResult - /// - internal GeoRadiusResult(RedisValue member, double? distance, long? hash, GeoPosition? position) - { - Member = member; - Distance = distance; - Hash = hash; - Position = position; - } - } - - /// - /// Describes the longitude and latitude of a GeoEntry - /// - public struct GeoPosition : IEquatable - { - internal static string GetRedisUnit(GeoUnit unit) - { - switch (unit) - { - case GeoUnit.Meters: return "m"; - case GeoUnit.Kilometers: return "km"; - case GeoUnit.Miles: return "mi"; - case GeoUnit.Feet: return "ft"; - default: - throw new ArgumentOutOfRangeException(nameof(unit)); - } - } - - /// - /// The Latitude of the GeoPosition - /// - public double Latitude { get; } - - /// - /// The Logitude of the GeoPosition - /// - public double Longitude { get; } - - /// - /// Creates a new GeoPosition - /// - /// - /// - public GeoPosition(double longitude, double latitude) - { - Longitude = longitude; - Latitude = latitude; - } - - /// - /// See Object.ToString() - /// - public override string ToString() - { - return string.Format("{0} {1}", Longitude, Latitude); - } - /// - /// See Object.GetHashCode() - /// Diagonals not an issue in the case of lat/long - /// - public override int GetHashCode() - { - // diagonals not an issue in the case of lat/long - return Longitude.GetHashCode() ^ Latitude.GetHashCode(); - } - /// - /// Compares two values for equality - /// - public override bool Equals(object obj) - { - return obj is GeoPosition && Equals((GeoPosition)obj); - } - /// - /// Compares two values for equality - /// - public bool Equals(GeoPosition value) - { - return this == value; - } - /// - /// Compares two values for equality - /// - public static bool operator ==(GeoPosition x, GeoPosition y) - { - return x.Longitude == y.Longitude && x.Latitude == y.Latitude; - } - /// - /// Compares two values for non-equality - /// - public static bool operator !=(GeoPosition x, GeoPosition y) - { - return x.Longitude != y.Longitude || x.Latitude != y.Latitude; - } - } - - /// - /// Describes a GeoEntry element with the corresponding value - /// GeoEntries are stored in redis as SortedSetEntries - /// - public struct GeoEntry : IEquatable - { - /// - /// The name of the geo entry - /// - public RedisValue Member { get; } - - /// - /// Describes the longitude and latitude of a GeoEntry - /// - public GeoPosition Position { get; } - - /// - /// Initializes a GeoEntry value - /// - public GeoEntry(double longitude, double latitude, RedisValue member) - { - Member = member; - Position = new GeoPosition(longitude, latitude); - } - - - - /// - /// The longitude of the geo entry - /// - public double Longitude => Position.Longitude; - - /// - /// The latitude of the geo entry - /// - public double Latitude => Position.Latitude; - - /// - /// See Object.ToString() - /// - public override string ToString() - { - return $"({Longitude},{Latitude})={Member}"; - } - /// - /// See Object.GetHashCode() - /// - public override int GetHashCode() - { - return Position.GetHashCode() ^ Member.GetHashCode(); - } - /// - /// Compares two values for equality - /// - public override bool Equals(object obj) - { - return obj is GeoEntry && Equals((GeoEntry)obj); - } - /// - /// Compares two values for equality - /// - public bool Equals(GeoEntry value) - { - return this == value; - } - /// - /// Compares two values for equality - /// - public static bool operator ==(GeoEntry x, GeoEntry y) - { - return x.Position == y.Position && x.Member == y.Member; - } - /// - /// Compares two values for non-equality - /// - public static bool operator !=(GeoEntry x, GeoEntry y) - { - return x.Position != y.Position || x.Member != y.Member; - } - } -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/GeoUnit.cs b/StackExchange.Redis/StackExchange/Redis/GeoUnit.cs deleted file mode 100644 index a88e2deec..000000000 --- a/StackExchange.Redis/StackExchange/Redis/GeoUnit.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.ComponentModel; - -namespace StackExchange.Redis -{ - /// - /// Units associated with Geo Commands - /// - public enum GeoUnit - { - /// - /// Meters - /// - Meters, - /// - /// Kilometers - /// - Kilometers, - /// - /// Miles - /// - Miles, - /// - /// Feet - /// - Feet - } -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/HashEntry.cs b/StackExchange.Redis/StackExchange/Redis/HashEntry.cs deleted file mode 100644 index 0446a4e66..000000000 --- a/StackExchange.Redis/StackExchange/Redis/HashEntry.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; - -namespace StackExchange.Redis -{ - /// - /// Describes a hash-field (a name/value pair) - /// - public struct HashEntry : IEquatable - { - internal readonly RedisValue name, value; - - /// - /// Initializes a HashEntry value - /// - public HashEntry(RedisValue name, RedisValue value) - { - this.name = name; - this.value = value; - } - /// - /// The name of the hash field - /// - public RedisValue Name => name; - - /// - /// The value of the hash field - /// - public RedisValue Value => value; - - /// - /// The name of the hash field - /// -#if !CORE_CLR - [Browsable(false)] -#endif - [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Please use Name", false)] - public RedisValue Key { get { return name; } } - - /// - /// Converts to a key/value pair - /// - public static implicit operator KeyValuePair(HashEntry value) - { - return new KeyValuePair(value.name, value.value); - } - /// - /// Converts from a key/value pair - /// - public static implicit operator HashEntry(KeyValuePair value) - { - return new HashEntry(value.Key, value.Value); - } - - /// - /// See Object.ToString() - /// - public override string ToString() - { - return name + ": " + value; - } - /// - /// See Object.GetHashCode() - /// - public override int GetHashCode() - { - return name.GetHashCode() ^ value.GetHashCode(); - } - /// - /// Compares two values for equality - /// - public override bool Equals(object obj) - { - return obj is HashEntry && Equals((HashEntry)obj); - } - - /// - /// Compares two values for equality - /// - public bool Equals(HashEntry value) - { - return name == value.name && this.value == value.value; - } - /// - /// Compares two values for equality - /// - public static bool operator ==(HashEntry x, HashEntry y) - { - return x.name == y.name && x.value == y.value; - } - /// - /// Compares two values for non-equality - /// - public static bool operator !=(HashEntry x, HashEntry y) - { - return x.name != y.name || x.value != y.value; - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/HashSlotMovedEventArgs.cs b/StackExchange.Redis/StackExchange/Redis/HashSlotMovedEventArgs.cs deleted file mode 100644 index d5189622b..000000000 --- a/StackExchange.Redis/StackExchange/Redis/HashSlotMovedEventArgs.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Net; -using System.Text; - -namespace StackExchange.Redis -{ - /// - /// Contains information about individual hash-slot relocations - /// - public sealed class HashSlotMovedEventArgs : EventArgs, ICompletable - { - private readonly object sender; - private readonly EventHandler handler; - /// - /// The hash-slot that was relocated - /// - public int HashSlot { get; } - - /// - /// The old endpoint for this hash-slot (if known) - /// - public EndPoint OldEndPoint { get; } - - /// - /// The new endpoint for this hash-slot (if known) - /// - public EndPoint NewEndPoint { get; } - - internal HashSlotMovedEventArgs(EventHandler handler, object sender, - int hashSlot, EndPoint old, EndPoint @new) - { - this.handler = handler; - this.sender = sender; - HashSlot = hashSlot; - OldEndPoint = old; - NewEndPoint = @new; - } - - bool ICompletable.TryComplete(bool isAsync) - { - return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync); - } - - void ICompletable.AppendStormLog(StringBuilder sb) - { - sb.Append("event, slot-moved: ").Append(HashSlot); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/IConnectionMultiplexer.cs b/StackExchange.Redis/StackExchange/Redis/IConnectionMultiplexer.cs deleted file mode 100644 index 52d27770f..000000000 --- a/StackExchange.Redis/StackExchange/Redis/IConnectionMultiplexer.cs +++ /dev/null @@ -1,253 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - /// - /// Represents the abstract multiplexer API - /// - public interface IConnectionMultiplexer - { - -#if DEBUG - /// - /// For debugging; when not enabled, servers cannot connect - /// - bool AllowConnect { get; set; } - - /// - /// For debugging; when not enabled, end-connect is silently ignored (to simulate a long-running connect) - /// - bool IgnoreConnect { get; set; } -#endif - /// - /// Gets the client-name that will be used on all new connections - /// - string ClientName { get; } - - /// - /// Gets the configuration of the connection - /// - string Configuration { get; } - - /// - /// Gets the timeout associated with the connections - /// - int TimeoutMilliseconds { get; } - - /// - /// The number of operations that have been performed on all connections - /// - long OperationCount { get; } - - /// - /// Gets or sets whether asynchronous operations should be invoked in a way that guarantees their original delivery order - /// - bool PreserveAsyncOrder { get; set; } - - /// - /// Indicates whether any servers are connected - /// - bool IsConnected { get; } - - /// - /// Should exceptions include identifiable details? (key names, additional .Data annotations) - /// - bool IncludeDetailInExceptions { get; set; } - - /// - /// Limit at which to start recording unusual busy patterns (only one log will be retained at a time; - /// set to a negative value to disable this feature) - /// - int StormLogThreshold { get; set; } - - /// - /// Sets an IProfiler instance for this ConnectionMultiplexer. - /// - /// An IProfiler instances is used to determine which context to associate an - /// IProfiledCommand with. See BeginProfiling(object) and FinishProfiling(object) - /// for more details. - /// - void RegisterProfiler(IProfiler profiler); - - /// - /// Begins profiling for the given context. - /// - /// If the same context object is returned by the registered IProfiler, the IProfiledCommands - /// will be associated with each other. - /// - /// Call FinishProfiling with the same context to get the assocated commands. - /// - /// Note that forContext cannot be a WeakReference or a WeakReference<T> - /// - void BeginProfiling(object forContext); - - /// - /// Stops profiling for the given context, returns all IProfiledCommands associated. - /// - /// By default this may do a sweep for dead profiling contexts, you can disable this by passing "allowCleanupSweep: false". - /// - ProfiledCommandEnumerable FinishProfiling(object forContext, bool allowCleanupSweep = true); - - /// - /// Get summary statistics associates with this server - /// - ServerCounters GetCounters(); - - /// - /// A server replied with an error message; - /// - event EventHandler ErrorMessage; - - /// - /// Raised whenever a physical connection fails - /// - event EventHandler ConnectionFailed; - - /// - /// Raised whenever an internal error occurs (this is primarily for debugging) - /// - event EventHandler InternalError; - - /// - /// Raised whenever a physical connection is established - /// - event EventHandler ConnectionRestored; - - /// - /// Raised when configuration changes are detected - /// - event EventHandler ConfigurationChanged; - - /// - /// Raised when nodes are explicitly requested to reconfigure via broadcast; - /// this usually means master/slave changes - /// - event EventHandler ConfigurationChangedBroadcast; - - /// - /// Gets all endpoints defined on the server - /// - /// - EndPoint[] GetEndPoints(bool configuredOnly = false); - - /// - /// Wait for a given asynchronous operation to complete (or timeout) - /// - void Wait(Task task); - - /// - /// Wait for a given asynchronous operation to complete (or timeout) - /// - T Wait(Task task); - - /// - /// Wait for the given asynchronous operations to complete (or timeout) - /// - void WaitAll(params Task[] tasks); - - /// - /// Raised when a hash-slot has been relocated - /// - event EventHandler HashSlotMoved; - - /// - /// Compute the hash-slot of a specified key - /// - int HashSlot(RedisKey key); - - /// - /// Obtain a pub/sub subscriber connection to the specified server - /// - ISubscriber GetSubscriber(object asyncState = null); - - /// - /// Obtain an interactive connection to a database inside redis - /// - IDatabase GetDatabase(int db = -1, object asyncState = null); - - /// - /// Obtain a configuration API for an individual server - /// - IServer GetServer(string host, int port, object asyncState = null); - - /// - /// Obtain a configuration API for an individual server - /// - IServer GetServer(string hostAndPort, object asyncState = null); - - /// - /// Obtain a configuration API for an individual server - /// - IServer GetServer(IPAddress host, int port); - - /// - /// Obtain a configuration API for an individual server - /// - IServer GetServer(EndPoint endpoint, object asyncState = null); - - /// - /// Reconfigure the current connections based on the existing configuration - /// - Task ConfigureAsync(TextWriter log = null); - - /// - /// Reconfigure the current connections based on the existing configuration - /// - bool Configure(TextWriter log = null); - - /// - /// Provides a text overview of the status of all connections - /// - string GetStatus(); - - /// - /// Provides a text overview of the status of all connections - /// - void GetStatus(TextWriter log); - - /// - /// See Object.ToString() - /// - string ToString(); - - /// - /// Close all connections and release all resources associated with this object - /// - void Close(bool allowCommandsToComplete = true); - - /// - /// Close all connections and release all resources associated with this object - /// - Task CloseAsync(bool allowCommandsToComplete = true); - - /// - /// Release all resources associated with this object - /// - void Dispose(); - - /// - /// Obtains the log of unusual busy patterns - /// - string GetStormLog(); - - /// - /// Resets the log of unusual busy patterns - /// - void ResetStormLog(); - - /// - /// Request all compatible clients to reconfigure or reconnect - /// - /// The number of instances known to have received the message (however, the actual number can be higher; returns -1 if the operation is pending) - long PublishReconfigure(CommandFlags flags = CommandFlags.None); - - /// - /// Request all compatible clients to reconfigure or reconnect - /// - /// The number of instances known to have received the message (however, the actual number can be higher) - Task PublishReconfigureAsync(CommandFlags flags = CommandFlags.None); - } -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/IDatabase.cs b/StackExchange.Redis/StackExchange/Redis/IDatabase.cs deleted file mode 100644 index 331fe4783..000000000 --- a/StackExchange.Redis/StackExchange/Redis/IDatabase.cs +++ /dev/null @@ -1,1078 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; - -namespace StackExchange.Redis -{ - - /// - /// Describes functionality that is common to both standalone redis servers and redis clusters - /// - public interface IDatabase : IRedis, IDatabaseAsync - { - - /// - /// The numeric identifier of this database - /// - int Database { get; } - - /// - /// Allows creation of a group of operations that will be sent to the server as a single unit, - /// but which may or may not be processed on the server contiguously. - /// - [IgnoreNamePrefix] - IBatch CreateBatch(object asyncState = null); - - - /// - /// Atomically transfer a key from a source Redis instance to a destination Redis instance. On success the key is deleted from the original instance by default, and is guaranteed to exist in the target instance. - /// - /// http://redis.io/commands/MIGRATE - void KeyMigrate(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None); - - - /// - /// Allows creation of a group of operations that will be sent to the server as a single unit, - /// and processed on the server as a single unit. - /// - [IgnoreNamePrefix] - ITransaction CreateTransaction(object asyncState = null); - - /// - /// Returns the raw DEBUG OBJECT output for a key; this command is not fully documented and should be avoided unless you have good reason, and then avoided anyway. - /// - /// http://redis.io/commands/debug-object - RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Add the specified member to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members. - /// - /// True if the specified member was not already present in the set, else False - /// http://redis.io/commands/geoadd - bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None); - - - /// - /// Add the specified member to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members. - /// - /// True if the specified member was not already present in the set, else False - /// http://redis.io/commands/geoadd - bool GeoAdd(RedisKey key, StackExchange.Redis.GeoEntry value, CommandFlags flags = CommandFlags.None); - - - /// - /// Add the specified members to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members. - /// - /// the number of elements that were added to the set, not including all the elements already present into the set. - /// http://redis.io/commands/geoadd - long GeoAdd(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified member from the geo sorted set stored at key. Non existing members are ignored. - /// - /// True if the member existed in the sorted set and was removed; False otherwise. - /// http://redis.io/commands/zrem - bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); - - /// - /// Return the distance between two members in the geospatial index represented by the sorted set. - /// - /// The command returns the distance as a double (represented as a string) in the specified unit, or NULL if one or both the elements are missing. - /// http://redis.io/commands/geodist - double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None); - - /// - /// Return valid Geohash strings representing the position of one or more elements in a sorted set value representing a geospatial index (where elements were added using GEOADD). - /// - /// The command returns an array where each element is the Geohash corresponding to each member name passed as argument to the command. - /// http://redis.io/commands/geohash - string[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); - - /// - /// Return valid Geohash strings representing the position of one or more elements in a sorted set value representing a geospatial index (where elements were added using GEOADD). - /// - /// The command returns an array where each element is the Geohash corresponding to each member name passed as argument to the command. - /// http://redis.io/commands/geohash - string GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); - - - /// - /// Return the positions (longitude,latitude) of all the specified members of the geospatial index represented by the sorted set at key. - /// - /// The command returns an array where each element is a two elements array representing longitude and latitude (x,y) of each member name passed as argument to the command.Non existing elements are reported as NULL elements of the array. - /// http://redis.io/commands/geopos - GeoPosition?[] GeoPosition(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); - - /// - /// Return the positions (longitude,latitude) of all the specified members of the geospatial index represented by the sorted set at key. - /// - /// The command returns an array where each element is a two elements array representing longitude and latitude (x,y) of each member name passed as argument to the command.Non existing elements are reported as NULL elements of the array. - /// http://redis.io/commands/geopos - GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); - - /// - /// Return the members of a sorted set populated with geospatial information using GEOADD, which are within the borders of the area specified with the center location and the maximum distance from the center (the radius). - /// - /// GeoRadiusResult[] - /// http://redis.io/commands/georadius - GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None); - - /// - /// Return the members of a sorted set populated with geospatial information using GEOADD, which are within the borders of the area specified with the center location and the maximum distance from the center (the radius). - /// - /// GeoRadiusResult[] - /// http://redis.io/commands/georadius - GeoRadiusResult[] GeoRadius(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None); - - /// - /// Decrements the number stored at field in the hash stored at key by decrement. If key does not exist, a new key holding a hash is created. If field does not exist or holds a string that cannot be interpreted as integer, the value is set to 0 before the operation is performed. - /// - /// The range of values supported by HINCRBY is limited to 64 bit signed integers. - /// the value at field after the decrement operation. - /// http://redis.io/commands/hincrby - long HashDecrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None); - - /// - /// Decrement the specified field of an hash stored at key, and representing a floating point number, by the specified decrement. If the field does not exist, it is set to 0 before performing the operation. - /// - /// The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. - /// the value at field after the decrement operation. - /// http://redis.io/commands/hincrbyfloat - double HashDecrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified fields from the hash stored at key. Non-existing fields are ignored. Non-existing keys are treated as empty hashes and this command returns 0. - /// - /// http://redis.io/commands/hdel - /// The number of fields that were removed. - bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified fields from the hash stored at key. Non-existing fields are ignored. Non-existing keys are treated as empty hashes and this command returns 0. - /// - /// http://redis.io/commands/hdel - /// The number of fields that were removed. - long HashDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); - - /// - /// Returns if field is an existing field in the hash stored at key. - /// - /// 1 if the hash contains field. 0 if the hash does not contain field, or key does not exist. - /// http://redis.io/commands/hexists - bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the value associated with field in the hash stored at key. - /// - /// the value associated with field, or nil when field is not present in the hash or key does not exist. - /// http://redis.io/commands/hget - RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the values associated with the specified fields in the hash stored at key. - /// For every field that does not exist in the hash, a nil value is returned.Because a non-existing keys are treated as empty hashes, running HMGET against a non-existing key will return a list of nil values. - /// - /// list of values associated with the given fields, in the same order as they are requested. - /// http://redis.io/commands/hmget - RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); - - /// - /// Returns all fields and values of the hash stored at key. - /// - /// list of fields and their values stored in the hash, or an empty list when key does not exist. - /// http://redis.io/commands/hgetall - HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Increments the number stored at field in the hash stored at key by increment. If key does not exist, a new key holding a hash is created. If field does not exist or holds a string that cannot be interpreted as integer, the value is set to 0 before the operation is performed. - /// - /// The range of values supported by HINCRBY is limited to 64 bit signed integers. - /// the value at field after the increment operation. - /// http://redis.io/commands/hincrby - long HashIncrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None); - - /// - /// Increment the specified field of an hash stored at key, and representing a floating point number, by the specified increment. If the field does not exist, it is set to 0 before performing the operation. - /// - /// The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. - /// the value at field after the increment operation. - /// http://redis.io/commands/hincrbyfloat - double HashIncrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None); - - /// - /// Returns all field names in the hash stored at key. - /// - /// list of fields in the hash, or an empty list when key does not exist. - /// http://redis.io/commands/hkeys - RedisValue[] HashKeys(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the number of fields contained in the hash stored at key. - /// - /// number of fields in the hash, or 0 when key does not exist. - /// http://redis.io/commands/hlen - long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// The HSCAN command is used to incrementally iterate over a hash - /// - /// yields all elements of the hash. - /// http://redis.io/commands/hscan - IEnumerable HashScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags); - - /// - /// The HSCAN command is used to incrementally iterate over a hash; note: to resume an iteration via cursor, cast the original enumerable or enumerator to IScanningCursor. - /// - /// yields all elements of the hash. - /// http://redis.io/commands/hscan - IEnumerable HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None); - - /// - /// Sets the specified fields to their respective values in the hash stored at key. This command overwrites any existing fields in the hash. If key does not exist, a new key holding a hash is created. - /// - /// http://redis.io/commands/hmset - void HashSet(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None); - - /// - /// Sets field in the hash stored at key to value. If key does not exist, a new key holding a hash is created. If field already exists in the hash, it is overwritten. - /// - /// 1 if field is a new field in the hash and value was set. 0 if field already exists in the hash and the value was updated. - /// http://redis.io/commands/hset - /// http://redis.io/commands/hsetnx - bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Returns all values in the hash stored at key. - /// - /// list of values in the hash, or an empty list when key does not exist. - /// http://redis.io/commands/hvals - RedisValue[] HashValues(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Adds the element to the HyperLogLog data structure stored at the variable name specified as first argument. - /// - /// true if at least 1 HyperLogLog internal register was altered. false otherwise. - /// http://redis.io/commands/pfadd - bool HyperLogLogAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Adds all the element arguments to the HyperLogLog data structure stored at the variable name specified as first argument. - /// - /// true if at least 1 HyperLogLog internal register was altered. false otherwise. - /// http://redis.io/commands/pfadd - bool HyperLogLogAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the approximated cardinality computed by the HyperLogLog data structure stored at the specified variable, or 0 if the variable does not exist. - /// - /// The approximated number of unique elements observed via HyperLogLogAdd. - /// http://redis.io/commands/pfcount - long HyperLogLogLength(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the approximated cardinality of the union of the HyperLogLogs passed, by internally merging the HyperLogLogs stored at the provided keys into a temporary hyperLogLog, or 0 if the variable does not exist. - /// - /// The approximated number of unique elements observed via HyperLogLogAdd. - /// http://redis.io/commands/pfcount - long HyperLogLogLength(RedisKey[] keys, CommandFlags flags = CommandFlags.None); - - /// - /// Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of the observed Sets of the source HyperLogLog structures. - /// - /// http://redis.io/commands/pfmerge - void HyperLogLogMerge(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); - - /// - /// Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of the observed Sets of the source HyperLogLog structures. - /// - /// http://redis.io/commands/pfmerge - void HyperLogLogMerge(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None); - - /// - /// Inidicate exactly which redis server we are talking to - /// - [IgnoreNamePrefix] - EndPoint IdentifyEndpoint(RedisKey key = default(RedisKey), CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified key. A key is ignored if it does not exist. - /// - /// True if the key was removed. - /// http://redis.io/commands/del - bool KeyDelete(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified keys. A key is ignored if it does not exist. - /// - /// The number of keys that were removed. - /// http://redis.io/commands/del - long KeyDelete(RedisKey[] keys, CommandFlags flags = CommandFlags.None); - - /// - /// Serialize the value stored at key in a Redis-specific format and return it to the user. The returned value can be synthesized back into a Redis key using the RESTORE command. - /// - /// the serialized value. - /// http://redis.io/commands/dump - byte[] KeyDump(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns if key exists. - /// - /// 1 if the key exists. 0 if the key does not exist. - /// http://redis.io/commands/exists - bool KeyExists(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is said to be volatile in Redis terminology. - /// - /// If key is updated before the timeout has expired, then the timeout is removed as if the PERSIST command was invoked on key. - /// For Redis versions < 2.1.3, existing timeouts cannot be overwritten. So, if key already has an associated timeout, it will do nothing and return 0. Since Redis 2.1.3, you can update the timeout of a key. It is also possible to remove the timeout using the PERSIST command. See the page on key expiry for more information. - /// 1 if the timeout was set. 0 if key does not exist or the timeout could not be set. - /// http://redis.io/commands/expire - /// http://redis.io/commands/pexpire - /// http://redis.io/commands/persist - bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None); - - /// - /// Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is said to be volatile in Redis terminology. - /// - /// If key is updated before the timeout has expired, then the timeout is removed as if the PERSIST command was invoked on key. - /// For Redis versions < 2.1.3, existing timeouts cannot be overwritten. So, if key already has an associated timeout, it will do nothing and return 0. Since Redis 2.1.3, you can update the timeout of a key. It is also possible to remove the timeout using the PERSIST command. See the page on key expiry for more information. - /// 1 if the timeout was set. 0 if key does not exist or the timeout could not be set. - /// http://redis.io/commands/expireat - /// http://redis.io/commands/pexpireat - /// http://redis.io/commands/persist - bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None); - - /// - /// Move key from the currently selected database (see SELECT) to the specified destination database. When key already exists in the destination database, or it does not exist in the source database, it does nothing. It is possible to use MOVE as a locking primitive because of this. - /// - /// 1 if key was moved; 0 if key was not moved. - /// http://redis.io/commands/move - bool KeyMove(RedisKey key, int database, CommandFlags flags = CommandFlags.None); - - /// Remove the existing timeout on key, turning the key from volatile (a key with an expire set) to persistent (a key that will never expire as no timeout is associated). - /// 1 if the timeout was removed. 0 if key does not exist or does not have an associated timeout. - /// http://redis.io/commands/persist - bool KeyPersist(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Return a random key from the currently selected database. - /// - /// the random key, or nil when the database is empty. - /// http://redis.io/commands/randomkey - RedisKey KeyRandom(CommandFlags flags = CommandFlags.None); - - /// - /// Renames key to newkey. It returns an error when the source and destination names are the same, or when key does not exist. - /// - /// http://redis.io/commands/rename - /// http://redis.io/commands/renamenx - bool KeyRename(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Create a key associated with a value that is obtained by deserializing the provided serialized value (obtained via DUMP). - /// If ttl is 0 the key is created without any expire, otherwise the specified expire time(in milliseconds) is set. - /// - /// http://redis.io/commands/restore - void KeyRestore(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the remaining time to live of a key that has a timeout. This introspection capability allows a Redis client to check how many seconds a given key will continue to be part of the dataset. - /// - /// TTL, or nil when key does not exist or does not have a timeout. - /// http://redis.io/commands/ttl - TimeSpan? KeyTimeToLive(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the string representation of the type of the value stored at key. The different types that can be returned are: string, list, set, zset and hash. - /// - /// type of key, or none when key does not exist. - /// http://redis.io/commands/type - RedisType KeyType(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the element at index index in the list stored at key. The index is zero-based, so 0 means the first element, 1 the second element and so on. Negative indices can be used to designate elements starting at the tail of the list. Here, -1 means the last element, -2 means the penultimate and so forth. - /// - /// the requested element, or nil when index is out of range. - /// http://redis.io/commands/lindex - RedisValue ListGetByIndex(RedisKey key, long index, CommandFlags flags = CommandFlags.None); - - /// - /// Inserts value in the list stored at key either before or after the reference value pivot. - /// When key does not exist, it is considered an empty list and no operation is performed. - /// - /// the length of the list after the insert operation, or -1 when the value pivot was not found. - /// http://redis.io/commands/linsert - long ListInsertAfter(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Inserts value in the list stored at key either before or after the reference value pivot. - /// When key does not exist, it is considered an empty list and no operation is performed. - /// - /// the length of the list after the insert operation, or -1 when the value pivot was not found. - /// http://redis.io/commands/linsert - long ListInsertBefore(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Removes and returns the first element of the list stored at key. - /// - /// the value of the first element, or nil when key does not exist. - /// http://redis.io/commands/lpop - RedisValue ListLeftPop(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Insert the specified value at the head of the list stored at key. If key does not exist, it is created as empty list before performing the push operations. - /// - /// the length of the list after the push operations. - /// http://redis.io/commands/lpush - /// http://redis.io/commands/lpushx - long ListLeftPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Insert all the specified values at the head of the list stored at key. If key does not exist, it is created as empty list before performing the push operations. - /// Elements are inserted one after the other to the head of the list, from the leftmost element to the rightmost element. So for instance the command LPUSH mylist a b c will result into a list containing c as first element, b as second element and a as third element. - /// - /// the length of the list after the push operations. - /// http://redis.io/commands/lpush - long ListLeftPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the length of the list stored at key. If key does not exist, it is interpreted as an empty list and 0 is returned. - /// - /// the length of the list at key. - /// http://redis.io/commands/llen - long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the specified elements of the list stored at key. The offsets start and stop are zero-based indexes, with 0 being the first element of the list (the head of the list), 1 being the next element and so on. - /// These offsets can also be negative numbers indicating offsets starting at the end of the list.For example, -1 is the last element of the list, -2 the penultimate, and so on. - /// Note that if you have a list of numbers from 0 to 100, LRANGE list 0 10 will return 11 elements, that is, the rightmost item is included. - /// - /// list of elements in the specified range. - /// http://redis.io/commands/lrange - RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the first count occurrences of elements equal to value from the list stored at key. The count argument influences the operation in the following ways: - /// count > 0: Remove elements equal to value moving from head to tail. - /// count < 0: Remove elements equal to value moving from tail to head. - /// count = 0: Remove all elements equal to value. - /// - /// the number of removed elements. - /// http://redis.io/commands/lrem - long ListRemove(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None); - - /// - /// Removes and returns the last element of the list stored at key. - /// - /// http://redis.io/commands/rpop - RedisValue ListRightPop(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Atomically returns and removes the last element (tail) of the list stored at source, and pushes the element at the first element (head) of the list stored at destination. - /// - /// the element being popped and pushed. - /// http://redis.io/commands/rpoplpush - RedisValue ListRightPopLeftPush(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None); - - /// - /// Insert the specified value at the tail of the list stored at key. If key does not exist, it is created as empty list before performing the push operation. - /// - /// the length of the list after the push operation. - /// http://redis.io/commands/rpush - /// http://redis.io/commands/rpushx - long ListRightPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Insert all the specified values at the tail of the list stored at key. If key does not exist, it is created as empty list before performing the push operation. - /// Elements are inserted one after the other to the tail of the list, from the leftmost element to the rightmost element. So for instance the command RPUSH mylist a b c will result into a list containing a as first element, b as second element and c as third element. - /// - /// the length of the list after the push operation. - /// http://redis.io/commands/rpush - long ListRightPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); - - /// - /// Sets the list element at index to value. For more information on the index argument, see ListGetByIndex. An error is returned for out of range indexes. - /// - /// http://redis.io/commands/lset - void ListSetByIndex(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Trim an existing list so that it will contain only the specified range of elements specified. Both start and stop are zero-based indexes, where 0 is the first element of the list (the head), 1 the next element and so on. - /// For example: LTRIM foobar 0 2 will modify the list stored at foobar so that only the first three elements of the list will remain. - /// start and end can also be negative numbers indicating offsets from the end of the list, where -1 is the last element of the list, -2 the penultimate element and so on. - /// - /// http://redis.io/commands/ltrim - void ListTrim(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None); - - /// - /// Extends a lock, if the token value is correct - /// - bool LockExtend(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None); - - /// - /// Queries the token held against a lock - /// - RedisValue LockQuery(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Releases a lock, if the token value is correct - /// - bool LockRelease(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Takes a lock (specifying a token value) if it is not already taken - /// - bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None); - - /// - /// Posts a message to the given channel. - /// - /// the number of clients that received the message. - /// http://redis.io/commands/publish - long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None); - - /// - /// Execute an arbitrary command against the server; this is primarily intended for - /// executing modules, but may also be used to provide access to new features that lack - /// a direct API - /// - /// A dynamic representation of the command's result - RedisResult Execute(string command, params object[] args); - - /// - /// Execute an arbitrary command against the server; this is primarily intended for - /// executing modules, but may also be used to provide access to new features that lack - /// a direct API - /// - /// A dynamic representation of the command's result - RedisResult Execute(string command, ICollection args, CommandFlags flags = CommandFlags.None); - - - /// - /// Execute a Lua script against the server - /// - /// http://redis.io/commands/eval, http://redis.io/commands/evalsha - /// A dynamic representation of the script's result - RedisResult ScriptEvaluate(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None); - - /// - /// Execute a Lua script against the server using just the SHA1 hash - /// - /// http://redis.io/commands/evalsha - /// A dynamic representation of the script's result - RedisResult ScriptEvaluate(byte[] hash, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None); - - /// - /// Execute a lua script against the server, using previously prepared script. - /// Named parameters, if any, are provided by the `parameters` object. - /// - RedisResult ScriptEvaluate(LuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None); - - /// - /// Execute a lua script against the server, using previously prepared and loaded script. - /// This method sends only the SHA1 hash of the lua script to Redis. - /// Named parameters, if any, are provided by the `parameters` object. - /// - RedisResult ScriptEvaluate(LoadedLuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None); - - /// - /// Add the specified member to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members. - /// - /// True if the specified member was not already present in the set, else False - /// http://redis.io/commands/sadd - bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Add the specified members to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members. - /// - /// the number of elements that were added to the set, not including all the elements already present into the set. - /// http://redis.io/commands/sadd - long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the members of the set resulting from the specified operation against the given sets. - /// - /// list with members of the resulting set. - /// http://redis.io/commands/sunion - /// http://redis.io/commands/sinter - /// http://redis.io/commands/sdiff - RedisValue[] SetCombine(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the members of the set resulting from the specified operation against the given sets. - /// - /// list with members of the resulting set. - /// http://redis.io/commands/sunion - /// http://redis.io/commands/sinter - /// http://redis.io/commands/sdiff - RedisValue[] SetCombine(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None); - - /// - /// This command is equal to SetCombine, but instead of returning the resulting set, it is stored in destination. If destination already exists, it is overwritten. - /// - /// the number of elements in the resulting set. - /// http://redis.io/commands/sunionstore - /// http://redis.io/commands/sinterstore - /// http://redis.io/commands/sdiffstore - long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); - - /// - /// This command is equal to SetCombine, but instead of returning the resulting set, it is stored in destination. If destination already exists, it is overwritten. - /// - /// the number of elements in the resulting set. - /// http://redis.io/commands/sunionstore - /// http://redis.io/commands/sinterstore - /// http://redis.io/commands/sdiffstore - long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None); - - /// - /// Returns if member is a member of the set stored at key. - /// - /// 1 if the element is a member of the set. 0 if the element is not a member of the set, or if key does not exist. - /// http://redis.io/commands/sismember - bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the set cardinality (number of elements) of the set stored at key. - /// - /// the cardinality (number of elements) of the set, or 0 if key does not exist. - /// http://redis.io/commands/scard - long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns all the members of the set value stored at key. - /// - /// all elements of the set. - /// http://redis.io/commands/smembers - RedisValue[] SetMembers(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Move member from the set at source to the set at destination. This operation is atomic. In every given moment the element will appear to be a member of source or destination for other clients. - /// When the specified element already exists in the destination set, it is only removed from the source set. - /// - /// 1 if the element is moved. 0 if the element is not a member of source and no operation was performed. - /// http://redis.io/commands/smove - bool SetMove(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Removes and returns a random element from the set value stored at key. - /// - /// the removed element, or nil when key does not exist. - /// http://redis.io/commands/spop - RedisValue SetPop(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Return a random element from the set value stored at key. - /// - /// the randomly selected element, or nil when key does not exist - /// http://redis.io/commands/srandmember - RedisValue SetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Return an array of count distinct elements if count is positive. If called with a negative count the behavior changes and the command is allowed to return the same element multiple times. - /// In this case the numer of returned elements is the absolute value of the specified count. - /// - /// an array of elements, or an empty array when key does not exist - /// http://redis.io/commands/srandmember - RedisValue[] SetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None); - - /// - /// Remove the specified member from the set stored at key. Specified members that are not a member of this set are ignored. - /// - /// True if the specified member was already present in the set, else False - /// http://redis.io/commands/srem - bool SetRemove(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Remove the specified members from the set stored at key. Specified members that are not a member of this set are ignored. - /// - /// the number of members that were removed from the set, not including non existing members. - /// http://redis.io/commands/srem - long SetRemove(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); - - /// - /// The SSCAN command is used to incrementally iterate over set - /// - /// yields all elements of the set. - /// http://redis.io/commands/sscan - IEnumerable SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags); - - /// - /// The SSCAN command is used to incrementally iterate over set; note: to resume an iteration via cursor, cast the original enumerable or enumerator to IScanningCursor. - /// - /// yields all elements of the set. - /// http://redis.io/commands/sscan - IEnumerable SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None); - - /// - /// Sorts a list, set or sorted set (numerically or alphabetically, ascending by default); By default, the elements themselves are compared, but the values can also be - /// used to perform external key-lookups using the by parameter. By default, the elements themselves are returned, but external key-lookups (one or many) can - /// be performed instead by specifying the get parameter (note that # specifies the element itself, when used in get). - /// Referring to the redis SORT documentation for examples is recommended. When used in hashes, by and get - /// can be used to specify fields using -> notation (again, refer to redis documentation). - /// - /// http://redis.io/commands/sort - /// Returns the sorted elements, or the external values if get is specified - [IgnoreNamePrefix] - RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None); - - /// - /// Sorts a list, set or sorted set (numerically or alphabetically, ascending by default); By default, the elements themselves are compared, but the values can also be - /// used to perform external key-lookups using the by parameter. By default, the elements themselves are returned, but external key-lookups (one or many) can - /// be performed instead by specifying the get parameter (note that # specifies the element itself, when used in get). - /// Referring to the redis SORT documentation for examples is recommended. When used in hashes, by and get - /// can be used to specify fields using -> notation (again, refer to redis documentation). - /// - /// http://redis.io/commands/sort - /// Returns the number of elements stored in the new list - [IgnoreNamePrefix] - long SortAndStore(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None); - - /// - /// Adds the specified member with the specified score to the sorted set stored at key. If the specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. - /// - /// True if the value was added, False if it already existed (the score is still updated) - /// http://redis.io/commands/zadd - bool SortedSetAdd(RedisKey key, RedisValue member, double score, CommandFlags flags); - - /// - /// Adds the specified member with the specified score to the sorted set stored at key. If the specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. - /// - /// True if the value was added, False if it already existed (the score is still updated) - /// http://redis.io/commands/zadd - bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Adds all the specified members with the specified scores to the sorted set stored at key. If a specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. - /// - /// The number of elements added to the sorted sets, not including elements already existing for which the score was updated. - /// http://redis.io/commands/zadd - long SortedSetAdd(RedisKey key, SortedSetEntry[] values, CommandFlags flags); - - /// - /// Adds all the specified members with the specified scores to the sorted set stored at key. If a specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. - /// - /// The number of elements added to the sorted sets, not including elements already existing for which the score was updated. - /// http://redis.io/commands/zadd - long SortedSetAdd(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Computes a set operation over two sorted sets, and stores the result in destination, optionally performing - /// a specific aggregation (defaults to sum) - /// - /// http://redis.io/commands/zunionstore - /// http://redis.io/commands/zinterstore - /// the number of elements in the resulting sorted set at destination - long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None); - - /// - /// Computes a set operation over multiple sorted sets (optionally using per-set weights), and stores the result in destination, optionally performing - /// a specific aggregation (defaults to sum) - /// - /// http://redis.io/commands/zunionstore - /// http://redis.io/commands/zinterstore - /// the number of elements in the resulting sorted set at destination - long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None); - - /// - /// Decrements the score of member in the sorted set stored at key by decrement. If member does not exist in the sorted set, it is added with -decrement as its score (as if its previous score was 0.0). - /// - /// the new score of member - /// http://redis.io/commands/zincrby - double SortedSetDecrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None); - - /// - /// Increments the score of member in the sorted set stored at key by increment. If member does not exist in the sorted set, it is added with increment as its score (as if its previous score was 0.0). - /// - /// the new score of member - /// http://redis.io/commands/zincrby - double SortedSetIncrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the sorted set cardinality (number of elements) of the sorted set stored at key. - /// - /// the cardinality (number of elements) of the sorted set, or 0 if key does not exist. - /// http://redis.io/commands/zcard - long SortedSetLength(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); - - /// - /// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns the number of elements in the sorted set at key with a value between min and max. - /// - /// the number of elements in the specified score range. - /// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns all the elements in the sorted set at key with a value between min and max. - long SortedSetLengthByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. - /// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on. - /// - /// list of elements in the specified range - /// http://redis.io/commands/zrange - /// http://redis.io/commands/zrevrange - RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. - /// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on. - /// - /// list of elements in the specified range - /// http://redis.io/commands/zrange - /// http://redis.io/commands/zrevrange - SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); - - - - /// - /// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. - /// Start and stop are used to specify the min and max range for score values. Similar to other range methods the values are inclusive. - /// - /// list of elements in the specified score range - /// http://redis.io/commands/zrangebyscore - /// http://redis.io/commands/zrevrangebyscore - RedisValue[] SortedSetRangeByScore(RedisKey key, - double start = double.NegativeInfinity, double stop = double.PositiveInfinity, - Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, - CommandFlags flags = CommandFlags.None); - - /// - /// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. - /// Start and stop are used to specify the min and max range for score values. Similar to other range methods the values are inclusive. - /// - /// list of elements in the specified score range - /// http://redis.io/commands/zrangebyscore - /// http://redis.io/commands/zrevrangebyscore - SortedSetEntry[] SortedSetRangeByScoreWithScores(RedisKey key, - double start = double.NegativeInfinity, double stop = double.PositiveInfinity, - Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, - CommandFlags flags = CommandFlags.None); - - /// - /// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns all the elements in the sorted set at key with a value between min and max. - /// - /// http://redis.io/commands/zrangebylex - /// list of elements in the specified score range. - RedisValue[] SortedSetRangeByValue(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue), - Exclude exclude = Exclude.None, long skip = 0, long take = -1, - CommandFlags flags = CommandFlags.None); - - /// - /// Returns the rank of member in the sorted set stored at key, by default with the scores ordered from low to high. The rank (or index) is 0-based, which means that the member with the lowest score has rank 0. - /// - /// If member exists in the sorted set, the rank of member; If member does not exist in the sorted set or key does not exist, null - /// http://redis.io/commands/zrank - /// http://redis.io/commands/zrevrank - long? SortedSetRank(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified member from the sorted set stored at key. Non existing members are ignored. - /// - /// True if the member existed in the sorted set and was removed; False otherwise. - /// http://redis.io/commands/zrem - bool SortedSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified members from the sorted set stored at key. Non existing members are ignored. - /// - /// The number of members removed from the sorted set, not including non existing members. - /// http://redis.io/commands/zrem - long SortedSetRemove(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); - - /// - /// Removes all elements in the sorted set stored at key with rank between start and stop. Both start and stop are 0 -based indexes with 0 being the element with the lowest score. These indexes can be negative numbers, where they indicate offsets starting at the element with the highest score. For example: -1 is the element with the highest score, -2 the element with the second highest score and so forth. - /// - /// the number of elements removed. - /// http://redis.io/commands/zremrangebyrank - long SortedSetRemoveRangeByRank(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None); - - /// - /// Removes all elements in the sorted set stored at key with a score between min and max (inclusive by default). - /// - /// the number of elements removed. - /// http://redis.io/commands/zremrangebyscore - long SortedSetRemoveRangeByScore(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); - - /// - /// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command removes all elements in the sorted set stored at key between the lexicographical range specified by min and max. - /// - /// http://redis.io/commands/zremrangebylex - /// the number of elements removed. - long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); - /// - /// The ZSCAN command is used to incrementally iterate over a sorted set - /// - /// yields all elements of the sorted set. - /// http://redis.io/commands/zscan - IEnumerable SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags); - - /// - /// The ZSCAN command is used to incrementally iterate over a sorted set; note: to resume an iteration via cursor, cast the original enumerable or enumerator to IScanningCursor. - /// - /// yields all elements of the sorted set. - /// http://redis.io/commands/zscan - IEnumerable SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None); - /// - /// Returns the score of member in the sorted set at key; If member does not exist in the sorted set, or key does not exist, nil is returned. - /// - /// the score of member - /// http://redis.io/commands/zscore - double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); - - - - - - /// - /// If key already exists and is a string, this command appends the value at the end of the string. If key does not exist it is created and set as an empty string, - /// so APPEND will be similar to SET in this special case. - /// - /// the length of the string after the append operation. - /// http://redis.io/commands/append - long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Count the number of set bits (population counting) in a string. - /// By default all the bytes contained in the string are examined.It is possible to specify the counting operation only in an interval passing the additional arguments start and end. - /// Like for the GETRANGE command start and end can contain negative values in order to index bytes starting from the end of the string, where -1 is the last byte, -2 is the penultimate, and so forth. - /// - /// The number of bits set to 1 - /// http://redis.io/commands/bitcount - long StringBitCount(RedisKey key, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None); - - /// - /// Perform a bitwise operation between multiple keys (containing string values) and store the result in the destination key. - /// The BITOP command supports four bitwise operations; note that NOT is a unary operator: the second key should be omitted in this case - /// and only the first key will be considered. - /// The result of the operation is always stored at destkey. - /// - /// The size of the string stored in the destination key, that is equal to the size of the longest input string. - /// http://redis.io/commands/bitop - long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default(RedisKey), CommandFlags flags = CommandFlags.None); - - /// - /// Perform a bitwise operation between multiple keys (containing string values) and store the result in the destination key. - /// The BITOP command supports four bitwise operations; note that NOT is a unary operator. - /// The result of the operation is always stored at destkey. - /// - /// The size of the string stored in the destination key, that is equal to the size of the longest input string. - /// http://redis.io/commands/bitop - long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None); - - /// - /// Return the position of the first bit set to 1 or 0 in a string. - /// The position is returned thinking at the string as an array of bits from left to right where the first byte most significant bit is at position 0, the second byte most significant bit is at position 8 and so forth. - /// An start and end may be specified; these are in bytes, not bits; start and end can contain negative values in order to index bytes starting from the end of the string, where -1 is the last byte, -2 is the penultimate, and so forth. - /// - /// The command returns the position of the first bit set to 1 or 0 according to the request. - /// If we look for set bits(the bit argument is 1) and the string is empty or composed of just zero bytes, -1 is returned. - /// http://redis.io/commands/bitpos - long StringBitPosition(RedisKey key, bool bit, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None); - - /// - /// Decrements the number stored at key by decrement. If the key does not exist, it is set to 0 before performing the operation. - /// An error is returned if the key contains a value of the wrong type or contains a string that is not representable as integer. This operation is limited to 64 bit signed integers. - /// - /// the value of key after the decrement - /// http://redis.io/commands/decrby - /// http://redis.io/commands/decr - long StringDecrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None); - - /// - /// Decrements the string representing a floating point number stored at key by the specified decrement. If the key does not exist, it is set to 0 before performing the operation. The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. - /// - /// the value of key after the decrement - /// http://redis.io/commands/incrbyfloat - double StringDecrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None); - - /// - /// Get the value of key. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values. - /// - /// the value of key, or nil when key does not exist. - /// http://redis.io/commands/get - RedisValue StringGet(RedisKey key, CommandFlags flags = CommandFlags.None); - /// - /// Returns the values of all specified keys. For every key that does not hold a string value or does not exist, the special value nil is returned. - /// - /// http://redis.io/commands/mget - RedisValue[] StringGet(RedisKey[] keys, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the bit value at offset in the string value stored at key. - /// When offset is beyond the string length, the string is assumed to be a contiguous space with 0 bits. - /// - /// the bit value stored at offset. - /// http://redis.io/commands/getbit - bool StringGetBit(RedisKey key, long offset, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive). Negative offsets can be used in order to provide an offset starting from the end of the string. So -1 means the last character, -2 the penultimate and so forth. - /// - /// the substring of the string value stored at key - /// http://redis.io/commands/getrange - RedisValue StringGetRange(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None); - - /// - /// Atomically sets key to value and returns the old value stored at key. - /// - /// http://redis.io/commands/getset - /// the old value stored at key, or nil when key did not exist. - RedisValue StringGetSet(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Get the value of key. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values. - /// - /// the value of key, or nil when key does not exist. - /// http://redis.io/commands/get - RedisValueWithExpiry StringGetWithExpiry(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Increments the number stored at key by increment. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that is not representable as integer. This operation is limited to 64 bit signed integers. - /// - /// the value of key after the increment - /// http://redis.io/commands/incrby - /// http://redis.io/commands/incr - long StringIncrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None); - - /// - /// Increments the string representing a floating point number stored at key by the specified increment. If the key does not exist, it is set to 0 before performing the operation. The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. - /// - /// the value of key after the increment - /// http://redis.io/commands/incrbyfloat - double StringIncrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the length of the string value stored at key. - /// - /// the length of the string at key, or 0 when key does not exist. - /// http://redis.io/commands/strlen - long StringLength(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type. - /// - /// http://redis.io/commands/set - bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None); - /// - /// Sets the given keys to their respective values. If "not exists" is specified, this will not perform any operation at all even if just a single key already exists. - /// - /// True if the keys were set, else False - /// http://redis.io/commands/mset - /// http://redis.io/commands/msetnx - bool StringSet(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Sets or clears the bit at offset in the string value stored at key. - /// The bit is either set or cleared depending on value, which can be either 0 or 1. When key does not exist, a new string value is created.The string is grown to make sure it can hold a bit at offset. - /// - /// the original bit value stored at offset. - /// http://redis.io/commands/setbit - bool StringSetBit(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None); - /// - /// Overwrites part of the string stored at key, starting at the specified offset, for the entire length of value. If the offset is larger than the current length of the string at key, the string is padded with zero-bytes to make offset fit. Non-existing keys are considered as empty strings, so this command will make sure it holds a string large enough to be able to set value at offset. - /// - /// the length of the string after it was modified by the command. - /// http://redis.io/commands/setrange - RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None); - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/IDatabaseAsync.cs b/StackExchange.Redis/StackExchange/Redis/IDatabaseAsync.cs deleted file mode 100644 index c8af3eae2..000000000 --- a/StackExchange.Redis/StackExchange/Redis/IDatabaseAsync.cs +++ /dev/null @@ -1,1040 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - /// - /// Describes functionality that is common to both standalone redis servers and redis clusters - /// - public interface IDatabaseAsync : IRedisAsync - { - /// - /// Returns the raw DEBUG OBJECT output for a key; this command is not fully documented and should be avoided unless you have good reason, and then avoided anyway. - /// - /// http://redis.io/commands/debug-object - Task DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Add the specified member to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members. - /// - /// True if the specified member was not already present in the set, else False - /// http://redis.io/commands/geoadd - Task GeoAddAsync(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None); - - - /// - /// Add the specified member to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members. - /// - /// True if the specified member was not already present in the set, else False - /// http://redis.io/commands/geoadd - Task GeoAddAsync(RedisKey key, StackExchange.Redis.GeoEntry value, CommandFlags flags = CommandFlags.None); - - - /// - /// Add the specified members to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members. - /// - /// the number of elements that were added to the set, not including all the elements already present into the set. - /// http://redis.io/commands/geoadd - Task GeoAddAsync(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified member from the geo sorted set stored at key. Non existing members are ignored. - /// - /// True if the member existed in the sorted set and was removed; False otherwise. - /// http://redis.io/commands/zrem - Task GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); - - /// - /// Return the distance between two members in the geospatial index represented by the sorted set. - /// - /// The command returns the distance as a double (represented as a string) in the specified unit, or NULL if one or both the elements are missing. - /// http://redis.io/commands/geodist - Task GeoDistanceAsync(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None); - - /// - /// Return valid Geohash strings representing the position of one or more elements in a sorted set value representing a geospatial index (where elements were added using GEOADD). - /// - /// The command returns an array where each element is the Geohash corresponding to each member name passed as argument to the command. - /// http://redis.io/commands/geohash - Task GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); - - /// - /// Return valid Geohash strings representing the position of one or more elements in a sorted set value representing a geospatial index (where elements were added using GEOADD). - /// - /// The command returns an array where each element is the Geohash corresponding to each member name passed as argument to the command. - /// http://redis.io/commands/geohash - Task GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); - - - /// - /// Return the positions (longitude,latitude) of all the specified members of the geospatial index represented by the sorted set at key. - /// - /// The command returns an array where each element is a two elements array representing longitude and latitude (x,y) of each member name passed as argument to the command.Non existing elements are reported as NULL elements of the array. - /// http://redis.io/commands/geopos - Task GeoPositionAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); - - /// - /// Return the positions (longitude,latitude) of all the specified members of the geospatial index represented by the sorted set at key. - /// - /// The command returns an array where each element is a two elements array representing longitude and latitude (x,y) of each member name passed as argument to the command.Non existing elements are reported as NULL elements of the array. - /// http://redis.io/commands/geopos - Task GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); - - /// - /// Return the members of a sorted set populated with geospatial information using GEOADD, which are within the borders of the area specified with the center location and the maximum distance from the center (the radius). - /// - /// GeoRadiusResult[] - /// http://redis.io/commands/georadius - Task GeoRadiusAsync(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None); - - /// - /// Return the members of a sorted set populated with geospatial information using GEOADD, which are within the borders of the area specified with the center location and the maximum distance from the center (the radius). - /// - /// GeoRadiusResult[] - /// http://redis.io/commands/georadius - Task GeoRadiusAsync(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None); - - - /// - /// Increments the number stored at field in the hash stored at key by increment. If key does not exist, a new key holding a hash is created. If field does not exist or holds a string that cannot be interpreted as integer, the value is set to 0 before the operation is performed. - /// - /// The range of values supported by HINCRBY is limited to 64 bit signed integers. - /// the value at field after the increment operation. - /// http://redis.io/commands/hincrby - Task HashDecrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None); - - /// - /// Decrement the specified field of an hash stored at key, and representing a floating point number, by the specified decrement. If the field does not exist, it is set to 0 before performing the operation. - /// - /// The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. - /// the value at field after the decrement operation. - /// http://redis.io/commands/hincrbyfloat - Task HashDecrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified fields from the hash stored at key. Non-existing fields are ignored. Non-existing keys are treated as empty hashes and this command returns 0. - /// - /// http://redis.io/commands/hdel - /// The number of fields that were removed. - Task HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified fields from the hash stored at key. Non-existing fields are ignored. Non-existing keys are treated as empty hashes and this command returns 0. - /// - /// http://redis.io/commands/hdel - /// The number of fields that were removed. - Task HashDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); - - /// - /// Returns if field is an existing field in the hash stored at key. - /// - /// 1 if the hash contains field. 0 if the hash does not contain field, or key does not exist. - /// http://redis.io/commands/hexists - Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); - - /// - /// Returns all fields and values of the hash stored at key. - /// - /// list of fields and their values stored in the hash, or an empty list when key does not exist. - /// http://redis.io/commands/hgetall - Task HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the value associated with field in the hash stored at key. - /// - /// the value associated with field, or nil when field is not present in the hash or key does not exist. - /// http://redis.io/commands/hget - Task HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the values associated with the specified fields in the hash stored at key. - /// For every field that does not exist in the hash, a nil value is returned.Because a non-existing keys are treated as empty hashes, running HMGET against a non-existing key will return a list of nil values. - /// - /// list of values associated with the given fields, in the same order as they are requested. - /// http://redis.io/commands/hmget - Task HashGetAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); - - /// - /// Increments the number stored at field in the hash stored at key by increment. If key does not exist, a new key holding a hash is created. If field does not exist or holds a string that cannot be interpreted as integer, the value is set to 0 before the operation is performed. - /// - /// The range of values supported by HINCRBY is limited to 64 bit signed integers. - /// the value at field after the increment operation. - /// http://redis.io/commands/hincrby - Task HashIncrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None); - - /// - /// Increment the specified field of an hash stored at key, and representing a floating point number, by the specified increment. If the field does not exist, it is set to 0 before performing the operation. - /// - /// The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. - /// the value at field after the increment operation. - /// http://redis.io/commands/hincrbyfloat - Task HashIncrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None); - - /// - /// Returns all field names in the hash stored at key. - /// - /// list of fields in the hash, or an empty list when key does not exist. - /// http://redis.io/commands/hkeys - Task HashKeysAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the number of fields contained in the hash stored at key. - /// - /// number of fields in the hash, or 0 when key does not exist. - /// http://redis.io/commands/hlen - Task HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Sets the specified fields to their respective values in the hash stored at key. This command overwrites any existing fields in the hash. If key does not exist, a new key holding a hash is created. - /// - /// http://redis.io/commands/hmset - Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None); - - /// - /// Sets field in the hash stored at key to value. If key does not exist, a new key holding a hash is created. If field already exists in the hash, it is overwritten. - /// - /// 1 if field is a new field in the hash and value was set. 0 if field already exists in the hash and the value was updated. - /// http://redis.io/commands/hset - /// http://redis.io/commands/hsetnx - Task HashSetAsync(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Returns all values in the hash stored at key. - /// - /// list of values in the hash, or an empty list when key does not exist. - /// http://redis.io/commands/hvals - Task HashValuesAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Adds the element to the HyperLogLog data structure stored at the variable name specified as first argument. - /// - /// true if at least 1 HyperLogLog internal register was altered. false otherwise. - /// http://redis.io/commands/pfadd - Task HyperLogLogAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Adds all the element arguments to the HyperLogLog data structure stored at the variable name specified as first argument. - /// - /// true if at least 1 HyperLogLog internal register was altered. false otherwise. - /// http://redis.io/commands/pfadd - Task HyperLogLogAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the approximated cardinality computed by the HyperLogLog data structure stored at the specified variable, or 0 if the variable does not exist. - /// - /// The approximated number of unique elements observed via HyperLogLogAdd. - /// http://redis.io/commands/pfcount - Task HyperLogLogLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the approximated cardinality of the union of the HyperLogLogs passed, by internally merging the HyperLogLogs stored at the provided keys into a temporary hyperLogLog, or 0 if the variable does not exist. - /// - /// The approximated number of unique elements observed via HyperLogLogAdd. - /// http://redis.io/commands/pfcount - Task HyperLogLogLengthAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None); - - /// - /// Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of the observed Sets of the source HyperLogLog structures. - /// - /// http://redis.io/commands/pfmerge - Task HyperLogLogMergeAsync(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); - - /// - /// Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of the observed Sets of the source HyperLogLog structures. - /// - /// http://redis.io/commands/pfmerge - Task HyperLogLogMergeAsync(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None); - - /// - /// Inidicate exactly which redis server we are talking to - /// - [IgnoreNamePrefix] - Task IdentifyEndpointAsync(RedisKey key = default(RedisKey), CommandFlags flags = CommandFlags.None); - - /// - /// Indicates whether the instance can communicate with the server (resolved - /// using the supplied key and optional flags) - /// - [IgnoreNamePrefix(true)] - bool IsConnected(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified key. A key is ignored if it does not exist. - /// - /// True if the key was removed. - /// http://redis.io/commands/del - Task KeyDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified keys. A key is ignored if it does not exist. - /// - /// The number of keys that were removed. - /// http://redis.io/commands/del - Task KeyDeleteAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None); - - /// - /// Serialize the value stored at key in a Redis-specific format and return it to the user. The returned value can be synthesized back into a Redis key using the RESTORE command. - /// - /// the serialized value. - /// http://redis.io/commands/dump - Task KeyDumpAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns if key exists. - /// - /// 1 if the key exists. 0 if the key does not exist. - /// http://redis.io/commands/exists - Task KeyExistsAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is said to be volatile in Redis terminology. - /// - /// If key is updated before the timeout has expired, then the timeout is removed as if the PERSIST command was invoked on key. - /// For Redis versions < 2.1.3, existing timeouts cannot be overwritten. So, if key already has an associated timeout, it will do nothing and return 0. Since Redis 2.1.3, you can update the timeout of a key. It is also possible to remove the timeout using the PERSIST command. See the page on key expiry for more information. - /// 1 if the timeout was set. 0 if key does not exist or the timeout could not be set. - /// http://redis.io/commands/expire - /// http://redis.io/commands/pexpire - /// http://redis.io/commands/persist - Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None); - - /// - /// Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is said to be volatile in Redis terminology. - /// - /// If key is updated before the timeout has expired, then the timeout is removed as if the PERSIST command was invoked on key. - /// For Redis versions < 2.1.3, existing timeouts cannot be overwritten. So, if key already has an associated timeout, it will do nothing and return 0. Since Redis 2.1.3, you can update the timeout of a key. It is also possible to remove the timeout using the PERSIST command. See the page on key expiry for more information. - /// 1 if the timeout was set. 0 if key does not exist or the timeout could not be set. - /// http://redis.io/commands/expireat - /// http://redis.io/commands/pexpireat - /// http://redis.io/commands/persist - Task KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None); - - - /// - /// Atomically transfer a key from a source Redis instance to a destination Redis instance. On success the key is deleted from the original instance by default, and is guaranteed to exist in the target instance. - /// - /// http://redis.io/commands/MIGRATE - Task KeyMigrateAsync(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None); - - /// - /// Move key from the currently selected database (see SELECT) to the specified destination database. When key already exists in the destination database, or it does not exist in the source database, it does nothing. It is possible to use MOVE as a locking primitive because of this. - /// - /// 1 if key was moved; 0 if key was not moved. - /// http://redis.io/commands/move - Task KeyMoveAsync(RedisKey key, int database, CommandFlags flags = CommandFlags.None); - - /// Remove the existing timeout on key, turning the key from volatile (a key with an expire set) to persistent (a key that will never expire as no timeout is associated). - /// 1 if the timeout was removed. 0 if key does not exist or does not have an associated timeout. - /// http://redis.io/commands/persist - Task KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Return a random key from the currently selected database. - /// - /// the random key, or nil when the database is empty. - /// http://redis.io/commands/randomkey - Task KeyRandomAsync(CommandFlags flags = CommandFlags.None); - - /// - /// Renames key to newkey. It returns an error when the source and destination names are the same, or when key does not exist. - /// - /// http://redis.io/commands/rename - /// http://redis.io/commands/renamenx - Task KeyRenameAsync(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Create a key associated with a value that is obtained by deserializing the provided serialized value (obtained via DUMP). - /// If ttl is 0 the key is created without any expire, otherwise the specified expire time(in milliseconds) is set. - /// - /// http://redis.io/commands/restore - Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the remaining time to live of a key that has a timeout. This introspection capability allows a Redis client to check how many seconds a given key will continue to be part of the dataset. - /// - /// TTL, or nil when key does not exist or does not have a timeout. - /// http://redis.io/commands/ttl - Task KeyTimeToLiveAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the string representation of the type of the value stored at key. The different types that can be returned are: string, list, set, zset and hash. - /// - /// type of key, or none when key does not exist. - /// http://redis.io/commands/type - Task KeyTypeAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the element at index index in the list stored at key. The index is zero-based, so 0 means the first element, 1 the second element and so on. Negative indices can be used to designate elements starting at the tail of the list. Here, -1 means the last element, -2 means the penultimate and so forth. - /// - /// the requested element, or nil when index is out of range. - /// http://redis.io/commands/lindex - Task ListGetByIndexAsync(RedisKey key, long index, CommandFlags flags = CommandFlags.None); - - /// - /// Inserts value in the list stored at key either before or after the reference value pivot. - /// When key does not exist, it is considered an empty list and no operation is performed. - /// - /// the length of the list after the insert operation, or -1 when the value pivot was not found. - /// http://redis.io/commands/linsert - Task ListInsertAfterAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Inserts value in the list stored at key either before or after the reference value pivot. - /// When key does not exist, it is considered an empty list and no operation is performed. - /// - /// the length of the list after the insert operation, or -1 when the value pivot was not found. - /// http://redis.io/commands/linsert - Task ListInsertBeforeAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Removes and returns the first element of the list stored at key. - /// - /// the value of the first element, or nil when key does not exist. - /// http://redis.io/commands/lpop - Task ListLeftPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Insert the specified value at the head of the list stored at key. If key does not exist, it is created as empty list before performing the push operations. - /// - /// the length of the list after the push operations. - /// http://redis.io/commands/lpush - /// http://redis.io/commands/lpushx - Task ListLeftPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Insert all the specified values at the head of the list stored at key. If key does not exist, it is created as empty list before performing the push operations. - /// Elements are inserted one after the other to the head of the list, from the leftmost element to the rightmost element. So for instance the command LPUSH mylist a b c will result into a list containing c as first element, b as second element and a as third element. - /// - /// the length of the list after the push operations. - /// http://redis.io/commands/lpush - Task ListLeftPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the length of the list stored at key. If key does not exist, it is interpreted as an empty list and 0 is returned. - /// - /// the length of the list at key. - /// http://redis.io/commands/llen - Task ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the specified elements of the list stored at key. The offsets start and stop are zero-based indexes, with 0 being the first element of the list (the head of the list), 1 being the next element and so on. - /// These offsets can also be negative numbers indicating offsets starting at the end of the list.For example, -1 is the last element of the list, -2 the penultimate, and so on. - /// Note that if you have a list of numbers from 0 to 100, LRANGE list 0 10 will return 11 elements, that is, the rightmost item is included. - /// - /// list of elements in the specified range. - /// http://redis.io/commands/lrange - Task ListRangeAsync(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the first count occurrences of elements equal to value from the list stored at key. The count argument influences the operation in the following ways: - /// count > 0: Remove elements equal to value moving from head to tail. - /// count < 0: Remove elements equal to value moving from tail to head. - /// count = 0: Remove all elements equal to value. - /// - /// the number of removed elements. - /// http://redis.io/commands/lrem - Task ListRemoveAsync(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None); - - /// - /// Removes and returns the last element of the list stored at key. - /// - /// http://redis.io/commands/rpop - Task ListRightPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Atomically returns and removes the last element (tail) of the list stored at source, and pushes the element at the first element (head) of the list stored at destination. - /// - /// the element being popped and pushed. - /// http://redis.io/commands/rpoplpush - Task ListRightPopLeftPushAsync(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None); - - /// - /// Insert the specified value at the tail of the list stored at key. If key does not exist, it is created as empty list before performing the push operation. - /// - /// the length of the list after the push operation. - /// http://redis.io/commands/rpush - /// http://redis.io/commands/rpushx - Task ListRightPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Insert all the specified values at the tail of the list stored at key. If key does not exist, it is created as empty list before performing the push operation. - /// Elements are inserted one after the other to the tail of the list, from the leftmost element to the rightmost element. So for instance the command RPUSH mylist a b c will result into a list containing a as first element, b as second element and c as third element. - /// - /// the length of the list after the push operation. - /// http://redis.io/commands/rpush - Task ListRightPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); - - /// - /// Sets the list element at index to value. For more information on the index argument, see ListGetByIndex. An error is returned for out of range indexes. - /// - /// http://redis.io/commands/lset - Task ListSetByIndexAsync(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Trim an existing list so that it will contain only the specified range of elements specified. Both start and stop are zero-based indexes, where 0 is the first element of the list (the head), 1 the next element and so on. - /// For example: LTRIM foobar 0 2 will modify the list stored at foobar so that only the first three elements of the list will remain. - /// start and end can also be negative numbers indicating offsets from the end of the list, where -1 is the last element of the list, -2 the penultimate element and so on. - /// - /// http://redis.io/commands/ltrim - Task ListTrimAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None); - - /// - /// Extends a lock, if the token value is correct - /// - Task LockExtendAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None); - - /// - /// Queries the token held against a lock - /// - Task LockQueryAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Releases a lock, if the token value is correct - /// - Task LockReleaseAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Takes a lock (specifying a token value) if it is not already taken - /// - Task LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None); - - /// - /// Posts a message to the given channel. - /// - /// the number of clients that received the message. - /// http://redis.io/commands/publish - Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None); - - /// - /// Execute a Lua script against the server - /// - /// http://redis.io/commands/eval, http://redis.io/commands/evalsha - /// A dynamic representation of the script's result - Task ScriptEvaluateAsync(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None); - - /// - /// Execute an arbitrary command against the server; this is primarily intended for - /// executing modules, but may also be used to provide access to new features that lack - /// a direct API - /// - /// A dynamic representation of the command's result - Task ExecuteAsync(string command, params object[] args); - - - /// - /// Execute an arbitrary command against the server; this is primarily intended for - /// executing modules, but may also be used to provide access to new features that lack - /// a direct API - /// - /// A dynamic representation of the command's result - Task ExecuteAsync(string command, ICollection args, CommandFlags flags = CommandFlags.None); - - /// - /// Execute a Lua script against the server using just the SHA1 hash - /// - /// http://redis.io/commands/evalsha - /// A dynamic representation of the script's result - Task ScriptEvaluateAsync(byte[] hash, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None); - - /// - /// Execute a lua script against the server, using previously prepared script. - /// Named parameters, if any, are provided by the `parameters` object. - /// - Task ScriptEvaluateAsync(LuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None); - - /// - /// Execute a lua script against the server, using previously prepared and loaded script. - /// This method sends only the SHA1 hash of the lua script to Redis. - /// Named parameters, if any, are provided by the `parameters` object. - /// - Task ScriptEvaluateAsync(LoadedLuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None); - - /// - /// Add the specified member to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members. - /// - /// True if the specified member was not already present in the set, else False - /// http://redis.io/commands/sadd - Task SetAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Add the specified members to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members. - /// - /// the number of elements that were added to the set, not including all the elements already present into the set. - /// http://redis.io/commands/sadd - Task SetAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); - - /// - /// This command is equal to SetCombine, but instead of returning the resulting set, it is stored in destination. If destination already exists, it is overwritten. - /// - /// the number of elements in the resulting set. - /// http://redis.io/commands/sunionstore - /// http://redis.io/commands/sinterstore - /// http://redis.io/commands/sdiffstore - Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); - - /// - /// This command is equal to SetCombine, but instead of returning the resulting set, it is stored in destination. If destination already exists, it is overwritten. - /// - /// the number of elements in the resulting set. - /// http://redis.io/commands/sunionstore - /// http://redis.io/commands/sinterstore - /// http://redis.io/commands/sdiffstore - Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the members of the set resulting from the specified operation against the given sets. - /// - /// list with members of the resulting set. - /// http://redis.io/commands/sunion - /// http://redis.io/commands/sinter - /// http://redis.io/commands/sdiff - Task SetCombineAsync(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the members of the set resulting from the specified operation against the given sets. - /// - /// list with members of the resulting set. - /// http://redis.io/commands/sunion - /// http://redis.io/commands/sinter - /// http://redis.io/commands/sdiff - Task SetCombineAsync(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None); - - /// - /// Returns if member is a member of the set stored at key. - /// - /// 1 if the element is a member of the set. 0 if the element is not a member of the set, or if key does not exist. - /// http://redis.io/commands/sismember - Task SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the set cardinality (number of elements) of the set stored at key. - /// - /// the cardinality (number of elements) of the set, or 0 if key does not exist. - /// http://redis.io/commands/scard - Task SetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Returns all the members of the set value stored at key. - /// - /// all elements of the set. - /// http://redis.io/commands/smembers - Task SetMembersAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Move member from the set at source to the set at destination. This operation is atomic. In every given moment the element will appear to be a member of source or destination for other clients. - /// When the specified element already exists in the destination set, it is only removed from the source set. - /// - /// 1 if the element is moved. 0 if the element is not a member of source and no operation was performed. - /// http://redis.io/commands/smove - Task SetMoveAsync(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Removes and returns a random element from the set value stored at key. - /// - /// the removed element, or nil when key does not exist. - /// http://redis.io/commands/spop - Task SetPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Return a random element from the set value stored at key. - /// - /// the randomly selected element, or nil when key does not exist - /// http://redis.io/commands/srandmember - Task SetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Return an array of count distinct elements if count is positive. If called with a negative count the behavior changes and the command is allowed to return the same element multiple times. - /// In this case the numer of returned elements is the absolute value of the specified count. - /// - /// an array of elements, or an empty array when key does not exist - /// http://redis.io/commands/srandmember - Task SetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None); - - /// - /// Remove the specified member from the set stored at key. Specified members that are not a member of this set are ignored. - /// - /// True if the specified member was already present in the set, else False - /// http://redis.io/commands/srem - Task SetRemoveAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Remove the specified members from the set stored at key. Specified members that are not a member of this set are ignored. - /// - /// the number of members that were removed from the set, not including non existing members. - /// http://redis.io/commands/srem - Task SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); - - /// - /// Sorts a list, set or sorted set (numerically or alphabetically, ascending by default); By default, the elements themselves are compared, but the values can also be - /// used to perform external key-lookups using the by parameter. By default, the elements themselves are returned, but external key-lookups (one or many) can - /// be performed instead by specifying the get parameter (note that # specifies the element itself, when used in get). - /// Referring to the redis SORT documentation for examples is recommended. When used in hashes, by and get - /// can be used to specify fields using -> notation (again, refer to redis documentation). - /// - /// http://redis.io/commands/sort - /// Returns the number of elements stored in the new list - [IgnoreNamePrefix] - Task SortAndStoreAsync(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None); - - /// - /// Sorts a list, set or sorted set (numerically or alphabetically, ascending by default); By default, the elements themselves are compared, but the values can also be - /// used to perform external key-lookups using the by parameter. By default, the elements themselves are returned, but external key-lookups (one or many) can - /// be performed instead by specifying the get parameter (note that # specifies the element itself, when used in get). - /// Referring to the redis SORT documentation for examples is recommended. When used in hashes, by and get - /// can be used to specify fields using -> notation (again, refer to redis documentation). - /// - /// http://redis.io/commands/sort - /// Returns the sorted elements, or the external values if get is specified - [IgnoreNamePrefix] - Task SortAsync(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None); - - /// - /// Adds the specified member with the specified score to the sorted set stored at key. If the specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. - /// - /// True if the value was added, False if it already existed (the score is still updated) - /// http://redis.io/commands/zadd - Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, CommandFlags flags); - - /// - /// Adds the specified member with the specified score to the sorted set stored at key. If the specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. - /// - /// True if the value was added, False if it already existed (the score is still updated) - /// http://redis.io/commands/zadd - Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Adds all the specified members with the specified scores to the sorted set stored at key. If a specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. - /// - /// The number of elements added to the sorted sets, not including elements already existing for which the score was updated. - /// http://redis.io/commands/zadd - Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, CommandFlags flags); - - /// - /// Adds all the specified members with the specified scores to the sorted set stored at key. If a specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. - /// - /// The number of elements added to the sorted sets, not including elements already existing for which the score was updated. - /// http://redis.io/commands/zadd - Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Computes a set operation over two sorted sets, and stores the result in destination, optionally performing - /// a specific aggregation (defaults to sum) - /// - /// http://redis.io/commands/zunionstore - /// http://redis.io/commands/zinterstore - /// the number of elements in the resulting sorted set at destination - Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None); - - /// - /// Computes a set operation over multiple sorted sets (optionally using per-set weights), and stores the result in destination, optionally performing - /// a specific aggregation (defaults to sum) - /// - /// http://redis.io/commands/zunionstore - /// http://redis.io/commands/zinterstore - /// the number of elements in the resulting sorted set at destination - Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None); - - /// - /// Decrements the score of member in the sorted set stored at key by decrement. If member does not exist in the sorted set, it is added with -decrement as its score (as if its previous score was 0.0). - /// - /// the new score of member - /// http://redis.io/commands/zincrby - Task SortedSetDecrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None); - - /// - /// Increments the score of member in the sorted set stored at key by increment. If member does not exist in the sorted set, it is added with increment as its score (as if its previous score was 0.0). - /// - /// the new score of member - /// http://redis.io/commands/zincrby - Task SortedSetIncrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the sorted set cardinality (number of elements) of the sorted set stored at key. - /// - /// the cardinality (number of elements) of the sorted set, or 0 if key does not exist. - /// http://redis.io/commands/zcard - Task SortedSetLengthAsync(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); - - /// - /// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns the number of elements in the sorted set at key with a value between min and max. - /// - /// the number of elements in the specified score range. - /// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns all the elements in the sorted set at key with a value between min and max. - Task SortedSetLengthByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. - /// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on. - /// - /// list of elements in the specified range - /// http://redis.io/commands/zrange - /// http://redis.io/commands/zrevrange - Task SortedSetRangeByRankAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. - /// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on. - /// - /// list of elements in the specified range - /// http://redis.io/commands/zrange - /// http://redis.io/commands/zrevrange - Task SortedSetRangeByRankWithScoresAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. - /// Start and stop are used to specify the min and max range for score values. Similar to other range methods the values are inclusive. - /// - /// list of elements in the specified score range - /// http://redis.io/commands/zrangebyscore - /// http://redis.io/commands/zrevrangebyscore - Task SortedSetRangeByScoreAsync(RedisKey key, - double start = double.NegativeInfinity, double stop = double.PositiveInfinity, - Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, - CommandFlags flags = CommandFlags.None); - - /// - /// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. - /// Start and stop are used to specify the min and max range for score values. Similar to other range methods the values are inclusive. - /// - /// list of elements in the specified score range - /// http://redis.io/commands/zrangebyscore - /// http://redis.io/commands/zrevrangebyscore - Task SortedSetRangeByScoreWithScoresAsync(RedisKey key, - double start = double.NegativeInfinity, double stop = double.PositiveInfinity, - Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, - CommandFlags flags = CommandFlags.None); - - /// - /// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns all the elements in the sorted set at key with a value between min and max. - /// - /// http://redis.io/commands/zrangebylex - /// list of elements in the specified score range. - Task SortedSetRangeByValueAsync(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue), - Exclude exclude = Exclude.None, long skip = 0, long take = -1, - CommandFlags flags = CommandFlags.None); - - /// - /// Returns the rank of member in the sorted set stored at key, by default with the scores ordered from low to high. The rank (or index) is 0-based, which means that the member with the lowest score has rank 0. - /// - /// If member exists in the sorted set, the rank of member; If member does not exist in the sorted set or key does not exist, null - /// http://redis.io/commands/zrank - /// http://redis.io/commands/zrevrank - Task SortedSetRankAsync(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified member from the sorted set stored at key. Non existing members are ignored. - /// - /// True if the member existed in the sorted set and was removed; False otherwise. - /// http://redis.io/commands/zrem - Task SortedSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); - - /// - /// Removes the specified members from the sorted set stored at key. Non existing members are ignored. - /// - /// The number of members removed from the sorted set, not including non existing members. - /// http://redis.io/commands/zrem - Task SortedSetRemoveAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); - - /// - /// Removes all elements in the sorted set stored at key with rank between start and stop. Both start and stop are 0 -based indexes with 0 being the element with the lowest score. These indexes can be negative numbers, where they indicate offsets starting at the element with the highest score. For example: -1 is the element with the highest score, -2 the element with the second highest score and so forth. - /// - /// the number of elements removed. - /// http://redis.io/commands/zremrangebyrank - Task SortedSetRemoveRangeByRankAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None); - - /// - /// Removes all elements in the sorted set stored at key with a score between min and max (inclusive by default). - /// - /// the number of elements removed. - /// http://redis.io/commands/zremrangebyscore - Task SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); - - /// - /// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command removes all elements in the sorted set stored at key between the lexicographical range specified by min and max. - /// - /// http://redis.io/commands/zremrangebylex - /// the number of elements removed. - Task SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); - /// - /// Returns the score of member in the sorted set at key; If member does not exist in the sorted set, or key does not exist, nil is returned. - /// - /// the score of member - /// http://redis.io/commands/zscore - Task SortedSetScoreAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); - - - /// - /// If key already exists and is a string, this command appends the value at the end of the string. If key does not exist it is created and set as an empty string, - /// so APPEND will be similar to SET in this special case. - /// - /// the length of the string after the append operation. - /// http://redis.io/commands/append - Task StringAppendAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Count the number of set bits (population counting) in a string. - /// By default all the bytes contained in the string are examined.It is possible to specify the counting operation only in an interval passing the additional arguments start and end. - /// Like for the GETRANGE command start and end can contain negative values in order to index bytes starting from the end of the string, where -1 is the last byte, -2 is the penultimate, and so forth. - /// - /// The number of bits set to 1 - /// http://redis.io/commands/bitcount - Task StringBitCountAsync(RedisKey key, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None); - - /// - /// Perform a bitwise operation between multiple keys (containing string values) and store the result in the destination key. - /// The BITOP command supports four bitwise operations; note that NOT is a unary operator: the second key should be omitted in this case - /// and only the first key will be considered. - /// The result of the operation is always stored at destkey. - /// - /// The size of the string stored in the destination key, that is equal to the size of the longest input string. - /// http://redis.io/commands/bitop - Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default(RedisKey), CommandFlags flags = CommandFlags.None); - - /// - /// Perform a bitwise operation between multiple keys (containing string values) and store the result in the destination key. - /// The BITOP command supports four bitwise operations; note that NOT is a unary operator. - /// The result of the operation is always stored at destkey. - /// - /// The size of the string stored in the destination key, that is equal to the size of the longest input string. - /// http://redis.io/commands/bitop - Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None); - - /// - /// Return the position of the first bit set to 1 or 0 in a string. - /// The position is returned thinking at the string as an array of bits from left to right where the first byte most significant bit is at position 0, the second byte most significant big is at position 8 and so forth. - /// An start and end may be specified; these are in bytes, not bits; start and end can contain negative values in order to index bytes starting from the end of the string, where -1 is the last byte, -2 is the penultimate, and so forth. - /// - /// The command returns the position of the first bit set to 1 or 0 according to the request. - /// If we look for set bits(the bit argument is 1) and the string is empty or composed of just zero bytes, -1 is returned. - /// http://redis.io/commands/bitpos - Task StringBitPositionAsync(RedisKey key, bool bit, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None); - - /// - /// Decrements the number stored at key by decrement. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that is not representable as integer. This operation is limited to 64 bit signed integers. - /// - /// the value of key after the increment - /// http://redis.io/commands/decrby - /// http://redis.io/commands/decr - Task StringDecrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None); - - /// - /// Decrements the string representing a floating point number stored at key by the specified increment. If the key does not exist, it is set to 0 before performing the operation. The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. - /// - /// the value of key after the increment - /// http://redis.io/commands/incrbyfloat - Task StringDecrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None); - - /// - /// Get the value of key. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values. - /// - /// the value of key, or nil when key does not exist. - /// http://redis.io/commands/get - Task StringGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - /// - /// Returns the values of all specified keys. For every key that does not hold a string value or does not exist, the special value nil is returned. - /// - /// http://redis.io/commands/mget - Task StringGetAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the bit value at offset in the string value stored at key. - /// When offset is beyond the string length, the string is assumed to be a contiguous space with 0 bits. - /// - /// the bit value stored at offset. - /// http://redis.io/commands/getbit - Task StringGetBitAsync(RedisKey key, long offset, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive). Negative offsets can be used in order to provide an offset starting from the end of the string. So -1 means the last character, -2 the penultimate and so forth. - /// - /// the substring of the string value stored at key - /// http://redis.io/commands/getrange - Task StringGetRangeAsync(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None); - - /// - /// Atomically sets key to value and returns the old value stored at key. - /// - /// http://redis.io/commands/getset - /// the old value stored at key, or nil when key did not exist. - Task StringGetSetAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Get the value of key. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values. - /// - /// the value of key, or nil when key does not exist. - /// http://redis.io/commands/get - Task StringGetWithExpiryAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Increments the number stored at key by increment. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that is not representable as integer. This operation is limited to 64 bit signed integers. - /// - /// the value of key after the increment - /// http://redis.io/commands/incrby - /// http://redis.io/commands/incr - Task StringIncrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None); - - /// - /// Increment the string representing a floating point number stored at key by the specified increment. If the key does not exist, it is set to 0 before performing the operation. The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. - /// - /// the value of key after the increment - /// http://redis.io/commands/incrbyfloat - Task StringIncrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None); - /// - /// Returns the length of the string value stored at key. - /// - /// the length of the string at key, or 0 when key does not exist. - /// http://redis.io/commands/strlen - Task StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); - - /// - /// Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type. - /// - /// http://redis.io/commands/set - Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None); - /// - /// Sets the given keys to their respective values. If "not exists" is specified, this will not perform any operation at all even if just a single key already exists. - /// - /// True if the keys were set, else False - /// http://redis.io/commands/mset - /// http://redis.io/commands/msetnx - Task StringSetAsync(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None); - - /// - /// Sets or clears the bit at offset in the string value stored at key. - /// The bit is either set or cleared depending on value, which can be either 0 or 1. When key does not exist, a new string value is created.The string is grown to make sure it can hold a bit at offset. - /// - /// the original bit value stored at offset. - /// http://redis.io/commands/setbit - Task StringSetBitAsync(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None); - /// - /// Overwrites part of the string stored at key, starting at the specified offset, for the entire length of value. If the offset is larger than the current length of the string at key, the string is padded with zero-bytes to make offset fit. Non-existing keys are considered as empty strings, so this command will make sure it holds a string large enough to be able to set value at offset. - /// - /// the length of the string after it was modified by the command. - /// http://redis.io/commands/setrange - Task StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None); - } - - - /// - /// Describes a value/expiry pair - /// - public struct RedisValueWithExpiry - { - internal RedisValueWithExpiry(RedisValue value, TimeSpan? expiry) - { - Value = value; - Expiry = expiry; - } - - /// - /// The expiry of this record - /// - public TimeSpan? Expiry { get; } - - /// - /// The value of this record - /// - public RedisValue Value { get; } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/IProfiler.cs b/StackExchange.Redis/StackExchange/Redis/IProfiler.cs deleted file mode 100644 index 63054f047..000000000 --- a/StackExchange.Redis/StackExchange/Redis/IProfiler.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Net; - -namespace StackExchange.Redis -{ - /// - /// If an IProfiledCommand is a retransmission of a previous command, this enum - /// is used to indicate what prompted the retransmission. - /// - /// This can be used to distinguish between transient causes (moving hashslots, joining nodes, etc.) - /// and incorrect routing. - /// - public enum RetransmissionReasonType - { - /// - /// No stated reason - /// - None = 0, - /// - /// Issued to investigate which node owns a key - /// - Ask, - /// - /// A node has indicated that it does *not* own the given key - /// - Moved - } - - /// - /// A profiled command against a redis instance. - /// - /// TimeSpans returned by this interface use a high precision timer if possible. - /// DateTimes returned by this interface are no more precise than DateTime.UtcNow. - /// - public interface IProfiledCommand - { - /// - /// The endpoint this command was sent to. - /// - EndPoint EndPoint { get; } - - /// - /// The Db this command was sent to. - /// - int Db { get; } - - /// - /// The name of this command. - /// - string Command { get; } - - /// - /// The CommandFlags the command was submitted with. - /// - CommandFlags Flags { get; } - - /// - /// When this command was *created*, will be approximately - /// when the paired method of StackExchange.Redis was called but - /// before that method returned. - /// - /// Note that the resolution of the returned DateTime is limited by DateTime.UtcNow. - /// - DateTime CommandCreated { get; } - - /// - /// How long this command waited to be added to the queue of pending - /// redis commands. A large TimeSpan indicates serious contention for - /// the pending queue. - /// - TimeSpan CreationToEnqueued { get; } - - /// - /// How long this command spent in the pending queue before being sent to redis. - /// A large TimeSpan can indicate a large number of pending events, large pending events, - /// or network issues. - /// - TimeSpan EnqueuedToSending { get; } - - /// - /// How long before Redis responded to this command and it's response could be handled after it was sent. - /// A large TimeSpan can indicate a large response body, an overtaxed redis instance, or network issues. - /// - TimeSpan SentToResponse { get; } - - /// - /// How long between Redis responding to this command and awaiting consumers being notified. - /// - TimeSpan ResponseToCompletion { get; } - - /// - /// How long it took this redis command to be processed, from creation to deserializing the final response. - /// - /// Note that this TimeSpan *does not* include time spent awaiting a Task in consumer code. - /// - TimeSpan ElapsedTime { get; } - - /// - /// If a command has to be resent due to an ASK or MOVED response from redis (in a cluster configuration), - /// the second sending of the command will have this property set to the original IProfiledCommand. - /// - /// This can only be set if redis is configured as a cluster. - /// - IProfiledCommand RetransmissionOf { get; } - - /// - /// If RetransmissionOf is not null, this property will be set to either Ask or Moved to indicate - /// what sort of response triggered the retransmission. - /// - /// This can be useful for determining the root cause of extra commands. - /// - RetransmissionReasonType? RetransmissionReason { get; } - } - - /// - /// Interface for profiling individual commands against an Redis ConnectionMulitplexer. - /// - public interface IProfiler - { - /// - /// Called to provide a context object. - /// - /// This method is called before the method which triggers work against redis (such as StringSet(Async)) returns, - /// and will always be called on the same thread as that method. - /// - /// Note that GetContext() may be called even if ConnectionMultiplexer.BeginProfiling() has not been called. - /// You may return `null` to prevent any tracking of commands. - /// - object GetContext(); - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/IRedis.cs b/StackExchange.Redis/StackExchange/Redis/IRedis.cs deleted file mode 100644 index 37cbdf0cb..000000000 --- a/StackExchange.Redis/StackExchange/Redis/IRedis.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Diagnostics; - -namespace StackExchange.Redis -{ - /// - /// Common operations available to all redis connections - /// - public partial interface IRedis : IRedisAsync - { - /// - /// This command is often used to test if a connection is still alive, or to measure latency. - /// - /// The observed latency. - /// http://redis.io/commands/ping - TimeSpan Ping(CommandFlags flags = CommandFlags.None); - } - - /// - /// Represents a resumable, cursor-based scanning operation - /// - public interface IScanningCursor - { - /// - /// Returns the cursor that represents the *active* page of results (not the pending/next page of results as returned by SCAN/HSCAN/ZSCAN/SSCAN) - /// - long Cursor { get; } - - /// - /// The page size of the current operation - /// - int PageSize { get; } - - /// - /// The offset into the current page - /// - int PageOffset { get; } - } - - [Conditional("DEBUG")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - internal class IgnoreNamePrefixAttribute : Attribute - { - public IgnoreNamePrefixAttribute(bool ignoreEntireMethod = false) - { - IgnoreEntireMethod = ignoreEntireMethod; - } - - public bool IgnoreEntireMethod { get; private set; } - } -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/IRedisAsync.cs b/StackExchange.Redis/StackExchange/Redis/IRedisAsync.cs deleted file mode 100644 index 840844a41..000000000 --- a/StackExchange.Redis/StackExchange/Redis/IRedisAsync.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - /// - /// Common operations available to all redis connections - /// - public partial interface IRedisAsync - { - /// - /// Gets the multiplexer that created this instance - /// - ConnectionMultiplexer Multiplexer { get; } - - /// - /// This command is often used to test if a connection is still alive, or to measure latency. - /// - /// The observed latency. - /// http://redis.io/commands/ping - Task PingAsync(CommandFlags flags = CommandFlags.None); - /// - /// Wait for a given asynchronous operation to complete (or timeout), reporting which - /// - bool TryWait(Task task); - - /// - /// Wait for a given asynchronous operation to complete (or timeout) - /// - void Wait(Task task); - /// - /// Wait for a given asynchronous operation to complete (or timeout) - /// - T Wait(Task task); - /// - /// Wait for the given asynchronous operations to complete (or timeout) - /// - - void WaitAll(params Task[] tasks); - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/IServer.cs b/StackExchange.Redis/StackExchange/Redis/IServer.cs deleted file mode 100644 index df8501414..000000000 --- a/StackExchange.Redis/StackExchange/Redis/IServer.cs +++ /dev/null @@ -1,529 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - /// - /// Provides configuration controls of a redis server - /// - public partial interface IServer : IRedis - { - /// - /// Gets the cluster configuration associated with this server, if known - /// - ClusterConfiguration ClusterConfiguration { get; } - - /// - /// Gets the address of the connected server - /// - EndPoint EndPoint { get; } - - /// - /// Gets the features available to the connected server - /// - RedisFeatures Features { get; } - - /// - /// Gets whether the connection to the server is active and usable - /// - bool IsConnected { get; } - - /// - /// Gets whether the connected server is a replica / slave - /// - bool IsSlave { get; } - - /// - /// Explicitly opt in for slave writes on writable slaves - /// - bool AllowSlaveWrites { get; set; } - - /// - /// Gets the operating mode of the connected server - /// - ServerType ServerType { get; } - - /// - /// Gets the version of the connected server - /// - Version Version { get; } - - /// - /// The CLIENT KILL command closes a given client connection identified by ip:port. - /// The ip:port should match a line returned by the CLIENT LIST command. - /// Due to the single-treaded nature of Redis, it is not possible to kill a client connection while it is executing a command.From the client point of view, the connection can never be closed in the middle of the execution of a command.However, the client will notice the connection has been closed only when the next command is sent (and results in network error). - /// - /// http://redis.io/commands/client-kill - void ClientKill(EndPoint endpoint, CommandFlags flags = CommandFlags.None); - - /// - /// The CLIENT KILL command closes a given client connection identified by ip:port. - /// The ip:port should match a line returned by the CLIENT LIST command. - /// Due to the single-treaded nature of Redis, it is not possible to kill a client connection while it is executing a command.From the client point of view, the connection can never be closed in the middle of the execution of a command.However, the client will notice the connection has been closed only when the next command is sent (and results in network error). - /// - /// http://redis.io/commands/client-kill - Task ClientKillAsync(EndPoint endpoint, CommandFlags flags = CommandFlags.None); - - /// - /// The CLIENT KILL command closes multiple connections that match the specified filters - /// - /// the number of clients killed. - /// http://redis.io/commands/client-kill - long ClientKill(long? id = null, ClientType? clientType = null, EndPoint endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None); - /// - /// The CLIENT KILL command closes multiple connections that match the specified filters - /// - /// the number of clients killed. - /// http://redis.io/commands/client-kill - Task ClientKillAsync(long? id = null, ClientType? clientType = null, EndPoint endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None); - - /// - /// The CLIENT LIST command returns information and statistics about the client connections server in a mostly human readable format. - /// - /// http://redis.io/commands/client-list - ClientInfo[] ClientList(CommandFlags flags = CommandFlags.None); - - /// - /// The CLIENT LIST command returns information and statistics about the client connections server in a mostly human readable format. - /// - /// http://redis.io/commands/client-list - Task ClientListAsync(CommandFlags flags = CommandFlags.None); - - /// - /// Obtains the current CLUSTER NODES output from a cluster server - /// - ClusterConfiguration ClusterNodes(CommandFlags flags = CommandFlags.None); - - /// - /// Obtains the current CLUSTER NODES output from a cluster server - /// - Task ClusterNodesAsync(CommandFlags flags = CommandFlags.None); - - /// - /// Obtains the current raw CLUSTER NODES output from a cluster server - /// - string ClusterNodesRaw(CommandFlags flags = CommandFlags.None); - - /// - /// Obtains the current raw CLUSTER NODES output from a cluster server - /// - Task ClusterNodesRawAsync(CommandFlags flags = CommandFlags.None); - - /// - /// Get all configuration parameters matching the specified pattern. - /// - /// All matching configuration parameters. - /// http://redis.io/commands/config-get - KeyValuePair[] ConfigGet(RedisValue pattern = default(RedisValue), CommandFlags flags = CommandFlags.None); - - /// - /// Get all configuration parameters matching the specified pattern. - /// - /// All matching configuration parameters. - /// http://redis.io/commands/config-get - Task[]> ConfigGetAsync(RedisValue pattern = default(RedisValue), CommandFlags flags = CommandFlags.None); - - /// - /// Resets the statistics reported by Redis using the INFO command. - /// - /// http://redis.io/commands/config-resetstat - void ConfigResetStatistics(CommandFlags flags = CommandFlags.None); - - /// - /// Resets the statistics reported by Redis using the INFO command. - /// - /// http://redis.io/commands/config-resetstat - Task ConfigResetStatisticsAsync(CommandFlags flags = CommandFlags.None); - - /// - /// The CONFIG REWRITE command rewrites the redis.conf file the server was started with, applying the minimal changes needed to make it reflecting the configuration currently used by the server, that may be different compared to the original one because of the use of the CONFIG SET command. - /// - /// http://redis.io/commands/config-rewrite - void ConfigRewrite(CommandFlags flags = CommandFlags.None); - - /// - /// The CONFIG REWRITE command rewrites the redis.conf file the server was started with, applying the minimal changes needed to make it reflecting the configuration currently used by the server, that may be different compared to the original one because of the use of the CONFIG SET command. - /// - /// http://redis.io/commands/config-rewrite - Task ConfigRewriteAsync(CommandFlags flags = CommandFlags.None); - - /// - /// The CONFIG SET command is used in order to reconfigure the server at runtime without the need to restart Redis. You can change both trivial parameters or switch from one to another persistence option using this command. - /// - /// http://redis.io/commands/config-set - void ConfigSet(RedisValue setting, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// The CONFIG SET command is used in order to reconfigure the server at runtime without the need to restart Redis. You can change both trivial parameters or switch from one to another persistence option using this command. - /// - /// http://redis.io/commands/config-set - Task ConfigSetAsync(RedisValue setting, RedisValue value, CommandFlags flags = CommandFlags.None); - - /// - /// Return the number of keys in the database. - /// - /// http://redis.io/commands/dbsize - long DatabaseSize(int database = 0, CommandFlags flags = CommandFlags.None); - - /// - /// Return the number of keys in the database. - /// - /// http://redis.io/commands/dbsize - Task DatabaseSizeAsync(int database = 0, CommandFlags flags = CommandFlags.None); - - /// - /// Return the same message passed in - /// - /// http://redis.io/commands/echo - RedisValue Echo(RedisValue message, CommandFlags flags = CommandFlags.None); - - /// - /// Return the same message passed in - /// - /// http://redis.io/commands/echo - Task EchoAsync(RedisValue message, CommandFlags flags = CommandFlags.None); - - /// - /// Delete all the keys of all databases on the server. - /// - /// http://redis.io/commands/flushall - void FlushAllDatabases(CommandFlags flags = CommandFlags.None); - - /// - /// Delete all the keys of all databases on the server. - /// - /// http://redis.io/commands/flushall - Task FlushAllDatabasesAsync(CommandFlags flags = CommandFlags.None); - - /// - /// Delete all the keys of the database. - /// - /// http://redis.io/commands/flushdb - void FlushDatabase(int database = 0, CommandFlags flags = CommandFlags.None); - - /// - /// Delete all the keys of the database. - /// - /// http://redis.io/commands/flushdb - Task FlushDatabaseAsync(int database = 0, CommandFlags flags = CommandFlags.None); - - /// - /// Get summary statistics associates with this server - /// - ServerCounters GetCounters(); - /// - /// The INFO command returns information and statistics about the server in a format that is simple to parse by computers and easy to read by humans. - /// - /// http://redis.io/commands/info - IGrouping>[] Info(RedisValue section = default(RedisValue), CommandFlags flags = CommandFlags.None); - - /// - /// The INFO command returns information and statistics about the server in a format that is simple to parse by computers and easy to read by humans. - /// - /// http://redis.io/commands/info - Task>[]> InfoAsync(RedisValue section = default(RedisValue), CommandFlags flags = CommandFlags.None); - - /// - /// The INFO command returns information and statistics about the server in a format that is simple to parse by computers and easy to read by humans. - /// - /// http://redis.io/commands/info - string InfoRaw(RedisValue section = default(RedisValue), CommandFlags flags = CommandFlags.None); - - /// - /// The INFO command returns information and statistics about the server in a format that is simple to parse by computers and easy to read by humans. - /// - /// http://redis.io/commands/info - Task InfoRawAsync(RedisValue section = default(RedisValue), CommandFlags flags = CommandFlags.None); - - /// - /// Returns all keys matching pattern; the KEYS or SCAN commands will be used based on the server capabilities. - /// - /// Warning: consider KEYS as a command that should only be used in production environments with extreme care. - /// http://redis.io/commands/keys - /// http://redis.io/commands/scan - IEnumerable Keys(int database, RedisValue pattern, int pageSize, CommandFlags flags); - - /// - /// Returns all keys matching pattern; the KEYS or SCAN commands will be used based on the server capabilities; note: to resume an iteration via cursor, cast the original enumerable or enumerator to IScanningCursor. - /// - /// Warning: consider KEYS as a command that should only be used in production environments with extreme care. - /// http://redis.io/commands/keys - /// http://redis.io/commands/scan - IEnumerable Keys(int database = 0, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None); - - /// - /// Return the time of the last DB save executed with success. A client may check if a BGSAVE command succeeded reading the LASTSAVE value, then issuing a BGSAVE command and checking at regular intervals every N seconds if LASTSAVE changed. - /// - /// http://redis.io/commands/lastsave - DateTime LastSave(CommandFlags flags = CommandFlags.None); - - /// - /// Return the time of the last DB save executed with success. A client may check if a BGSAVE command succeeded reading the LASTSAVE value, then issuing a BGSAVE command and checking at regular intervals every N seconds if LASTSAVE changed. - /// - /// http://redis.io/commands/lastsave - Task LastSaveAsync(CommandFlags flags = CommandFlags.None); - - /// - /// Promote the selected node to be master - /// - void MakeMaster(ReplicationChangeOptions options, TextWriter log = null); - - /// - /// Explicitly request the database to persist the current state to disk - /// - /// http://redis.io/commands/bgrewriteaof - /// http://redis.io/commands/bgsave - /// http://redis.io/commands/save - /// http://redis.io/topics/persistence - void Save(SaveType type, CommandFlags flags = CommandFlags.None); - - /// - /// Explicitly request the database to persist the current state to disk - /// - /// http://redis.io/commands/bgrewriteaof - /// http://redis.io/commands/bgsave - /// http://redis.io/commands/save - /// http://redis.io/topics/persistence - Task SaveAsync(SaveType type, CommandFlags flags = CommandFlags.None); - - /// - /// Inidicates whether the specified script is defined on the server - /// - bool ScriptExists(string script, CommandFlags flags = CommandFlags.None); - - /// - /// Inidicates whether the specified script hash is defined on the server - /// - bool ScriptExists(byte[] sha1, CommandFlags flags = CommandFlags.None); - - /// - /// Inidicates whether the specified script is defined on the server - /// - Task ScriptExistsAsync(string script, CommandFlags flags = CommandFlags.None); - - /// - /// Inidicates whether the specified script hash is defined on the server - /// - Task ScriptExistsAsync(byte[] sha1, CommandFlags flags = CommandFlags.None); - - /// - /// Removes all cached scripts on this server - /// - void ScriptFlush(CommandFlags flags = CommandFlags.None); - - /// - /// Removes all cached scripts on this server - /// - Task ScriptFlushAsync(CommandFlags flags = CommandFlags.None); - - /// - /// Explicitly defines a script on the server - /// - byte[] ScriptLoad(string script, CommandFlags flags = CommandFlags.None); - - /// - /// Explicitly defines a script on the server - /// - LoadedLuaScript ScriptLoad(LuaScript script, CommandFlags flags = CommandFlags.None); - - /// - /// Explicitly defines a script on the server - /// - Task ScriptLoadAsync(string script, CommandFlags flags = CommandFlags.None); - - /// - /// Explicitly defines a script on the server - /// - Task ScriptLoadAsync(LuaScript script, CommandFlags flags = CommandFlags.None); - - /// Asks the redis server to shutdown, killing all connections. Please FULLY read the notes on the SHUTDOWN command. - /// http://redis.io/commands/shutdown - void Shutdown(ShutdownMode shutdownMode = ShutdownMode.Default, CommandFlags flags = CommandFlags.None); - - /// - /// The SLAVEOF command can change the replication settings of a slave on the fly. If a Redis server is already acting as slave, specifying a null master will turn off the replication, turning the Redis server into a MASTER. Specifying a non-null master will make the server a slave of another server listening at the specified hostname and port. - /// - /// http://redis.io/commands/slaveof - void SlaveOf(EndPoint master, CommandFlags flags = CommandFlags.None); - /// - /// The SLAVEOF command can change the replication settings of a slave on the fly. If a Redis server is already acting as slave, specifying a null master will turn off the replication, turning the Redis server into a MASTER. Specifying a non-null master will make the server a slave of another server listening at the specified hostname and port. - /// - /// http://redis.io/commands/slaveof - Task SlaveOfAsync(EndPoint master, CommandFlags flags = CommandFlags.None); - - /// - /// To read the slow log the SLOWLOG GET command is used, that returns every entry in the slow log. It is possible to return only the N most recent entries passing an additional argument to the command (for instance SLOWLOG GET 10). - /// - /// http://redis.io/commands/slowlog - CommandTrace[] SlowlogGet(int count = 0, CommandFlags flags = CommandFlags.None); - /// - /// To read the slow log the SLOWLOG GET command is used, that returns every entry in the slow log. It is possible to return only the N most recent entries passing an additional argument to the command (for instance SLOWLOG GET 10). - /// - /// http://redis.io/commands/slowlog - Task SlowlogGetAsync(int count = 0, CommandFlags flags = CommandFlags.None); - /// - /// You can reset the slow log using the SLOWLOG RESET command. Once deleted the information is lost forever. - /// - /// http://redis.io/commands/slowlog - void SlowlogReset(CommandFlags flags = CommandFlags.None); - /// - /// You can reset the slow log using the SLOWLOG RESET command. Once deleted the information is lost forever. - /// - /// http://redis.io/commands/slowlog - Task SlowlogResetAsync(CommandFlags flags = CommandFlags.None); - /// - /// Lists the currently active channels. An active channel is a Pub/Sub channel with one ore more subscribers (not including clients subscribed to patterns). - /// - /// a list of active channels, optionally matching the specified pattern. - /// http://redis.io/commands/pubsub - RedisChannel[] SubscriptionChannels(RedisChannel pattern = default(RedisChannel), CommandFlags flags = CommandFlags.None); - - /// - /// Lists the currently active channels. An active channel is a Pub/Sub channel with one ore more subscribers (not including clients subscribed to patterns). - /// - /// a list of active channels, optionally matching the specified pattern. - /// http://redis.io/commands/pubsub - Task SubscriptionChannelsAsync(RedisChannel pattern = default(RedisChannel), CommandFlags flags = CommandFlags.None); - - /// - /// Returns the number of subscriptions to patterns (that are performed using the PSUBSCRIBE command). Note that this is not just the count of clients subscribed to patterns but the total number of patterns all the clients are subscribed to. - /// - /// the number of patterns all the clients are subscribed to. - /// http://redis.io/commands/pubsub - long SubscriptionPatternCount(CommandFlags flags = CommandFlags.None); - - /// - /// Returns the number of subscriptions to patterns (that are performed using the PSUBSCRIBE command). Note that this is not just the count of clients subscribed to patterns but the total number of patterns all the clients are subscribed to. - /// - /// the number of patterns all the clients are subscribed to. - /// http://redis.io/commands/pubsub - Task SubscriptionPatternCountAsync(CommandFlags flags = CommandFlags.None); - - /// - /// Returns the number of subscribers (not counting clients subscribed to patterns) for the specified channel. - /// - /// http://redis.io/commands/pubsub - long SubscriptionSubscriberCount(RedisChannel channel, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the number of subscribers (not counting clients subscribed to patterns) for the specified channel. - /// - /// http://redis.io/commands/pubsub - Task SubscriptionSubscriberCountAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None); - - /// - /// The TIME command returns the current server time. - /// - /// The server's current time. - /// http://redis.io/commands/time - DateTime Time(CommandFlags flags = CommandFlags.None); - /// - /// The TIME command returns the current server time. - /// - /// The server's current time. - /// http://redis.io/commands/time - Task TimeAsync(CommandFlags flags = CommandFlags.None); - - #region Sentinel - - /// - /// Returns the ip and port number of the master with that name. - /// If a failover is in progress or terminated successfully for this master it returns the address and port of the promoted slave. - /// - /// the sentinel service name - /// - /// the master ip and port - /// http://redis.io/topics/sentinel - EndPoint SentinelGetMasterAddressByName(string serviceName, CommandFlags flags = CommandFlags.None); - - /// - /// Returns the ip and port number of the master with that name. - /// If a failover is in progress or terminated successfully for this master it returns the address and port of the promoted slave. - /// - /// the sentinel service name - /// - /// the master ip and port - /// http://redis.io/topics/sentinel - Task SentinelGetMasterAddressByNameAsync(string serviceName, CommandFlags flags = CommandFlags.None); - - /// - /// Show the state and info of the specified master. - /// - /// the sentinel service name - /// - /// the master state as KeyValuePairs - /// http://redis.io/topics/sentinel - KeyValuePair[] SentinelMaster(string serviceName, CommandFlags flags = CommandFlags.None); - - /// - /// Force a failover as if the master was not reachable, and without asking for agreement to other Sentinels - /// (however a new version of the configuration will be published so that the other Sentinels will update their configurations). - /// - /// the sentinel service name - /// - /// the master state as KeyValuePairs - /// http://redis.io/topics/sentinel - Task[]> SentinelMasterAsync(string serviceName, CommandFlags flags = CommandFlags.None); - - /// - /// Show a list of monitored masters and their state. - /// - /// - /// an array of master state KeyValuePair arrays - /// http://redis.io/topics/sentinel - KeyValuePair[][] SentinelMasters(CommandFlags flags = CommandFlags.None); - - /// - /// Show a list of monitored masters and their state. - /// - /// - /// an array of master state KeyValuePair arrays - /// http://redis.io/topics/sentinel - Task[][]> SentinelMastersAsync(CommandFlags flags = CommandFlags.None); - - /// - /// Show a list of slaves for this master, and their state. - /// - /// the sentinel service name - /// - /// an array of slave state KeyValuePair arrays - /// http://redis.io/topics/sentinel - KeyValuePair[][] SentinelSlaves(string serviceName, CommandFlags flags = CommandFlags.None); - - /// - /// Show a list of slaves for this master, and their state. - /// - /// the sentinel service name - /// - /// an array of slave state KeyValuePair arrays - /// http://redis.io/topics/sentinel - Task[][]> SentinelSlavesAsync(string serviceName, CommandFlags flags = CommandFlags.None); - - /// - /// Force a failover as if the master was not reachable, and without asking for agreement to other Sentinels - /// (however a new version of the configuration will be published so that the other Sentinels will update their configurations). - /// - /// the sentinel service name - /// - /// http://redis.io/topics/sentinel - void SentinelFailover(string serviceName, CommandFlags flags = CommandFlags.None); - - /// - /// Force a failover as if the master was not reachable, and without asking for agreement to other Sentinels - /// (however a new version of the configuration will be published so that the other Sentinels will update their configurations). - /// - /// the sentinel service name - /// - /// http://redis.io/topics/sentinel - Task SentinelFailoverAsync(string serviceName, CommandFlags flags = CommandFlags.None); - - #endregion - } - - - -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/ISubscriber.cs b/StackExchange.Redis/StackExchange/Redis/ISubscriber.cs deleted file mode 100644 index a03f56bce..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ISubscriber.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Net; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - /// - /// A redis connection used as the subscriber in a pub/sub scenario - /// - public interface ISubscriber : IRedis - { - - /// - /// Inidicate exactly which redis server we are talking to - /// - [IgnoreNamePrefix] - EndPoint IdentifyEndpoint(RedisChannel channel, CommandFlags flags = CommandFlags.None); - - /// - /// Inidicate exactly which redis server we are talking to - /// - [IgnoreNamePrefix] - Task IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None); - - /// - /// Indicates whether the instance can communicate with the server; - /// if a channel is specified, the existing subscription map is queried to - /// resolve the server responsible for that subscription - otherwise the - /// server is chosen aribtraily from the masters. - /// - bool IsConnected(RedisChannel channel = default(RedisChannel)); - - /// - /// Posts a message to the given channel. - /// - /// the number of clients that received the message. - /// http://redis.io/commands/publish - long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None); - /// - /// Posts a message to the given channel. - /// - /// the number of clients that received the message. - /// http://redis.io/commands/publish - Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None); - /// - /// Subscribe to perform some operation when a change to the preferred/active node is broadcast. - /// - /// http://redis.io/commands/subscribe - /// http://redis.io/commands/psubscribe - void Subscribe(RedisChannel channel, Action handler, CommandFlags flags = CommandFlags.None); - - /// - /// Subscribe to perform some operation when a change to the preferred/active node is broadcast. - /// - /// http://redis.io/commands/subscribe - /// http://redis.io/commands/psubscribe - Task SubscribeAsync(RedisChannel channel, Action handler, CommandFlags flags = CommandFlags.None); - - /// - /// Inidicate to which redis server we are actively subscribed for a given channel; returns null if - /// the channel is not actively subscribed - /// - [IgnoreNamePrefix] - EndPoint SubscribedEndpoint(RedisChannel channel); - - /// - /// Unsubscribe from a specified message channel; note; if no handler is specified, the subscription is cancelled regardless - /// of the subscribers; if a handler is specified, the subscription is only cancelled if this handler is the - /// last handler remaining against the channel - /// - /// http://redis.io/commands/unsubscribe - /// http://redis.io/commands/punsubscribe - void Unsubscribe(RedisChannel channel, Action handler = null, CommandFlags flags = CommandFlags.None); - - /// - /// Unsubscribe all subscriptions on this instance - /// - /// http://redis.io/commands/unsubscribe - /// http://redis.io/commands/punsubscribe - void UnsubscribeAll(CommandFlags flags = CommandFlags.None); - - /// - /// Unsubscribe all subscriptions on this instance - /// - /// http://redis.io/commands/unsubscribe - /// http://redis.io/commands/punsubscribe - Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None); - - /// - /// Unsubscribe from a specified message channel; note; if no handler is specified, the subscription is cancelled regardless - /// of the subscribers; if a handler is specified, the subscription is only cancelled if this handler is the - /// last handler remaining against the channel - /// - /// http://redis.io/commands/unsubscribe - /// http://redis.io/commands/punsubscribe - Task UnsubscribeAsync(RedisChannel channel, Action handler = null, CommandFlags flags = CommandFlags.None); - } -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/ITransaction.cs b/StackExchange.Redis/StackExchange/Redis/ITransaction.cs deleted file mode 100644 index cf4634539..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ITransaction.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - /// - /// Represents a group of operations that will be sent to the server as a single unit, - /// and processed on the server as a single unit. Transactions can also include constraints - /// (implemented via WATCH), but note that constraint checking involves will (very briefly) - /// block the connection, since the transaction cannot be correctly committed (EXEC), - /// aborted (DISCARD) or not applied in the first place (UNWATCH) until the responses from - /// the constraint checks have arrived. - /// - /// http://redis.io/topics/transactions - /// Note that on a cluster, it may be required that all keys involved in the transaction - /// (including constraints) are in the same hash-slot - public interface ITransaction : IBatch - { - /// - /// Adds a precondition for this transaction - /// - ConditionResult AddCondition(Condition condition); - - /// - /// Execute the batch operation, sending all queued commands to the server. - /// - bool Execute(CommandFlags flags = CommandFlags.None); - - /// - /// Execute the batch operation, sending all queued commands to the server. - /// - Task ExecuteAsync(CommandFlags flags = CommandFlags.None); - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/InternalErrorEventArgs.cs b/StackExchange.Redis/StackExchange/Redis/InternalErrorEventArgs.cs deleted file mode 100644 index 633fd1efd..000000000 --- a/StackExchange.Redis/StackExchange/Redis/InternalErrorEventArgs.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Net; -using System.Text; - -namespace StackExchange.Redis -{ - /// - /// Describes internal errors (mainly intended for debugging) - /// - public class InternalErrorEventArgs : EventArgs, ICompletable - { - private readonly EventHandler handler; - private readonly object sender; - internal InternalErrorEventArgs(EventHandler handler, object sender, EndPoint endpoint, ConnectionType connectionType, Exception exception, string origin) - { - this.handler = handler; - this.sender = sender; - EndPoint = endpoint; - ConnectionType = connectionType; - Exception = exception; - Origin = origin; - } - /// - /// Gets the connection-type of the failing connection - /// - public ConnectionType ConnectionType { get; } - - /// - /// Gets the failing server-endpoint (this can be null) - /// - public EndPoint EndPoint { get; } - - /// - /// Gets the exception if available (this can be null) - /// - public Exception Exception { get; } - - /// - /// The underlying origin of the error - /// - public string Origin { get; } - - void ICompletable.AppendStormLog(StringBuilder sb) - { - sb.Append("event, internal-error: ").Append(Origin); - if (EndPoint != null) sb.Append(", ").Append(Format.ToString(EndPoint)); - } - - bool ICompletable.TryComplete(bool isAsync) - { - return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync); - } - } -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/BatchWrapper.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/BatchWrapper.cs deleted file mode 100644 index 642d784f3..000000000 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/BatchWrapper.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace StackExchange.Redis.KeyspaceIsolation -{ - internal sealed class BatchWrapper : WrapperBase, IBatch - { - public BatchWrapper(IBatch inner, byte[] prefix) : base(inner, prefix) - { - } - - public void Execute() - { - Inner.Execute(); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs deleted file mode 100644 index 5158d6b8e..000000000 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseWrapper.cs +++ /dev/null @@ -1,731 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; - -namespace StackExchange.Redis.KeyspaceIsolation -{ - internal sealed class DatabaseWrapper : WrapperBase, IDatabase - { - public DatabaseWrapper(IDatabase inner, byte[] prefix) : base(inner, prefix) - { - } - - public IBatch CreateBatch(object asyncState = null) - { - return new BatchWrapper(Inner.CreateBatch(asyncState), Prefix); - } - - public ITransaction CreateTransaction(object asyncState = null) - { - return new TransactionWrapper(Inner.CreateTransaction(asyncState), Prefix); - } - - public int Database => Inner.Database; - - public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.DebugObject(ToInner(key), flags); - } - - public bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None) - { - return Inner.GeoAdd(ToInner(key), longitude, latitude, member, flags); - } - - public long GeoAdd(RedisKey key, GeoEntry[] geoEntries, CommandFlags flags = CommandFlags.None) - { - return Inner.GeoAdd(ToInner(key), geoEntries, flags); - } - public bool GeoAdd(RedisKey key, GeoEntry geoEntry, CommandFlags flags = CommandFlags.None) - { - return Inner.GeoAdd(ToInner(key), geoEntry, flags); - } - - public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - return Inner.GeoRemove(ToInner(key), member, flags); - } - - public double? GeoDistance(RedisKey key, RedisValue value0, RedisValue value1, GeoUnit unit = GeoUnit.Meters,CommandFlags flags = CommandFlags.None) - { - return Inner.GeoDistance(ToInner(key), value0, value1, unit, flags); - } - - public string[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) - { - return Inner.GeoHash(ToInner(key), members, flags); - } - - public string GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - return Inner.GeoHash(ToInner(key), member, flags); - } - - public GeoPosition?[] GeoPosition(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) - { - return Inner.GeoPosition(ToInner(key), members, flags); - } - - public GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - return Inner.GeoPosition(ToInner(key), member, flags); - } - - public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null,GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) - { - return Inner.GeoRadius(ToInner(key), member, radius, unit, count, order, options, flags); - } - public GeoRadiusResult[] GeoRadius(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) - { - return Inner.GeoRadius(ToInner(key), longitude, latitude, radius, unit, count, order, options, flags); - } - - public double HashDecrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) - { - return Inner.HashDecrement(ToInner(key), hashField, value, flags); - } - - public long HashDecrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) - { - return Inner.HashDecrement(ToInner(key), hashField, value, flags); - } - - public long HashDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) - { - return Inner.HashDelete(ToInner(key), hashFields, flags); - } - - public bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) - { - return Inner.HashDelete(ToInner(key), hashField, flags); - } - - public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) - { - return Inner.HashExists(ToInner(key), hashField, flags); - } - - public HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.HashGetAll(ToInner(key), flags); - } - - public RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) - { - return Inner.HashGet(ToInner(key), hashFields, flags); - } - - public RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) - { - return Inner.HashGet(ToInner(key), hashField, flags); - } - - public double HashIncrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) - { - return Inner.HashIncrement(ToInner(key), hashField, value, flags); - } - - public long HashIncrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) - { - return Inner.HashIncrement(ToInner(key), hashField, value, flags); - } - - public RedisValue[] HashKeys(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.HashKeys(ToInner(key), flags); - } - - public long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.HashLength(ToInner(key), flags); - } - - public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.HashSet(ToInner(key), hashField, value, when, flags); - } - - public void HashSet(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None) - { - Inner.HashSet(ToInner(key), hashFields, flags); - } - - public RedisValue[] HashValues(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.HashValues(ToInner(key), flags); - } - - public bool HyperLogLogAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - return Inner.HyperLogLogAdd(ToInner(key), values, flags); - } - - public bool HyperLogLogAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.HyperLogLogAdd(ToInner(key), value, flags); - } - - public long HyperLogLogLength(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.HyperLogLogLength(ToInner(key), flags); - } - - public long HyperLogLogLength(RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - return Inner.HyperLogLogLength(ToInner(keys), flags); - } - - public void HyperLogLogMerge(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) - { - Inner.HyperLogLogMerge(ToInner(destination), ToInner(sourceKeys), flags); - } - - public void HyperLogLogMerge(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - Inner.HyperLogLogMerge(ToInner(destination), ToInner(first), ToInner(second), flags); - } - - public EndPoint IdentifyEndpoint(RedisKey key = default(RedisKey), CommandFlags flags = CommandFlags.None) - { - return Inner.IdentifyEndpoint(ToInner(key), flags); - } - - public long KeyDelete(RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyDelete(ToInner(keys), flags); - } - - public bool KeyDelete(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyDelete(ToInner(key), flags); - } - - public byte[] KeyDump(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyDump(ToInner(key), flags); - } - - public bool KeyExists(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyExists(ToInner(key), flags); - } - - public bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyExpire(ToInner(key), expiry, flags); - } - - public bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyExpire(ToInner(key), expiry, flags); - } - - public void KeyMigrate(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) - { - Inner.KeyMigrate(ToInner(key), toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); - } - - public bool KeyMove(RedisKey key, int database, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyMove(ToInner(key), database, flags); - } - - public bool KeyPersist(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyPersist(ToInner(key), flags); - } - - public RedisKey KeyRandom(CommandFlags flags = CommandFlags.None) - { - throw new NotSupportedException("RANDOMKEY is not supported when a key-prefix is specified"); - } - - public bool KeyRename(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyRename(ToInner(key), ToInner(newKey), when, flags); - } - - public void KeyRestore(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None) - { - Inner.KeyRestore(ToInner(key), value, expiry, flags); - } - - public TimeSpan? KeyTimeToLive(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyTimeToLive(ToInner(key), flags); - } - - public RedisType KeyType(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyType(ToInner(key), flags); - } - - public RedisValue ListGetByIndex(RedisKey key, long index, CommandFlags flags = CommandFlags.None) - { - return Inner.ListGetByIndex(ToInner(key), index, flags); - } - - public long ListInsertAfter(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.ListInsertAfter(ToInner(key), pivot, value, flags); - } - - public long ListInsertBefore(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.ListInsertBefore(ToInner(key), pivot, value, flags); - } - - public RedisValue ListLeftPop(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.ListLeftPop(ToInner(key), flags); - } - - public long ListLeftPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - return Inner.ListLeftPush(ToInner(key), values, flags); - } - - public long ListLeftPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.ListLeftPush(ToInner(key), value, when, flags); - } - - public long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.ListLength(ToInner(key), flags); - } - - public RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) - { - return Inner.ListRange(ToInner(key), start, stop, flags); - } - - public long ListRemove(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) - { - return Inner.ListRemove(ToInner(key), value, count, flags); - } - - public RedisValue ListRightPop(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.ListRightPop(ToInner(key), flags); - } - - public RedisValue ListRightPopLeftPush(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) - { - return Inner.ListRightPopLeftPush(ToInner(source), ToInner(destination), flags); - } - - public long ListRightPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - return Inner.ListRightPush(ToInner(key), values, flags); - } - - public long ListRightPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.ListRightPush(ToInner(key), value, when, flags); - } - - public void ListSetByIndex(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) - { - Inner.ListSetByIndex(ToInner(key), index, value, flags); - } - - public void ListTrim(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) - { - Inner.ListTrim(ToInner(key), start, stop, flags); - } - - public bool LockExtend(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) - { - return Inner.LockExtend(ToInner(key), value, expiry, flags); - } - - public RedisValue LockQuery(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.LockQuery(ToInner(key), flags); - } - - public bool LockRelease(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.LockRelease(ToInner(key), value, flags); - } - - public bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) - { - return Inner.LockTake(ToInner(key), value, expiry, flags); - } - - public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) - { - return Inner.Publish(ToInner(channel), message, flags); - } - - public RedisResult Execute(string command, params object[] args) - => Inner.Execute(command, ToInner(args), CommandFlags.None); - - public RedisResult Execute(string command, ICollection args, CommandFlags flags = CommandFlags.None) - => Inner.Execute(command, ToInner(args), flags); - - public RedisResult ScriptEvaluate(byte[] hash, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) - { - // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? - return Inner.ScriptEvaluate(hash, ToInner(keys), values, flags); - } - - public RedisResult ScriptEvaluate(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) - { - // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? - return Inner.ScriptEvaluate(script, ToInner(keys), values, flags); - } - - public RedisResult ScriptEvaluate(LuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None) - { - // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? - return script.Evaluate(Inner, parameters, Prefix, flags); - } - - public RedisResult ScriptEvaluate(LoadedLuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None) - { - // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? - return script.Evaluate(Inner, parameters, Prefix, flags); - } - - public long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - return Inner.SetAdd(ToInner(key), values, flags); - } - - public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.SetAdd(ToInner(key), value, flags); - } - - public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - return Inner.SetCombineAndStore(operation, ToInner(destination), ToInner(keys), flags); - } - - public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - return Inner.SetCombineAndStore(operation, ToInner(destination), ToInner(first), ToInner(second), flags); - } - - public RedisValue[] SetCombine(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - return Inner.SetCombine(operation, ToInner(keys), flags); - } - - public RedisValue[] SetCombine(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - return Inner.SetCombine(operation, ToInner(first), ToInner(second), flags); - } - - public bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.SetContains(ToInner(key), value, flags); - } - - public long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.SetLength(ToInner(key), flags); - } - - public RedisValue[] SetMembers(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.SetMembers(ToInner(key), flags); - } - - public bool SetMove(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.SetMove(ToInner(source), ToInner(destination), value, flags); - } - - public RedisValue SetPop(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.SetPop(ToInner(key), flags); - } - - public RedisValue SetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.SetRandomMember(ToInner(key), flags); - } - - public RedisValue[] SetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) - { - return Inner.SetRandomMembers(ToInner(key), count, flags); - } - - public long SetRemove(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - return Inner.SetRemove(ToInner(key), values, flags); - } - - public bool SetRemove(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.SetRemove(ToInner(key), value, flags); - } - - public long SortAndStore(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) - { - return Inner.SortAndStore(ToInner(destination), ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags); - } - - public RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) - { - return Inner.Sort(ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags); - } - public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, CommandFlags flags) - { - return Inner.SortedSetAdd(ToInner(key), values, flags); - } - public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetAdd(ToInner(key), values, when, flags); - } - - public bool SortedSetAdd(RedisKey key, RedisValue member, double score, CommandFlags flags) - { - return Inner.SortedSetAdd(ToInner(key), member, score, flags); - } - public bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetAdd(ToInner(key), member, score, when, flags); - } - - public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetCombineAndStore(operation, ToInner(destination), ToInner(keys), weights, aggregate, flags); - } - - public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetCombineAndStore(operation, ToInner(destination), ToInner(first), ToInner(second), aggregate, flags); - } - - public double SortedSetDecrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetDecrement(ToInner(key), member, value, flags); - } - - public double SortedSetIncrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetIncrement(ToInner(key), member, value, flags); - } - - public long SortedSetLength(RedisKey key, double min = -1.0 / 0.0, double max = 1.0 / 0.0, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetLength(ToInner(key), min, max, exclude, flags); - } - - public long SortedSetLengthByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetLengthByValue(ToInner(key), min, max, exclude, flags); - } - - public RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRangeByRank(ToInner(key), start, stop, order, flags); - } - - public SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRangeByRankWithScores(ToInner(key), start, stop, order, flags); - } - - public RedisValue[] SortedSetRangeByScore(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRangeByScore(ToInner(key), start, stop, exclude, order, skip, take, flags); - } - - public SortedSetEntry[] SortedSetRangeByScoreWithScores(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRangeByScoreWithScores(ToInner(key), start, stop, exclude, order, skip, take, flags); - } - - public RedisValue[] SortedSetRangeByValue(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue), Exclude exclude = Exclude.None, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRangeByValue(ToInner(key), min, max, exclude, skip, take, flags); - } - - public long? SortedSetRank(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRank(ToInner(key), member, order, flags); - } - - public long SortedSetRemove(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRemove(ToInner(key), members, flags); - } - - public bool SortedSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRemove(ToInner(key), member, flags); - } - - public long SortedSetRemoveRangeByRank(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRemoveRangeByRank(ToInner(key), start, stop, flags); - } - - public long SortedSetRemoveRangeByScore(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRemoveRangeByScore(ToInner(key), start, stop, exclude, flags); - } - - public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRemoveRangeByValue(ToInner(key), min, max, exclude, flags); - } - - public double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetScore(ToInner(key), member, flags); - } - - public long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.StringAppend(ToInner(key), value, flags); - } - - public long StringBitCount(RedisKey key, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None) - { - return Inner.StringBitCount(ToInner(key), start, end, flags); - } - - public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - return Inner.StringBitOperation(operation, ToInner(destination), ToInner(keys), flags); - } - - public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default(RedisKey), CommandFlags flags = CommandFlags.None) - { - return Inner.StringBitOperation(operation, ToInner(destination), ToInner(first), ToInnerOrDefault(second), flags); - } - - public long StringBitPosition(RedisKey key, bool bit, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None) - { - return Inner.StringBitPosition(ToInner(key), bit, start, end, flags); - } - - public double StringDecrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) - { - return Inner.StringDecrement(ToInner(key), value, flags); - } - - public long StringDecrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) - { - return Inner.StringDecrement(ToInner(key), value, flags); - } - - public RedisValue[] StringGet(RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - return Inner.StringGet(ToInner(keys), flags); - } - - public RedisValue StringGet(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.StringGet(ToInner(key), flags); - } - - public bool StringGetBit(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) - { - return Inner.StringGetBit(ToInner(key), offset, flags); - } - - public RedisValue StringGetRange(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) - { - return Inner.StringGetRange(ToInner(key), start, end, flags); - } - - public RedisValue StringGetSet(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.StringGetSet(ToInner(key), value, flags); - } - - public RedisValueWithExpiry StringGetWithExpiry(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.StringGetWithExpiry(ToInner(key), flags); - } - - public double StringIncrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) - { - return Inner.StringIncrement(ToInner(key), value, flags); - } - - public long StringIncrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) - { - return Inner.StringIncrement(ToInner(key), value, flags); - } - - public long StringLength(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.StringLength(ToInner(key), flags); - } - - public bool StringSet(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.StringSet(ToInner(values), when, flags); - } - - public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.StringSet(ToInner(key), value, expiry, when, flags); - } - - public bool StringSetBit(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None) - { - return Inner.StringSetBit(ToInner(key), offset, bit, flags); - } - - public RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.StringSetRange(ToInner(key), offset, value, flags); - } - - public TimeSpan Ping(CommandFlags flags = CommandFlags.None) - { - return Inner.Ping(flags); - } - - - IEnumerable IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) - { - return HashScan(key, pattern, pageSize, RedisBase.CursorUtils.Origin, 0, flags); - } - public IEnumerable HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) - { - return Inner.HashScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); - } - - IEnumerable IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) - { - return SetScan(key, pattern, pageSize, RedisBase.CursorUtils.Origin, 0, flags); - } - public IEnumerable SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) - { - return Inner.SetScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); - } - - IEnumerable IDatabase.SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) - { - return SortedSetScan(key, pattern, pageSize, RedisBase.CursorUtils.Origin, 0, flags); - } - public IEnumerable SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); - } - - -#if DEBUG - public string ClientGetName(CommandFlags flags = CommandFlags.None) - { - return Inner.ClientGetName(flags); - } - - public void Quit(CommandFlags flags = CommandFlags.None) - { - Inner.Quit(flags); - } -#endif - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/TransactionWrapper.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/TransactionWrapper.cs deleted file mode 100644 index ea63f569d..000000000 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/TransactionWrapper.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Threading.Tasks; - -namespace StackExchange.Redis.KeyspaceIsolation -{ - internal sealed class TransactionWrapper : WrapperBase, ITransaction - { - public TransactionWrapper(ITransaction inner, byte[] prefix) : base(inner, prefix) - { - } - - public ConditionResult AddCondition(Condition condition) - { - return Inner.AddCondition(condition?.MapKeys(GetMapFunction())); - } - - public bool Execute(CommandFlags flags = CommandFlags.None) - { - return Inner.Execute(flags); - } - - public Task ExecuteAsync(CommandFlags flags = CommandFlags.None) - { - return Inner.ExecuteAsync(flags); - } - - public void Execute() - { - Inner.Execute(); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs b/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs deleted file mode 100644 index 27fc300a6..000000000 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/WrapperBase.cs +++ /dev/null @@ -1,845 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace StackExchange.Redis.KeyspaceIsolation -{ - internal class WrapperBase : IDatabaseAsync where TInner : IDatabaseAsync - { - internal WrapperBase(TInner inner, byte[] keyPrefix) - { - Inner = inner; - Prefix = keyPrefix; - } - - public ConnectionMultiplexer Multiplexer => Inner.Multiplexer; - - internal TInner Inner { get; } - - internal byte[] Prefix { get; } - - public Task DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.DebugObjectAsync(ToInner(key), flags); - } - - public Task GeoAddAsync(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None) - => Inner.GeoAddAsync(ToInner(key), longitude, latitude, member, flags); - - public Task GeoAddAsync(RedisKey key, StackExchange.Redis.GeoEntry value, CommandFlags flags = CommandFlags.None) - => Inner.GeoAddAsync(ToInner(key), value, flags); - - public Task GeoAddAsync(RedisKey key, StackExchange.Redis.GeoEntry[] values, CommandFlags flags = CommandFlags.None) - => Inner.GeoAddAsync(ToInner(key), values, flags); - - public Task GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - => Inner.GeoRemoveAsync(ToInner(key), member, flags); - - public Task GeoDistanceAsync(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) - => Inner.GeoDistanceAsync(ToInner(key), member1, member2, unit, flags); - - public Task GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) - => Inner.GeoHashAsync(ToInner(key), members, flags); - - public Task GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - => Inner.GeoHashAsync(ToInner(key), member, flags); - - - public Task GeoPositionAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) - => Inner.GeoPositionAsync(ToInner(key), members, flags); - - public Task GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - => Inner.GeoPositionAsync(ToInner(key), member, flags); - - public Task GeoRadiusAsync(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) - => Inner.GeoRadiusAsync(ToInner(key), member, radius, unit, count, order, options, flags); - - public Task GeoRadiusAsync(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) - => Inner.GeoRadiusAsync(ToInner(key), longitude, latitude, radius, unit, count, order, options, flags); - - - public Task HashDecrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) - { - return Inner.HashDecrementAsync(ToInner(key), hashField, value, flags); - } - - public Task HashDecrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) - { - return Inner.HashDecrementAsync(ToInner(key), hashField, value, flags); - } - - public Task HashDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) - { - return Inner.HashDeleteAsync(ToInner(key), hashFields, flags); - } - - public Task HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) - { - return Inner.HashDeleteAsync(ToInner(key), hashField, flags); - } - - public Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) - { - return Inner.HashExistsAsync(ToInner(key), hashField, flags); - } - - public Task HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.HashGetAllAsync(ToInner(key), flags); - } - - public Task HashGetAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) - { - return Inner.HashGetAsync(ToInner(key), hashFields, flags); - } - - public Task HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) - { - return Inner.HashGetAsync(ToInner(key), hashField, flags); - } - - public Task HashIncrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) - { - return Inner.HashIncrementAsync(ToInner(key), hashField, value, flags); - } - - public Task HashIncrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) - { - return Inner.HashIncrementAsync(ToInner(key), hashField, value, flags); - } - - public Task HashKeysAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.HashKeysAsync(ToInner(key), flags); - } - - public Task HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.HashLengthAsync(ToInner(key), flags); - } - - public Task HashSetAsync(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.HashSetAsync(ToInner(key), hashField, value, when, flags); - } - - public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None) - { - return Inner.HashSetAsync(ToInner(key), hashFields, flags); - } - - public Task HashValuesAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.HashValuesAsync(ToInner(key), flags); - } - - public Task HyperLogLogAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - return Inner.HyperLogLogAddAsync(ToInner(key), values, flags); - } - - public Task HyperLogLogAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.HyperLogLogAddAsync(ToInner(key), value, flags); - } - - public Task HyperLogLogLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.HyperLogLogLengthAsync(ToInner(key), flags); - } - - public Task HyperLogLogLengthAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - return Inner.HyperLogLogLengthAsync(ToInner(keys), flags); - } - - public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) - { - return Inner.HyperLogLogMergeAsync(ToInner(destination), ToInner(sourceKeys), flags); - } - - public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - return Inner.HyperLogLogMergeAsync(ToInner(destination), ToInner(first), ToInner(second), flags); - } - - public Task IdentifyEndpointAsync(RedisKey key = default(RedisKey), CommandFlags flags = CommandFlags.None) - { - return Inner.IdentifyEndpointAsync(ToInner(key), flags); - } - - public bool IsConnected(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.IsConnected(ToInner(key), flags); - } - - public Task KeyDeleteAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyDeleteAsync(ToInner(keys), flags); - } - - public Task KeyDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyDeleteAsync(ToInner(key), flags); - } - - public Task KeyDumpAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyDumpAsync(ToInner(key), flags); - } - - public Task KeyExistsAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyExistsAsync(ToInner(key), flags); - } - - public Task KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyExpireAsync(ToInner(key), expiry, flags); - } - - public Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyExpireAsync(ToInner(key), expiry, flags); - } - - public Task KeyMigrateAsync(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyMigrateAsync(ToInner(key), toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); - } - - public Task KeyMoveAsync(RedisKey key, int database, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyMoveAsync(ToInner(key), database, flags); - } - - public Task KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyPersistAsync(ToInner(key), flags); - } - - public Task KeyRandomAsync(CommandFlags flags = CommandFlags.None) - { - throw new NotSupportedException("RANDOMKEY is not supported when a key-prefix is specified"); - } - - public Task KeyRenameAsync(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyRenameAsync(ToInner(key), ToInner(newKey), when, flags); - } - - public Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyRestoreAsync(ToInner(key), value, expiry, flags); - } - - public Task KeyTimeToLiveAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyTimeToLiveAsync(ToInner(key), flags); - } - - public Task KeyTypeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.KeyTypeAsync(ToInner(key), flags); - } - - public Task ListGetByIndexAsync(RedisKey key, long index, CommandFlags flags = CommandFlags.None) - { - return Inner.ListGetByIndexAsync(ToInner(key), index, flags); - } - - public Task ListInsertAfterAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.ListInsertAfterAsync(ToInner(key), pivot, value, flags); - } - - public Task ListInsertBeforeAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.ListInsertBeforeAsync(ToInner(key), pivot, value, flags); - } - - public Task ListLeftPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.ListLeftPopAsync(ToInner(key), flags); - } - - public Task ListLeftPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - return Inner.ListLeftPushAsync(ToInner(key), values, flags); - } - - public Task ListLeftPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.ListLeftPushAsync(ToInner(key), value, when, flags); - } - - public Task ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.ListLengthAsync(ToInner(key), flags); - } - - public Task ListRangeAsync(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) - { - return Inner.ListRangeAsync(ToInner(key), start, stop, flags); - } - - public Task ListRemoveAsync(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) - { - return Inner.ListRemoveAsync(ToInner(key), value, count, flags); - } - - public Task ListRightPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.ListRightPopAsync(ToInner(key), flags); - } - - public Task ListRightPopLeftPushAsync(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) - { - return Inner.ListRightPopLeftPushAsync(ToInner(source), ToInner(destination), flags); - } - - public Task ListRightPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - return Inner.ListRightPushAsync(ToInner(key), values, flags); - } - - public Task ListRightPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.ListRightPushAsync(ToInner(key), value, when, flags); - } - - public Task ListSetByIndexAsync(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.ListSetByIndexAsync(ToInner(key), index, value, flags); - } - - public Task ListTrimAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) - { - return Inner.ListTrimAsync(ToInner(key), start, stop, flags); - } - - public Task LockExtendAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) - { - return Inner.LockExtendAsync(ToInner(key), value, expiry, flags); - } - - public Task LockQueryAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.LockQueryAsync(ToInner(key), flags); - } - - public Task LockReleaseAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.LockReleaseAsync(ToInner(key), value, flags); - } - - public Task LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) - { - return Inner.LockTakeAsync(ToInner(key), value, expiry, flags); - } - - public Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) - { - return Inner.PublishAsync(ToInner(channel), message, flags); - } - public Task ExecuteAsync(string command, params object[] args) - => Inner.ExecuteAsync(command, ToInner(args), CommandFlags.None); - public Task ExecuteAsync(string command, ICollection args, CommandFlags flags = CommandFlags.None) - => Inner.ExecuteAsync(command, ToInner(args), flags); - - public Task ScriptEvaluateAsync(byte[] hash, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) - { - // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? - return Inner.ScriptEvaluateAsync(hash, ToInner(keys), values, flags); - } - - public Task ScriptEvaluateAsync(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) - { - // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? - return Inner.ScriptEvaluateAsync(script, ToInner(keys), values, flags); - } - - public Task ScriptEvaluateAsync(LuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None) - { - return Inner.ScriptEvaluateAsync(script, parameters, flags); - } - - public Task ScriptEvaluateAsync(LoadedLuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None) - { - return Inner.ScriptEvaluateAsync(script, parameters, flags); - } - - public Task SetAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - return Inner.SetAddAsync(ToInner(key), values, flags); - } - - public Task SetAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.SetAddAsync(ToInner(key), value, flags); - } - - public Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - return Inner.SetCombineAndStoreAsync(operation, ToInner(destination), ToInner(keys), flags); - } - - public Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - return Inner.SetCombineAndStoreAsync(operation, ToInner(destination), ToInner(first), ToInner(second), flags); - } - - public Task SetCombineAsync(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - return Inner.SetCombineAsync(operation, ToInner(keys), flags); - } - - public Task SetCombineAsync(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - return Inner.SetCombineAsync(operation, ToInner(first), ToInner(second), flags); - } - - public Task SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.SetContainsAsync(ToInner(key), value, flags); - } - - public Task SetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.SetLengthAsync(ToInner(key), flags); - } - - public Task SetMembersAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.SetMembersAsync(ToInner(key), flags); - } - - public Task SetMoveAsync(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.SetMoveAsync(ToInner(source), ToInner(destination), value, flags); - } - - public Task SetPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.SetPopAsync(ToInner(key), flags); - } - - public Task SetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.SetRandomMemberAsync(ToInner(key), flags); - } - - public Task SetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) - { - return Inner.SetRandomMembersAsync(ToInner(key), count, flags); - } - - public Task SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - return Inner.SetRemoveAsync(ToInner(key), values, flags); - } - - public Task SetRemoveAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.SetRemoveAsync(ToInner(key), value, flags); - } - - public Task SortAndStoreAsync(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) - { - return Inner.SortAndStoreAsync(ToInner(destination), ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags); - } - - public Task SortAsync(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) - { - return Inner.SortAsync(ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags); - } - - public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, CommandFlags flags) - { - return Inner.SortedSetAddAsync(ToInner(key), values, flags); - } - public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetAddAsync(ToInner(key), values, when, flags); - } - - public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, CommandFlags flags) - { - return Inner.SortedSetAddAsync(ToInner(key), member, score, flags); - } - public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetAddAsync(ToInner(key), member, score, when, flags); - } - - public Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetCombineAndStoreAsync(operation, ToInner(destination), ToInner(keys), weights, aggregate, flags); - } - - public Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetCombineAndStoreAsync(operation, ToInner(destination), ToInner(first), ToInner(second), aggregate, flags); - } - - public Task SortedSetDecrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetDecrementAsync(ToInner(key), member, value, flags); - } - - public Task SortedSetIncrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetIncrementAsync(ToInner(key), member, value, flags); - } - - public Task SortedSetLengthAsync(RedisKey key, double min = -1.0 / 0.0, double max = 1.0 / 0.0, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetLengthAsync(ToInner(key), min, max, exclude, flags); - } - - public Task SortedSetLengthByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetLengthByValueAsync(ToInner(key), min, max, exclude, flags); - } - - public Task SortedSetRangeByRankAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRangeByRankAsync(ToInner(key), start, stop, order, flags); - } - - public Task SortedSetRangeByRankWithScoresAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRangeByRankWithScoresAsync(ToInner(key), start, stop, order, flags); - } - - public Task SortedSetRangeByScoreAsync(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRangeByScoreAsync(ToInner(key), start, stop, exclude, order, skip, take, flags); - } - - public Task SortedSetRangeByScoreWithScoresAsync(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRangeByScoreWithScoresAsync(ToInner(key), start, stop, exclude, order, skip, take, flags); - } - - public Task SortedSetRangeByValueAsync(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue), Exclude exclude = Exclude.None, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRangeByValueAsync(ToInner(key), min, max, exclude, skip, take, flags); - } - - public Task SortedSetRankAsync(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRankAsync(ToInner(key), member, order, flags); - } - - public Task SortedSetRemoveAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRemoveAsync(ToInner(key), members, flags); - } - - public Task SortedSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRemoveAsync(ToInner(key), member, flags); - } - - public Task SortedSetRemoveRangeByRankAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRemoveRangeByRankAsync(ToInner(key), start, stop, flags); - } - - public Task SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRemoveRangeByScoreAsync(ToInner(key), start, stop, exclude, flags); - } - - public Task SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetRemoveRangeByValueAsync(ToInner(key), min, max, exclude, flags); - } - - public Task SortedSetScoreAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - return Inner.SortedSetScoreAsync(ToInner(key), member, flags); - } - - public Task StringAppendAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.StringAppendAsync(ToInner(key), value, flags); - } - - public Task StringBitCountAsync(RedisKey key, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None) - { - return Inner.StringBitCountAsync(ToInner(key), start, end, flags); - } - - public Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - return Inner.StringBitOperationAsync(operation, ToInner(destination), ToInner(keys), flags); - } - - public Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default(RedisKey), CommandFlags flags = CommandFlags.None) - { - return Inner.StringBitOperationAsync(operation, ToInner(destination), ToInner(first), ToInnerOrDefault(second), flags); - } - - public Task StringBitPositionAsync(RedisKey key, bool bit, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None) - { - return Inner.StringBitPositionAsync(ToInner(key), bit, start, end, flags); - } - - public Task StringDecrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) - { - return Inner.StringDecrementAsync(ToInner(key), value, flags); - } - - public Task StringDecrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) - { - return Inner.StringDecrementAsync(ToInner(key), value, flags); - } - - public Task StringGetAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - return Inner.StringGetAsync(ToInner(keys), flags); - } - - public Task StringGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.StringGetAsync(ToInner(key), flags); - } - - public Task StringGetBitAsync(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) - { - return Inner.StringGetBitAsync(ToInner(key), offset, flags); - } - - public Task StringGetRangeAsync(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) - { - return Inner.StringGetRangeAsync(ToInner(key), start, end, flags); - } - - public Task StringGetSetAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.StringGetSetAsync(ToInner(key), value, flags); - } - - public Task StringGetWithExpiryAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.StringGetWithExpiryAsync(ToInner(key), flags); - } - - public Task StringIncrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) - { - return Inner.StringIncrementAsync(ToInner(key), value, flags); - } - - public Task StringIncrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) - { - return Inner.StringIncrementAsync(ToInner(key), value, flags); - } - - public Task StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return Inner.StringLengthAsync(ToInner(key), flags); - } - - public Task StringSetAsync(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.StringSetAsync(ToInner(values), when, flags); - } - - public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - return Inner.StringSetAsync(ToInner(key), value, expiry, when, flags); - } - - public Task StringSetBitAsync(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None) - { - return Inner.StringSetBitAsync(ToInner(key), offset, bit, flags); - } - - public Task StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) - { - return Inner.StringSetRangeAsync(ToInner(key), offset, value, flags); - } - - public Task PingAsync(CommandFlags flags = CommandFlags.None) - { - return Inner.PingAsync(flags); - } - - public bool TryWait(Task task) - { - return Inner.TryWait(task); - } - - public TResult Wait(Task task) - { - return Inner.Wait(task); - } - - public void Wait(Task task) - { - Inner.Wait(task); - } - - public void WaitAll(params Task[] tasks) - { - Inner.WaitAll(tasks); - } - -#if DEBUG - public Task ClientGetNameAsync(CommandFlags flags = CommandFlags.None) - { - return Inner.ClientGetNameAsync(flags); - } -#endif - - protected internal RedisKey ToInner(RedisKey outer) - { - return RedisKey.WithPrefix(Prefix, outer); - } - - protected RedisKey ToInnerOrDefault(RedisKey outer) - { - if (outer == default(RedisKey)) - { - return outer; - } - else - { - return ToInner(outer); - } - } - protected ICollection ToInner(ICollection args) - { - if (args != null && args.Any(x => x is RedisKey || x is RedisChannel)) - { - var withPrefix = new object[args.Count]; - int i = 0; - foreach(var oldArg in args) - { - object newArg; - if (oldArg is RedisKey) - { - newArg = ToInner((RedisKey)oldArg); - } - else if (oldArg is RedisChannel) - { - newArg = ToInner((RedisChannel)oldArg); - } - else - { - newArg = oldArg; - } - withPrefix[i++] = newArg; - } - args = withPrefix; - } - return args; - } - protected RedisKey[] ToInner(RedisKey[] outer) - { - if (outer == null || outer.Length == 0) - { - return outer; - } - else - { - RedisKey[] inner = new RedisKey[outer.Length]; - - for (int i = 0; i < outer.Length; ++i) - { - inner[i] = ToInner(outer[i]); - } - - return inner; - } - } - - protected KeyValuePair ToInner(KeyValuePair outer) - { - return new KeyValuePair(ToInner(outer.Key), outer.Value); - } - - protected KeyValuePair[] ToInner(KeyValuePair[] outer) - { - if (outer == null || outer.Length == 0) - { - return outer; - } - else - { - KeyValuePair[] inner = new KeyValuePair[outer.Length]; - - for (int i = 0; i < outer.Length; ++i) - { - inner[i] = ToInner(outer[i]); - } - - return inner; - } - } - - protected RedisValue ToInner(RedisValue outer) - { - return RedisKey.ConcatenateBytes(Prefix, null, (byte[])outer); - } - - protected RedisValue SortByToInner(RedisValue outer) - { - if (outer == "nosort") - { - return outer; - } - else - { - return ToInner(outer); - } - } - - protected RedisValue SortGetToInner(RedisValue outer) - { - if (outer == "#") - { - return outer; - } - else - { - return ToInner(outer); - } - } - - protected RedisValue[] SortGetToInner(RedisValue[] outer) - { - if (outer == null || outer.Length == 0) - { - return outer; - } - else - { - RedisValue[] inner = new RedisValue[outer.Length]; - - for (int i = 0; i < outer.Length; ++i) - { - inner[i] = SortGetToInner(outer[i]); - } - - return inner; - } - } - - protected RedisChannel ToInner(RedisChannel outer) - { - return RedisKey.ConcatenateBytes(Prefix, null, (byte[])outer); - } - - private Func mapFunction; - protected Func GetMapFunction() - { - // create as a delegate when first required, then re-use - return mapFunction ?? (mapFunction = new Func(ToInner)); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/LinearRetry.cs b/StackExchange.Redis/StackExchange/Redis/LinearRetry.cs deleted file mode 100644 index 80b9b248e..000000000 --- a/StackExchange.Redis/StackExchange/Redis/LinearRetry.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -namespace StackExchange.Redis -{ - /// - /// Represents a retry policy that performs retries at a fixed interval. The retries are performed upto a maximum allowed time. - /// - public class LinearRetry : IReconnectRetryPolicy - { - private int maxRetryElapsedTimeAllowedMilliseconds; - - /// - /// Initializes a new instance using the specified maximum retry elapsed time allowed. - /// - /// maximum elapsed time in milliseconds to be allowed for it to perform retries - public LinearRetry(int maxRetryElapsedTimeAllowedMilliseconds) - { - this.maxRetryElapsedTimeAllowedMilliseconds = maxRetryElapsedTimeAllowedMilliseconds; - } - - /// - /// This method is called by the ConnectionMultiplexer to determine if a reconnect operation can be retried now. - /// - /// The number of times reconnect retries have already been made by the ConnectionMultiplexer while it was in the connecting state - /// Total elapsed time in milliseconds since the last reconnect retry was made - public bool ShouldRetry(long currentRetryCount, int timeElapsedMillisecondsSinceLastRetry) - { - return timeElapsedMillisecondsSinceLastRetry >= maxRetryElapsedTimeAllowedMilliseconds; - } - } -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/LoggingTextStream.cs b/StackExchange.Redis/StackExchange/Redis/LoggingTextStream.cs deleted file mode 100644 index 732a00673..000000000 --- a/StackExchange.Redis/StackExchange/Redis/LoggingTextStream.cs +++ /dev/null @@ -1,141 +0,0 @@ -namespace StackExchange.Redis -{ -#if LOGOUTPUT - sealed class LoggingTextStream : Stream - { - [Conditional("VERBOSE")] - void Trace(string value, [CallerMemberName] string caller = null) - { - Debug.WriteLine(value, this.category + ":" + caller); - } - [Conditional("VERBOSE")] - void Trace(char value, [CallerMemberName] string caller = null) - { - Debug.WriteLine(value, this.category + ":" + caller); - } - - private readonly Stream stream, echo; - private readonly string category; - public LoggingTextStream(Stream stream, string category, Stream echo) - { - if (stream == null) throw new ArgumentNullException("stream"); - if (string.IsNullOrWhiteSpace(category)) category = GetType().Name; - this.stream = stream; - this.category = category; - this.echo = echo; - } - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - asyncBuffer = buffer; - asyncOffset = offset; - asyncCount = count; - return stream.BeginRead(buffer, offset, count, callback, state); - } - private volatile byte[] asyncBuffer; - private volatile int asyncOffset, asyncCount; - public override int EndRead(IAsyncResult asyncResult) - { - int bytes = stream.EndRead(asyncResult); - if (bytes <= 0) - { - Trace(""); - } - else - { - Trace(Encoding.UTF8.GetString(asyncBuffer, asyncOffset, asyncCount)); - } - return bytes; - } - public override bool CanRead { get { return stream.CanRead; } } - public override bool CanSeek { get { return stream.CanSeek; } } - public override bool CanWrite { get { return stream.CanWrite; } } - public override bool CanTimeout { get { return stream.CanTimeout; } } - public override long Length { get { return stream.Length; } } - public override long Position - { - get { return stream.Position; } - set { stream.Position = value; } - } - public override int ReadTimeout - { - get { return stream.ReadTimeout; } - set { stream.ReadTimeout = value; } - } - public override int WriteTimeout - { - get { return stream.WriteTimeout; } - set { stream.WriteTimeout = value; } - } - protected override void Dispose(bool disposing) - { - if (disposing) - { - stream.Dispose(); - if (echo != null) echo.Flush(); - } - base.Dispose(disposing); - } - public override void Close() - { - Trace("Close"); - stream.Close(); - if (echo != null) echo.Close(); - base.Close(); - } - public override void Flush() - { - Trace("Flush"); - stream.Flush(); - if (echo != null) echo.Flush(); - } - public override long Seek(long offset, SeekOrigin origin) - { - return stream.Seek(offset, origin); - } - public override void SetLength(long value) - { - stream.SetLength(value); - } - public override void WriteByte(byte value) - { - Trace((char)value); - stream.WriteByte(value); - if (echo != null) echo.WriteByte(value); - } - public override int ReadByte() - { - int value = stream.ReadByte(); - if(value < 0) - { - Trace(""); - } else - { - Trace((char)value); - } - return value; - } - public override int Read(byte[] buffer, int offset, int count) - { - int bytes = stream.Read(buffer, offset, count); - if(bytes <= 0) - { - Trace(""); - } - else - { - Trace(Encoding.UTF8.GetString(buffer, offset, bytes)); - } - return bytes; - } - public override void Write(byte[] buffer, int offset, int count) - { - if (count != 0) - { - Trace(Encoding.UTF8.GetString(buffer, offset, count)); - } - stream.Write(buffer, offset, count); - if (echo != null) echo.Write(buffer, offset, count); - } - } -#endif -} diff --git a/StackExchange.Redis/StackExchange/Redis/LuaScript.cs b/StackExchange.Redis/StackExchange/Redis/LuaScript.cs deleted file mode 100644 index dac35ae12..000000000 --- a/StackExchange.Redis/StackExchange/Redis/LuaScript.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - /// - /// Represents a Lua script that can be executed on Redis. - /// - /// Unlike normal Redis Lua scripts, LuaScript can have named parameters (prefixed by a @). - /// Public fields and properties of the passed in object are treated as parameters. - /// - /// Parameters of type RedisKey are sent to Redis as KEY (http://redis.io/commands/eval) in addition to arguments, - /// so as to play nicely with Redis Cluster. - /// - /// All members of this class are thread safe. - /// - public sealed class LuaScript - { - // Since the mapping of "script text" -> LuaScript doesn't depend on any particular details of - // the redis connection itself, this cache is global. - static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); - - /// - /// The original Lua script that was used to create this. - /// - public string OriginalScript { get; private set; } - - /// - /// The Lua script that will actually be sent to Redis for execution. - /// - /// All @-prefixed parameter names have been replaced at this point. - /// - public string ExecutableScript { get; private set; } - - // Arguments are in the order they have to passed to the script in - internal string[] Arguments { get; private set; } - - bool HasArguments => Arguments != null && Arguments.Length > 0; - - Hashtable ParameterMappers; - - internal LuaScript(string originalScript, string executableScript, string[] arguments) - { - OriginalScript = originalScript; - ExecutableScript = executableScript; - Arguments = arguments; - - if (HasArguments) - { - ParameterMappers = new Hashtable(); - } - } - - /// - /// Finalizer, used to prompt cleanups of the script cache when - /// a LuaScript reference goes out of scope. - /// - ~LuaScript() - { - try - { - WeakReference ignored; - Cache.TryRemove(OriginalScript, out ignored); - } - catch { } - } - - /// - /// Invalidates the internal cache of LuaScript objects. - /// Existing LuaScripts will continue to work, but future calls to LuaScript.Prepare - /// return a new LuaScript instance. - /// - public static void PurgeCache() - { - Cache.Clear(); - } - - /// - /// Returns the number of cached LuaScripts. - /// - public static int GetCachedScriptCount() - { - return Cache.Count; - } - - /// - /// Prepares a Lua script with named parameters to be run against any Redis instance. - /// - public static LuaScript Prepare(string script) - { - LuaScript ret; - - WeakReference weakRef; - if (!Cache.TryGetValue(script, out weakRef) || (ret = (LuaScript)weakRef.Target) == null) - { - ret = ScriptParameterMapper.PrepareScript(script); - Cache[script] = new WeakReference(ret); - } - - return ret; - } - - internal void ExtractParameters(object ps, RedisKey? keyPrefix, out RedisKey[] keys, out RedisValue[] args) - { - if (HasArguments) - { - if (ps == null) throw new ArgumentNullException(nameof(ps), "Script requires parameters"); - - var psType = ps.GetType(); - var mapper = (Func)ParameterMappers[psType]; - if (ps != null && mapper == null) - { - lock (ParameterMappers) - { - mapper = (Func)ParameterMappers[psType]; - if (mapper == null) - { - string missingMember; - string badMemberType; - if (!ScriptParameterMapper.IsValidParameterHash(psType, this, out missingMember, out badMemberType)) - { - if (missingMember != null) - { - throw new ArgumentException("ps", "Expected [" + missingMember + "] to be a field or gettable property on [" + psType.FullName + "]"); - } - - throw new ArgumentException("ps", "Expected [" + badMemberType + "] on [" + psType.FullName + "] to be convertable to a RedisValue"); - } - - ParameterMappers[psType] = mapper = ScriptParameterMapper.GetParameterExtractor(psType, this); - } - } - } - - var mapped = mapper(ps, keyPrefix); - keys = mapped.Keys; - args = mapped.Arguments; - } - else - { - keys = null; - args = null; - } - } - - /// - /// Evaluates this LuaScript against the given database, extracting parameters from the passed in object if any. - /// - public RedisResult Evaluate(IDatabase db, object ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) - { - RedisKey[] keys; - RedisValue[] args; - ExtractParameters(ps, withKeyPrefix, out keys, out args); - - return db.ScriptEvaluate(ExecutableScript, keys, args, flags); - } - - /// - /// Evaluates this LuaScript against the given database, extracting parameters from the passed in object if any. - /// - public Task EvaluateAsync(IDatabaseAsync db, object ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) - { - RedisKey[] keys; - RedisValue[] args; - ExtractParameters(ps, withKeyPrefix, out keys, out args); - - return db.ScriptEvaluateAsync(ExecutableScript, keys, args, flags); - } - - /// - /// Loads this LuaScript into the given IServer so it can be run with it's SHA1 hash, instead of - /// passing the full script on each Evaluate or EvaluateAsync call. - /// - /// Note: the FireAndForget command flag cannot be set - /// - public LoadedLuaScript Load(IServer server, CommandFlags flags = CommandFlags.None) - { - if (flags.HasFlag(CommandFlags.FireAndForget)) - { - throw new ArgumentOutOfRangeException(nameof(flags), "Loading a script cannot be FireAndForget"); - } - - var hash = server.ScriptLoad(ExecutableScript, flags); - - return new LoadedLuaScript(this, hash); - } - - /// - /// Loads this LuaScript into the given IServer so it can be run with it's SHA1 hash, instead of - /// passing the full script on each Evaluate or EvaluateAsync call. - /// - /// Note: the FireAndForget command flag cannot be set - /// - public async Task LoadAsync(IServer server, CommandFlags flags = CommandFlags.None) - { - if (flags.HasFlag(CommandFlags.FireAndForget)) - { - throw new ArgumentOutOfRangeException(nameof(flags), "Loading a script cannot be FireAndForget"); - } - - var hash = await server.ScriptLoadAsync(ExecutableScript, flags).ForAwait(); - - return new LoadedLuaScript(this, hash); - } - } - - /// - /// Represents a Lua script that can be executed on Redis. - /// - /// Unlike LuaScript, LoadedLuaScript sends the hash of it's ExecutableScript to Redis rather than pass - /// the whole script on each call. This requires that the script be loaded into Redis before it is used. - /// - /// To create a LoadedLuaScript first create a LuaScript via LuaScript.Prepare(string), then - /// call Load(IServer, CommandFlags) on the returned LuaScript. - /// - /// Unlike normal Redis Lua scripts, LoadedLuaScript can have named parameters (prefixed by a @). - /// Public fields and properties of the passed in object are treated as parameters. - /// - /// Parameters of type RedisKey are sent to Redis as KEY (http://redis.io/commands/eval) in addition to arguments, - /// so as to play nicely with Redis Cluster. - /// - /// All members of this class are thread safe. - /// - public sealed class LoadedLuaScript - { - /// - /// The original script that was used to create this LoadedLuaScript. - /// - public string OriginalScript => Original.OriginalScript; - - /// - /// The script that will actually be sent to Redis for execution. - /// - public string ExecutableScript => Original.ExecutableScript; - - /// - /// The SHA1 hash of ExecutableScript. - /// - /// This is sent to Redis instead of ExecutableScript during Evaluate and EvaluateAsync calls. - /// - public byte[] Hash { get; private set; } - - // internal for testing purposes only - internal LuaScript Original; - - internal LoadedLuaScript(LuaScript original, byte[] hash) - { - Original = original; - Hash = hash; - } - - /// - /// Evaluates this LoadedLuaScript against the given database, extracting parameters for the passed in object if any. - /// - /// This method sends the SHA1 hash of the ExecutableScript instead of the script itself. If the script has not - /// been loaded into the passed Redis instance it will fail. - /// - public RedisResult Evaluate(IDatabase db, object ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) - { - RedisKey[] keys; - RedisValue[] args; - Original.ExtractParameters(ps, withKeyPrefix, out keys, out args); - - return db.ScriptEvaluate(Hash, keys, args, flags); - } - - /// - /// Evaluates this LoadedLuaScript against the given database, extracting parameters for the passed in object if any. - /// - /// This method sends the SHA1 hash of the ExecutableScript instead of the script itself. If the script has not - /// been loaded into the passed Redis instance it will fail. - /// - public Task EvaluateAsync(IDatabaseAsync db, object ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) - { - RedisKey[] keys; - RedisValue[] args; - Original.ExtractParameters(ps, withKeyPrefix, out keys, out args); - - return db.ScriptEvaluateAsync(Hash, keys, args, flags); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/Message.cs b/StackExchange.Redis/StackExchange/Redis/Message.cs deleted file mode 100644 index 26a9a5cae..000000000 --- a/StackExchange.Redis/StackExchange/Redis/Message.cs +++ /dev/null @@ -1,1299 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -#if FEATURE_SERIALIZATION -using System.Runtime.Serialization; -#endif -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - /// - /// Indicates that a command was illegal and was not sent to the server - /// -#if FEATURE_SERIALIZATION - [Serializable] -#endif - public sealed class RedisCommandException : Exception - { -#if FEATURE_SERIALIZATION - private RedisCommandException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { } -#endif - internal RedisCommandException(string message) : base(message) { } - internal RedisCommandException(string message, Exception innerException) : base(message, innerException) { } - } - - -/// -/// Indicates the time allotted for a command or operation has expired. -/// -#if FEATURE_SERIALIZATION - [Serializable] -#endif - public sealed class RedisTimeoutException : TimeoutException - { -#if FEATURE_SERIALIZATION - private RedisTimeoutException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) - { - Commandstatus = (CommandStatus) info.GetValue("commandStatus", typeof(CommandStatus)); - } - /// - /// Serialization implementation; not intended for general usage - /// - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - base.GetObjectData(info, context); - info.AddValue("commandStatus", Commandstatus); - } -#endif - internal RedisTimeoutException(string message, CommandStatus commandStatus) : base(message) - { - Commandstatus = commandStatus; - } - - /// - /// status of the command while communicating with Redis - /// - public CommandStatus Commandstatus { get; } - } - - - - /// - /// Indicates a connection fault when communicating with redis - /// -#if FEATURE_SERIALIZATION - [Serializable] -#endif - public sealed class RedisConnectionException : RedisException - { -#if FEATURE_SERIALIZATION - private RedisConnectionException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) - { - FailureType = (ConnectionFailureType)info.GetInt32("failureType"); - CommandStatus = (CommandStatus)info.GetValue("commandStatus", typeof(CommandStatus)); - } - /// - /// Serialization implementation; not intended for general usage - /// - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - base.GetObjectData(info, context); - info.AddValue("failureType", (int)FailureType); - info.AddValue("commandStatus", CommandStatus); - } -#endif - - internal RedisConnectionException(ConnectionFailureType failureType, string message) : this(failureType, message, null, CommandStatus.Unknown) - { - } - - internal RedisConnectionException(ConnectionFailureType failureType, string message, Exception innerException) : this(failureType, message, innerException, CommandStatus.Unknown) - { - } - - internal RedisConnectionException(ConnectionFailureType failureType, string message, Exception innerException, CommandStatus commandStatus) : base(message, innerException) - { - FailureType = failureType; - CommandStatus = commandStatus; - } - - /// - /// The type of connection failure - /// - public ConnectionFailureType FailureType { get; } - - /// - /// status of the command while communicating with Redis - /// - public CommandStatus CommandStatus { get; } - } - - /// - /// Indicates an issue communicating with redis - /// -#if FEATURE_SERIALIZATION - [Serializable] -#endif - public class RedisException : Exception - { - /// - /// Deserialization constructor; not intended for general usage - /// -#if FEATURE_SERIALIZATION - protected RedisException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { } -#endif - - internal RedisException(string message) : base(message) { } - internal RedisException(string message, Exception innerException) : base(message, innerException) { } - } - /// - /// Indicates an exception raised by a redis server - /// -#if FEATURE_SERIALIZATION - [Serializable] -#endif - public sealed class RedisServerException : RedisException - { -#if FEATURE_SERIALIZATION - private RedisServerException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { } -#endif - - internal RedisServerException(string message) : base(message) { } - } - - sealed class LoggingMessage : Message - { - public readonly TextWriter log; - private readonly Message tail; - - public static Message Create(TextWriter log, Message tail) - { - return log == null ? tail : new LoggingMessage(log, tail); - } - private LoggingMessage(TextWriter log, Message tail) : base(tail.Db, tail.Flags, tail.Command) - { - this.log = log; - this.tail = tail; - FlagsRaw = tail.FlagsRaw; - } - public override string CommandAndKey => tail.CommandAndKey; - - public override void AppendStormLog(StringBuilder sb) - { - tail.AppendStormLog(sb); - } - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - return tail.GetHashSlot(serverSelectionStrategy); - } - internal override void WriteImpl(PhysicalConnection physical) - { - try - { - physical.Multiplexer.LogLocked(log, "Writing to {0}: {1}", physical.Bridge, tail.CommandAndKey); - } - catch { } - tail.WriteImpl(physical); - } - - public TextWriter Log => log; - } - - abstract class Message : ICompletable - { - - public static readonly Message[] EmptyArray = new Message[0]; - public readonly int Db; - - internal const CommandFlags InternalCallFlag = (CommandFlags)128; - protected RedisCommand command; - - private const CommandFlags AskingFlag = (CommandFlags)32, - ScriptUnavailableFlag = (CommandFlags)256; - - const CommandFlags MaskMasterServerPreference = CommandFlags.DemandMaster | CommandFlags.DemandSlave | CommandFlags.PreferMaster | CommandFlags.PreferSlave; - - private const CommandFlags UserSelectableFlags - = CommandFlags.None | CommandFlags.DemandMaster | CommandFlags.DemandSlave - | CommandFlags.PreferMaster | CommandFlags.PreferSlave - | CommandFlags.HighPriority | CommandFlags.FireAndForget | CommandFlags.NoRedirect | CommandFlags.NoScriptCache; - - private CommandFlags flags; - internal CommandFlags FlagsRaw { get { return flags; } set { flags = value; } } - private ResultBox resultBox; - - private ResultProcessor resultProcessor; - - // All for profiling purposes - private ProfileStorage performance; - internal DateTime createdDateTime; - internal long createdTimestamp; - - protected Message(int db, CommandFlags flags, RedisCommand command) - { - bool dbNeeded = RequiresDatabase(command); - if (db < 0) - { - if (dbNeeded) - { - throw ExceptionFactory.DatabaseRequired(false, command); - } - } - else - { - if (!dbNeeded) - { - throw ExceptionFactory.DatabaseNotRequired(false, command); - } - } - - bool masterOnly = IsMasterOnly(command); - Db = db; - this.command = command; - this.flags = flags & UserSelectableFlags; - if (masterOnly) SetMasterOnly(); - - createdDateTime = DateTime.UtcNow; - createdTimestamp = System.Diagnostics.Stopwatch.GetTimestamp(); - Status = CommandStatus.WaitingToBeSent; - } - - internal void SetMasterOnly() - { - switch (GetMasterSlaveFlags(flags)) - { - case CommandFlags.DemandSlave: - throw ExceptionFactory.MasterOnly(false, command, null, null); - case CommandFlags.DemandMaster: - // already fine as-is - break; - case CommandFlags.PreferMaster: - case CommandFlags.PreferSlave: - default: // we will run this on the master, then - flags = SetMasterSlaveFlags(flags, CommandFlags.DemandMaster); - break; - } - } - - internal void SetProfileStorage(ProfileStorage storage) - { - performance = storage; - performance.SetMessage(this); - } - - internal void PrepareToResend(ServerEndPoint resendTo, bool isMoved) - { - if (performance == null) return; - - var oldPerformance = performance; - - oldPerformance.SetCompleted(); - performance = null; - - createdDateTime = DateTime.UtcNow; - createdTimestamp = System.Diagnostics.Stopwatch.GetTimestamp(); - performance = ProfileStorage.NewAttachedToSameContext(oldPerformance, resendTo, isMoved); - performance.SetMessage(this); - Status = CommandStatus.WaitingToBeSent; - } - - internal CommandStatus Status { get; private set; } - - public RedisCommand Command => command; - - public virtual string CommandAndKey => Command.ToString(); - - public CommandFlags Flags => flags; - - /// - /// Things with the potential to cause harm, or to reveal configuration information - /// - public bool IsAdmin - { - get - { - switch (Command) - { - case RedisCommand.BGREWRITEAOF: - case RedisCommand.BGSAVE: - case RedisCommand.CLIENT: - case RedisCommand.CLUSTER: - case RedisCommand.CONFIG: - case RedisCommand.DEBUG: - case RedisCommand.FLUSHALL: - case RedisCommand.FLUSHDB: - case RedisCommand.INFO: - case RedisCommand.KEYS: - case RedisCommand.MONITOR: - case RedisCommand.SAVE: - case RedisCommand.SHUTDOWN: - case RedisCommand.SLAVEOF: - case RedisCommand.SLOWLOG: - case RedisCommand.SYNC: - return true; - default: - return false; - } - } - } - - public bool IsAsking => (flags & AskingFlag) != 0; - - internal bool IsScriptUnavailable => (flags & ScriptUnavailableFlag) != 0; - - internal void SetScriptUnavailable() - { - flags |= ScriptUnavailableFlag; - } - - public bool IsFireAndForget => (flags & CommandFlags.FireAndForget) != 0; - - public bool IsHighPriority => (flags & CommandFlags.HighPriority) != 0; - - public bool IsInternalCall => (flags & InternalCallFlag) != 0; - - public ResultBox ResultBox => resultBox; - - public static Message Create(int db, CommandFlags flags, RedisCommand command) - { - if (command == RedisCommand.SELECT) - return new SelectMessage(db, flags); - return new CommandMessage(db, flags, command); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key) - { - return new CommandKeyMessage(db, flags, command, key); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1) - { - return new CommandKeyKeyMessage(db, flags, command, key0, key1); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1, RedisValue value) - { - return new CommandKeyKeyValueMessage(db, flags, command, key0, key1, value); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1, RedisKey key2) - { - return new CommandKeyKeyKeyMessage(db, flags, command, key0, key1, key2); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisValue value) - { - return new CommandValueMessage(db, flags, command, value); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue value) - { - return new CommandKeyValueMessage(db, flags, command, key, value); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisChannel channel) - { - return new CommandChannelMessage(db, flags, command, channel); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisChannel channel, RedisValue value) - { - return new CommandChannelValueMessage(db, flags, command, channel, value); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisChannel channel) - { - return new CommandValueChannelMessage(db, flags, command, value, channel); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue value0, RedisValue value1) - { - return new CommandKeyValueValueMessage(db, flags, command, key, value0, value1); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue value0, RedisValue value1, RedisValue value2) - { - return new CommandKeyValueValueValueMessage(db, flags, command, key, value0, value1, value2); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key, GeoEntry[] values) - { - if (values == null) throw new ArgumentNullException(nameof(values)); - if (values.Length == 0) - { - throw new ArgumentOutOfRangeException(nameof(values)); - } - if (values.Length == 1) - { - var value = values[0]; - return Message.Create(db, flags, command, key, value.Longitude, value.Latitude, value.Member); - } - var arr = new RedisValue[3 * values.Length]; - int index = 0; - foreach (var value in values) - { - arr[index++] = value.Longitude; - arr[index++] = value.Latitude; - arr[index++] = value.Member; - } - return new CommandKeyValuesMessage(db, flags, command, key, arr); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue value0, RedisValue value1, RedisValue value2, RedisValue value3) - { - return new CommandKeyValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisValue value0, RedisValue value1) - { - return new CommandValueValueMessage(db, flags, command, value0, value1); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisKey key) - { - return new CommandValueKeyMessage(db, flags, command, value, key); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisValue value0, RedisValue value1, RedisValue value2) - { - return new CommandValueValueValueMessage(db, flags, command, value0, value1, value2); - } - - public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisValue value0, RedisValue value1, RedisValue value2, RedisValue value3, RedisValue value4) - { - return new CommandValueValueValueValueValueMessage(db, flags, command, value0, value1, value2, value3, value4); - } - - public static Message CreateInSlot(int db, int slot, CommandFlags flags, RedisCommand command, RedisValue[] values) - { - return new CommandSlotValuesMessage(db, slot, flags, command, values); - } - - public static bool IsMasterOnly(RedisCommand command) - { - switch (command) - { - case RedisCommand.APPEND: - case RedisCommand.BITOP: - case RedisCommand.BLPOP: - case RedisCommand.BRPOP: - case RedisCommand.BRPOPLPUSH: - case RedisCommand.DECR: - case RedisCommand.DECRBY: - case RedisCommand.DEL: - case RedisCommand.EXPIRE: - case RedisCommand.EXPIREAT: - case RedisCommand.FLUSHALL: - case RedisCommand.FLUSHDB: - case RedisCommand.GETSET: - case RedisCommand.HDEL: - case RedisCommand.HINCRBY: - case RedisCommand.HINCRBYFLOAT: - case RedisCommand.HMSET: - case RedisCommand.HSET: - case RedisCommand.HSETNX: - case RedisCommand.INCR: - case RedisCommand.INCRBY: - case RedisCommand.INCRBYFLOAT: - case RedisCommand.LINSERT: - case RedisCommand.LPOP: - case RedisCommand.LPUSH: - case RedisCommand.LPUSHX: - case RedisCommand.LREM: - case RedisCommand.LSET: - case RedisCommand.LTRIM: - case RedisCommand.MIGRATE: - case RedisCommand.MOVE: - case RedisCommand.MSET: - case RedisCommand.MSETNX: - case RedisCommand.PERSIST: - case RedisCommand.PEXPIRE: - case RedisCommand.PEXPIREAT: - case RedisCommand.PFADD: - case RedisCommand.PFMERGE: - case RedisCommand.PSETEX: - case RedisCommand.RENAME: - case RedisCommand.RENAMENX: - case RedisCommand.RESTORE: - case RedisCommand.RPOP: - case RedisCommand.RPOPLPUSH: - case RedisCommand.RPUSH: - case RedisCommand.RPUSHX: - case RedisCommand.SADD: - case RedisCommand.SDIFFSTORE: - case RedisCommand.SET: - case RedisCommand.SETBIT: - case RedisCommand.SETEX: - case RedisCommand.SETNX: - case RedisCommand.SETRANGE: - case RedisCommand.SINTERSTORE: - case RedisCommand.SMOVE: - case RedisCommand.SPOP: - case RedisCommand.SREM: - case RedisCommand.SUNIONSTORE: - case RedisCommand.ZADD: - case RedisCommand.ZINTERSTORE: - case RedisCommand.ZINCRBY: - case RedisCommand.ZREM: - case RedisCommand.ZREMRANGEBYLEX: - case RedisCommand.ZREMRANGEBYRANK: - case RedisCommand.ZREMRANGEBYSCORE: - case RedisCommand.ZUNIONSTORE: - return true; - default: - return false; - } - } - - public virtual void AppendStormLog(StringBuilder sb) - { - if (Db >= 0) sb.Append(Db).Append(':'); - sb.Append(CommandAndKey); - } - public virtual int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) { return ServerSelectionStrategy.NoSlot; } - public bool IsMasterOnly() - { - // note that the constructor runs the switch statement above, so - // this will alread be true for master-only commands, even if the - // user specified PreferMaster etc - return GetMasterSlaveFlags(flags) == CommandFlags.DemandMaster; - } - - /// - /// This does a few important things: - /// 1: it suppresses error events for commands that the user isn't interested in - /// (i.e. "why does my standalone server keep saying ERR unknown command 'cluster' ?") - /// 2: it allows the initial PING and GET (during connect) to get queued rather - /// than be rejected as no-server-available (note that this doesn't apply to - /// handshake messages, as they bypass the queue completely) - /// 3: it disables non-pref logging, as it is usually server-targeted - /// - public void SetInternalCall() - { - flags |= InternalCallFlag; - } - - public override string ToString() - { - return $"[{Db}]:{CommandAndKey} ({resultProcessor?.GetType().Name ?? "(n/a)"})"; - } - - public void SetResponseReceived() - { - performance?.SetResponseReceived(); - } - - public bool TryComplete(bool isAsync) - { - //Ensure we can never call TryComplete on the same resultBox from two threads by grabbing it now - var currBox = Interlocked.Exchange(ref resultBox, null); - if (currBox != null) - { - var ret = currBox.TryComplete(isAsync); - - //in async mode TryComplete will have unwrapped and recycled resultBox - if (!(ret && isAsync)) - { - //put result box back if it was not already recycled - Interlocked.Exchange(ref resultBox, currBox); - } - - performance?.SetCompleted(); - return ret; - } - else - { - ConnectionMultiplexer.TraceWithoutContext("No result-box to complete for " + Command, "Message"); - performance?.SetCompleted(); - return true; - } - } - - internal static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisKey[] keys) - { - switch (keys.Length) - { - case 0: return new CommandKeyMessage(db, flags, command, key); - case 1: return new CommandKeyKeyMessage(db, flags, command, key, keys[0]); - case 2: return new CommandKeyKeyKeyMessage(db, flags, command, key, keys[0], keys[1]); - default: return new CommandKeyKeysMessage(db, flags, command, key, keys); - } - } - - internal static Message Create(int db, CommandFlags flags, RedisCommand command, IList keys) - { - switch (keys.Count) - { - case 0: return new CommandMessage(db, flags, command); - case 1: return new CommandKeyMessage(db, flags, command, keys[0]); - case 2: return new CommandKeyKeyMessage(db, flags, command, keys[0], keys[1]); - case 3: return new CommandKeyKeyKeyMessage(db, flags, command, keys[0], keys[1], keys[2]); - default: return new CommandKeysMessage(db, flags, command, (keys as RedisKey[]) ?? keys.ToArray()); - } - } - - internal static Message Create(int db, CommandFlags flags, RedisCommand command, IList values) - { - switch (values.Count) - { - case 0: return new CommandMessage(db, flags, command); - case 1: return new CommandValueMessage(db, flags, command, values[0]); - case 2: return new CommandValueValueMessage(db, flags, command, values[0], values[1]); - case 3: return new CommandValueValueValueMessage(db, flags, command, values[0], values[1], values[2]); - // no 4; not worth adding - case 5: return new CommandValueValueValueValueValueMessage(db, flags, command, values[0], values[1], values[2], values[3], values[4]); - default: return new CommandValuesMessage(db, flags, command, (values as RedisValue[]) ?? values.ToArray()); - } - } - - internal static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue[] values) - { - if (values == null) throw new ArgumentNullException(nameof(values)); - switch (values.Length) - { - case 0: return new CommandKeyMessage(db, flags, command, key); - case 1: return new CommandKeyValueMessage(db, flags, command, key, values[0]); - case 2: return new CommandKeyValueValueMessage(db, flags, command, key, values[0], values[1]); - case 3: return new CommandKeyValueValueValueMessage(db, flags, command, key, values[0], values[1], values[2]); - case 4: return new CommandKeyValueValueValueValueMessage(db, flags, command, key, values[0], values[1], values[2], values[3]); - default: return new CommandKeyValuesMessage(db, flags, command, key, values); - } - } - - internal static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisValue[] values, RedisKey key1) - { - if (values == null) throw new ArgumentNullException(nameof(values)); - return new CommandKeyValuesKeyMessage(db, flags, command, key0, values, key1); - } - - internal static CommandFlags GetMasterSlaveFlags(CommandFlags flags) - { - // for the purposes of the switch, we only care about two bits - return flags & MaskMasterServerPreference; - } - internal static bool RequiresDatabase(RedisCommand command) - { - switch (command) - { - case RedisCommand.ASKING: - case RedisCommand.AUTH: - case RedisCommand.BGREWRITEAOF: - case RedisCommand.BGSAVE: - case RedisCommand.CLIENT: - case RedisCommand.CLUSTER: - case RedisCommand.CONFIG: - case RedisCommand.DISCARD: - case RedisCommand.ECHO: - case RedisCommand.FLUSHALL: - case RedisCommand.INFO: - case RedisCommand.LASTSAVE: - case RedisCommand.MONITOR: - case RedisCommand.MULTI: - case RedisCommand.PING: - case RedisCommand.PUBLISH: - case RedisCommand.PUBSUB: - case RedisCommand.PUNSUBSCRIBE: - case RedisCommand.PSUBSCRIBE: - case RedisCommand.QUIT: - case RedisCommand.READONLY: - case RedisCommand.READWRITE: - case RedisCommand.SAVE: - case RedisCommand.SCRIPT: - case RedisCommand.SHUTDOWN: - case RedisCommand.SLAVEOF: - case RedisCommand.SLOWLOG: - case RedisCommand.SUBSCRIBE: - case RedisCommand.SYNC: - case RedisCommand.TIME: - case RedisCommand.UNSUBSCRIBE: - case RedisCommand.SENTINEL: - return false; - default: - return true; - } - } - - internal static CommandFlags SetMasterSlaveFlags(CommandFlags everything, CommandFlags masterSlave) - { - // take away the two flags we don't want, and add back the ones we care about - return (everything & ~(CommandFlags.DemandMaster | CommandFlags.DemandSlave | CommandFlags.PreferMaster | CommandFlags.PreferSlave)) - | masterSlave; - } - - internal void Cancel() - { - resultProcessor?.SetException(this, new TaskCanceledException()); - } - - // true if ready to be completed (i.e. false if re-issued to another server) - internal bool ComputeResult(PhysicalConnection connection, RawResult result) - { - return resultProcessor == null || resultProcessor.SetResult(connection, this, result); - } - - internal void Fail(ConnectionFailureType failure, Exception innerException) - { - PhysicalConnection.IdentifyFailureType(innerException, ref failure); - resultProcessor?.ConnectionFail(this, failure, innerException); - } - - internal void SetEnqueued() - { - performance?.SetEnqueued(); - } - - internal void SetRequestSent() - { - Status = CommandStatus.Sent; - performance?.SetRequestSent(); - } - - internal void SetAsking(bool value) - { - if (value) flags |= AskingFlag; // the bits giveth - else flags &= ~AskingFlag; // and the bits taketh away - } - - internal void SetNoRedirect() - { - flags |= CommandFlags.NoRedirect; - } - - internal void SetPreferMaster() - { - flags = (flags & ~MaskMasterServerPreference) | CommandFlags.PreferMaster; - } - - internal void SetPreferSlave() - { - flags = (flags & ~MaskMasterServerPreference) | CommandFlags.PreferSlave; - } - internal void SetSource(ResultProcessor resultProcessor, ResultBox resultBox) - { // note order here reversed to prevent overload resolution errors - this.resultBox = resultBox; - this.resultProcessor = resultProcessor; - } - - internal void SetSource(ResultBox resultBox, ResultProcessor resultProcessor) - { - this.resultBox = resultBox; - this.resultProcessor = resultProcessor; - } - - internal abstract void WriteImpl(PhysicalConnection physical); - - internal void WriteTo(PhysicalConnection physical) - { - try - { - WriteImpl(physical); - } - catch (RedisCommandException) - { // these have specific meaning; don't wrap - throw; - } - catch (Exception ex) - { - physical?.OnInternalError(ex); - Fail(ConnectionFailureType.InternalFailure, ex); - } - } - internal abstract class CommandChannelBase : Message - { - protected readonly RedisChannel Channel; - - public CommandChannelBase(int db, CommandFlags flags, RedisCommand command, RedisChannel channel) : base(db, flags, command) - { - channel.AssertNotNull(); - Channel = channel; - } - - public override string CommandAndKey => Command + " " + Channel; - } - - internal abstract class CommandKeyBase : Message - { - protected readonly RedisKey Key; - - public CommandKeyBase(int db, CommandFlags flags, RedisCommand command, RedisKey key) : base(db, flags, command) - { - key.AssertNotNull(); - Key = key; - } - - public override string CommandAndKey => Command + " " + (string)Key; - - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - return serverSelectionStrategy.HashSlot(Key); - } - } - sealed class CommandChannelMessage : CommandChannelBase - { - public CommandChannelMessage(int db, CommandFlags flags, RedisCommand command, RedisChannel channel) : base(db, flags, command, channel) - { } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 1); - physical.Write(Channel); - } - } - - sealed class CommandChannelValueMessage : CommandChannelBase - { - private readonly RedisValue value; - public CommandChannelValueMessage(int db, CommandFlags flags, RedisCommand command, RedisChannel channel, RedisValue value) : base(db, flags, command, channel) - { - value.AssertNotNull(); - this.value = value; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 2); - physical.Write(Channel); - physical.Write(value); - } - } - - sealed class CommandKeyKeyKeyMessage : CommandKeyBase - { - private readonly RedisKey key1, key2; - public CommandKeyKeyKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1, RedisKey key2) : base(db, flags, command, key0) - { - key1.AssertNotNull(); - key2.AssertNotNull(); - this.key1 = key1; - this.key2 = key2; - } - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - var slot = serverSelectionStrategy.HashSlot(Key); - slot = serverSelectionStrategy.CombineSlot(slot, key1); - slot = serverSelectionStrategy.CombineSlot(slot, key2); - return slot; - } - - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 3); - physical.Write(Key); - physical.Write(key1); - physical.Write(key2); - } - } - - class CommandKeyKeyMessage : CommandKeyBase - { - protected readonly RedisKey key1; - public CommandKeyKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1) : base(db, flags, command, key0) - { - key1.AssertNotNull(); - this.key1 = key1; - } - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - var slot = serverSelectionStrategy.HashSlot(Key); - slot = serverSelectionStrategy.CombineSlot(slot, key1); - return slot; - } - - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 2); - physical.Write(Key); - physical.Write(key1); - } - } - sealed class CommandKeyKeysMessage : CommandKeyBase - { - private readonly RedisKey[] keys; - public CommandKeyKeysMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisKey[] keys) : base(db, flags, command, key) - { - for (int i = 0; i < keys.Length; i++) - { - keys[i].AssertNotNull(); - } - this.keys = keys; - } - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - var slot = serverSelectionStrategy.HashSlot(Key); - for (int i = 0; i < keys.Length; i++) - { - slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); - } - return slot; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(command, keys.Length + 1); - physical.Write(Key); - for (int i = 0; i < keys.Length; i++) - { - physical.Write(keys[i]); - } - } - } - - sealed class CommandKeyKeyValueMessage : CommandKeyKeyMessage - { - private readonly RedisValue value; - public CommandKeyKeyValueMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1, RedisValue value) : base(db, flags, command, key0, key1) - { - value.AssertNotNull(); - this.value = value; - } - - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 3); - physical.Write(Key); - physical.Write(key1); - physical.Write(value); - } - } - sealed class CommandKeyMessage : CommandKeyBase - { - public CommandKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key) : base(db, flags, command, key) - { } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 1); - physical.Write(Key); - } - } - sealed class CommandValuesMessage : Message - { - private readonly RedisValue[] values; - public CommandValuesMessage(int db, CommandFlags flags, RedisCommand command, RedisValue[] values) : base(db, flags, command) - { - for (int i = 0; i < values.Length; i++) - { - values[i].AssertNotNull(); - } - this.values = values; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(command, values.Length); - for (int i = 0; i < values.Length; i++) - { - physical.Write(values[i]); - } - } - } - sealed class CommandKeysMessage : Message - { - private readonly RedisKey[] keys; - public CommandKeysMessage(int db, CommandFlags flags, RedisCommand command, RedisKey[] keys) : base(db, flags, command) - { - for (int i = 0; i < keys.Length; i++) - { - keys[i].AssertNotNull(); - } - this.keys = keys; - } - - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - int slot = ServerSelectionStrategy.NoSlot; - for (int i = 0; i < keys.Length; i++) - { - slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); - } - return slot; - } - - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(command, keys.Length); - for (int i = 0; i < keys.Length; i++) - { - physical.Write(keys[i]); - } - } - } - - sealed class CommandKeyValueMessage : CommandKeyBase - { - private readonly RedisValue value; - public CommandKeyValueMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue value) : base(db, flags, command, key) - { - value.AssertNotNull(); - this.value = value; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 2); - physical.Write(Key); - physical.Write(value); - } - } - sealed class CommandKeyValuesKeyMessage : CommandKeyBase - { - private readonly RedisKey key1; - private readonly RedisValue[] values; - public CommandKeyValuesKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisValue[] values, RedisKey key1) : base(db, flags, command, key0) - { - for (int i = 0; i < values.Length; i++) - { - values[i].AssertNotNull(); - } - this.values = values; - key1.AssertNotNull(); - this.key1 = key1; - } - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - var slot = base.GetHashSlot(serverSelectionStrategy); - return serverSelectionStrategy.CombineSlot(slot, key1); - } - - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, values.Length + 2); - physical.Write(Key); - for (int i = 0; i < values.Length; i++) physical.Write(values[i]); - physical.Write(key1); - } - } - - sealed class CommandKeyValuesMessage : CommandKeyBase - { - private readonly RedisValue[] values; - public CommandKeyValuesMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue[] values) : base(db, flags, command, key) - { - for (int i = 0; i < values.Length; i++) - { - values[i].AssertNotNull(); - } - this.values = values; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, values.Length + 1); - physical.Write(Key); - for (int i = 0; i < values.Length; i++) physical.Write(values[i]); - } - } - - sealed class CommandKeyValueValueMessage : CommandKeyBase - { - private readonly RedisValue value0, value1; - public CommandKeyValueValueMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue value0, RedisValue value1) : base(db, flags, command, key) - { - value0.AssertNotNull(); - value1.AssertNotNull(); - this.value0 = value0; - this.value1 = value1; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 3); - physical.Write(Key); - physical.Write(value0); - physical.Write(value1); - } - } - - sealed class CommandKeyValueValueValueMessage : CommandKeyBase - { - private readonly RedisValue value0, value1, value2; - public CommandKeyValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue value0, RedisValue value1, RedisValue value2) : base(db, flags, command, key) - { - value0.AssertNotNull(); - value1.AssertNotNull(); - value2.AssertNotNull(); - this.value0 = value0; - this.value1 = value1; - this.value2 = value2; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 4); - physical.Write(Key); - physical.Write(value0); - physical.Write(value1); - physical.Write(value2); - } - } - - sealed class CommandKeyValueValueValueValueMessage : CommandKeyBase - { - private readonly RedisValue value0, value1, value2, value3; - public CommandKeyValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue value0, RedisValue value1, RedisValue value2, RedisValue value3) : base(db, flags, command, key) - { - value0.AssertNotNull(); - value1.AssertNotNull(); - value2.AssertNotNull(); - value3.AssertNotNull(); - this.value0 = value0; - this.value1 = value1; - this.value2 = value2; - this.value3 = value3; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 5); - physical.Write(Key); - physical.Write(value0); - physical.Write(value1); - physical.Write(value2); - physical.Write(value3); - } - } - - sealed class CommandMessage : Message - { - public CommandMessage(int db, CommandFlags flags, RedisCommand command) : base(db, flags, command) { } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 0); - } - } - - private class CommandSlotValuesMessage : Message - { - private readonly int slot; - private readonly RedisValue[] values; - - public CommandSlotValuesMessage(int db, int slot, CommandFlags flags, RedisCommand command, RedisValue[] values) - : base(db, flags, command) - { - this.slot = slot; - for (int i = 0; i < values.Length; i++) - { - values[i].AssertNotNull(); - } - this.values = values; - } - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - return slot; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(command, values.Length); - for (int i = 0; i < values.Length; i++) - { - physical.Write(values[i]); - } - } - } - - sealed class CommandValueChannelMessage : CommandChannelBase - { - private readonly RedisValue value; - public CommandValueChannelMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisChannel channel) : base(db, flags, command, channel) - { - value.AssertNotNull(); - this.value = value; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 2); - physical.Write(value); - physical.Write(Channel); - } - } - sealed class CommandValueKeyMessage : CommandKeyBase - { - private readonly RedisValue value; - - public CommandValueKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisKey key) : base(db, flags, command, key) - { - value.AssertNotNull(); - this.value = value; - } - - public override void AppendStormLog(StringBuilder sb) - { - base.AppendStormLog(sb); - sb.Append(" (").Append((string)value).Append(')'); - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 2); - physical.Write(value); - physical.Write(Key); - } - } - - sealed class CommandValueMessage : Message - { - private readonly RedisValue value; - public CommandValueMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value) : base(db, flags, command) - { - value.AssertNotNull(); - this.value = value; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 1); - physical.Write(value); - } - } - - sealed class CommandValueValueMessage : Message - { - private readonly RedisValue value0, value1; - public CommandValueValueMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value0, RedisValue value1) : base(db, flags, command) - { - value0.AssertNotNull(); - value1.AssertNotNull(); - this.value0 = value0; - this.value1 = value1; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 2); - physical.Write(value0); - physical.Write(value1); - } - } - - sealed class CommandValueValueValueMessage : Message - { - private readonly RedisValue value0, value1, value2; - public CommandValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value0, RedisValue value1, RedisValue value2) : base(db, flags, command) - { - value0.AssertNotNull(); - value1.AssertNotNull(); - value2.AssertNotNull(); - this.value0 = value0; - this.value1 = value1; - this.value2 = value2; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 3); - physical.Write(value0); - physical.Write(value1); - physical.Write(value2); - } - } - - sealed class CommandValueValueValueValueValueMessage : Message - { - private readonly RedisValue value0, value1, value2, value3, value4; - public CommandValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value0, RedisValue value1, RedisValue value2, RedisValue value3, RedisValue value4) : base(db, flags, command) - { - value0.AssertNotNull(); - value1.AssertNotNull(); - value2.AssertNotNull(); - value3.AssertNotNull(); - value4.AssertNotNull(); - this.value0 = value0; - this.value1 = value1; - this.value2 = value2; - this.value3 = value3; - this.value4 = value4; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 5); - physical.Write(value0); - physical.Write(value1); - physical.Write(value2); - physical.Write(value3); - physical.Write(value4); - } - } - - sealed class SelectMessage : Message - { - public SelectMessage(int db, CommandFlags flags) : base(db, flags, RedisCommand.SELECT) - { - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 1); - physical.Write(Db); - } - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/MessageCompletable.cs b/StackExchange.Redis/StackExchange/Redis/MessageCompletable.cs deleted file mode 100644 index 5c0fc51f5..000000000 --- a/StackExchange.Redis/StackExchange/Redis/MessageCompletable.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Text; - -namespace StackExchange.Redis -{ - sealed class MessageCompletable : ICompletable - { - private readonly RedisChannel channel; - - private readonly Action handler; - - private readonly RedisValue message; - - public MessageCompletable(RedisChannel channel, RedisValue message, Action handler) - { - this.channel = channel; - this.message = message; - this.handler = handler; - } - - public override string ToString() - { - return (string)channel; - } - public bool TryComplete(bool isAsync) - { - if (handler == null) return true; - if (isAsync) - { - ConnectionMultiplexer.TraceWithoutContext("Invoking...: " + (string)channel, "Subscription"); - foreach(Action sub in handler.GetInvocationList()) - { - try { sub.Invoke(channel, message); } - catch { } - } - ConnectionMultiplexer.TraceWithoutContext("Invoke complete", "Subscription"); - return true; - } - // needs to be called async (unless there is nothing to do!) - return false; - } - - void ICompletable.AppendStormLog(StringBuilder sb) - { - sb.Append("event, pub/sub: ").Append((string)channel); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/MessageQueue.cs b/StackExchange.Redis/StackExchange/Redis/MessageQueue.cs deleted file mode 100644 index 9774e2296..000000000 --- a/StackExchange.Redis/StackExchange/Redis/MessageQueue.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System.Collections.Generic; -using System.Text; - -namespace StackExchange.Redis -{ - sealed partial class MessageQueue - { - private readonly Queue - regular = new Queue(), - high = new Queue(); - - public object SyncLock => regular; - - public Message Dequeue() - { - lock (regular) - { - if (high.Count != 0) - { - return high.Dequeue(); - } - if (regular.Count != 0) - { - return regular.Dequeue(); - } - } - return null; - } - - /// - /// Checks both high-pri and regular queues to see if the next item is a PING, and if so: dequeues it and returns it - /// - public Message DequeueUnsentPing(out int queueLength) - { - lock (regular) - { - Message peeked; - queueLength = high.Count + regular.Count; - //In a disconnect scenario, we don't want to complete the Ping message twice, - //dequeue it now so it wont get dequeued in AbortUnsent (if we're going down that code path) - if (high.Count != 0 && (peeked = high.Peek()).Command == RedisCommand.PING) - { - queueLength--; - return high.Dequeue(); - } - if (regular.Count != 0 && (peeked = regular.Peek()).Command == RedisCommand.PING) - { - queueLength--; - return regular.Dequeue(); - } - } - return null; - } - - public bool Push(Message message) - { - lock (regular) - { - (message.IsHighPriority ? high : regular).Enqueue(message); - return high.Count + regular.Count == 1; - } - } - - internal bool Any() - { - lock (regular) - { - return high.Count != 0 || regular.Count != 0; - } - } - - internal int Count() - { - lock (regular) - { - return high.Count + regular.Count; - } - } - - internal Message[] DequeueAll() - { - lock (regular) - { - int count = high.Count + regular.Count; - if (count == 0) return Message.EmptyArray; - - var arr = new Message[count]; - high.CopyTo(arr, 0); - regular.CopyTo(arr, high.Count); - high.Clear(); - regular.Clear(); - return arr; - } - } - internal void GetStormLog(StringBuilder sb) - { - lock(regular) - { - int total = 0; - if (high.Count == 0 && regular.Count == 0) return; - sb.Append("Unsent: ").Append(high.Count + regular.Count).AppendLine(); - foreach (var item in high) - { - if (++total >= 500) break; - item.AppendStormLog(sb); - sb.AppendLine(); - } - foreach (var item in regular) - { - if (++total >= 500) break; - - item.AppendStormLog(sb); - sb.AppendLine(); - } - } - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/Order.cs b/StackExchange.Redis/StackExchange/Redis/Order.cs deleted file mode 100644 index 34f4cb36f..000000000 --- a/StackExchange.Redis/StackExchange/Redis/Order.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace StackExchange.Redis -{ - /// - /// The direction in which to sequence elements - /// - public enum Order - { - /// - /// Ordered from low values to high values - /// - Ascending, - /// - /// Ordered from high values to low values - /// - Descending - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/PhysicalBridge.cs b/StackExchange.Redis/StackExchange/Redis/PhysicalBridge.cs deleted file mode 100644 index 7ec8ecce7..000000000 --- a/StackExchange.Redis/StackExchange/Redis/PhysicalBridge.cs +++ /dev/null @@ -1,850 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; - -namespace StackExchange.Redis -{ - enum WriteResult - { - QueueEmptyAfterWrite, - NothingToDo, - MoreWork, - CompetingWriter, - NoConnection, - } - - sealed partial class PhysicalBridge : IDisposable - { - internal readonly string Name; - - internal int inWriteQueue = 0; - - const int ProfileLogSamples = 10; - - const double ProfileLogSeconds = (ConnectionMultiplexer.MillisecondsPerHeartbeat * ProfileLogSamples) / 1000.0; - - private static readonly Message ReusableAskingCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.ASKING); - - private readonly CompletionManager completionManager; - readonly long[] profileLog = new long[ProfileLogSamples]; - private readonly MessageQueue queue = new MessageQueue(); - int activeWriters = 0; - private int beating; - int failConnectCount = 0; - volatile bool isDisposed; - long nonPreferredEndpointCount; - - //private volatile int missedHeartbeats; - private long operationCount, socketCount; - private volatile PhysicalConnection physical; - - - long profileLastLog; - int profileLogIndex; - volatile bool reportNextFailure = true, reconfigureNextFailure = false; - private volatile int state = (int)State.Disconnected; - - public PhysicalBridge(ServerEndPoint serverEndPoint, ConnectionType type) - { - ServerEndPoint = serverEndPoint; - ConnectionType = type; - Multiplexer = serverEndPoint.Multiplexer; - Name = Format.ToString(serverEndPoint.EndPoint) + "/" + ConnectionType.ToString(); - completionManager = new CompletionManager(Multiplexer, Name); - } - - public enum State : byte - { - Connecting, - ConnectedEstablishing, - ConnectedEstablished, - Disconnected - } - - public Exception LastException { get; private set; } - - public ConnectionType ConnectionType { get; } - - public bool IsConnected => state == (int)State.ConnectedEstablished; - - public ConnectionMultiplexer Multiplexer { get; } - - public ServerEndPoint ServerEndPoint { get; } - - public long SubscriptionCount - { - get - { - var tmp = physical; - return tmp == null ? 0 : physical.SubscriptionCount; - } - } - - internal State ConnectionState => (State)state; - internal bool IsBeating => Interlocked.CompareExchange(ref beating, 0, 0) == 1; - - internal long OperationCount => Interlocked.Read(ref operationCount); - - public void CompleteSyncOrAsync(ICompletable operation) - { - completionManager.CompleteSyncOrAsync(operation); - } - - public void Dispose() - { - isDisposed = true; - using (var tmp = physical) - { - physical = null; - } - } - - public void ReportNextFailure() - { - reportNextFailure = true; - } - - public override string ToString() - { - return ConnectionType + "/" + Format.ToString(ServerEndPoint.EndPoint); - } - - public void TryConnect(TextWriter log) - { - GetConnection(log); - } - - public bool TryEnqueue(Message message, bool isSlave) - { - if (isDisposed) throw new ObjectDisposedException(Name); - if (!IsConnected) - { - if (message.IsInternalCall) - { - // you can go in the queue, but we won't be starting - // a worker, because the handshake has not completed - queue.Push(message); - message.SetEnqueued(); - return true; - } - else - { - // sorry, we're just not ready for you yet; - return false; - } - } - - bool reqWrite = queue.Push(message); - message.SetEnqueued(); - LogNonPreferred(message.Flags, isSlave); - Trace("Now pending: " + GetPendingCount()); - - if (reqWrite) - { - Multiplexer.RequestWrite(this, false); - } - return true; - } - internal void AppendProfile(StringBuilder sb) - { - long[] clone = new long[ProfileLogSamples + 1]; - for (int i = 0; i < ProfileLogSamples; i++) - { - clone[i] = Interlocked.Read(ref profileLog[i]); - } - clone[ProfileLogSamples] = Interlocked.Read(ref operationCount); - Array.Sort(clone); - sb.Append(" ").Append(clone[0]); - for (int i = 1; i < clone.Length; i++) - { - if (clone[i] != clone[i - 1]) - { - sb.Append("+").Append(clone[i] - clone[i - 1]); - } - } - if (clone[0] != clone[ProfileLogSamples]) - { - sb.Append("=").Append(clone[ProfileLogSamples]); - } - double rate = (clone[ProfileLogSamples] - clone[0]) / ProfileLogSeconds; - sb.Append(" (").Append(rate.ToString("N2")).Append(" ops/s; spans ").Append(ProfileLogSeconds).Append("s)"); - } - - internal bool ConfirmRemoveFromWriteQueue() - { - lock (queue.SyncLock) - { - if (queue.Count() == 0) - { - Interlocked.Exchange(ref inWriteQueue, 0); - return true; - } - } - return false; - } - - internal void GetCounters(ConnectionCounters counters) - { - counters.PendingUnsentItems = queue.Count(); - counters.OperationCount = OperationCount; - counters.SocketCount = Interlocked.Read(ref socketCount); - counters.WriterCount = Interlocked.CompareExchange(ref activeWriters, 0, 0); - counters.NonPreferredEndpointCount = Interlocked.Read(ref nonPreferredEndpointCount); - completionManager.GetCounters(counters); - physical?.GetCounters(counters); - } - - internal int GetOutstandingCount(out int inst, out int qu, out int qs, out int qc, out int wr, out int wq, out int @in, out int ar) - {// defined as: PendingUnsentItems + SentItemsAwaitingResponse + ResponsesAwaitingAsyncCompletion - inst = (int)(Interlocked.Read(ref operationCount) - Interlocked.Read(ref profileLastLog)); - qu = queue.Count(); - var tmp = physical; - if(tmp == null) - { - qs = @in = ar = 0; - } else - { - qs = tmp.GetSentAwaitingResponseCount(); - @in = tmp.GetAvailableInboundBytes(out ar); - } - qc = completionManager.GetOutstandingCount(); - wr = Interlocked.CompareExchange(ref activeWriters, 0, 0); - wq = Interlocked.CompareExchange(ref inWriteQueue, 0, 0); - return qu + qs + qc; - } - - internal int GetPendingCount() - { - return queue.Count(); - } - - internal string GetStormLog() - { - var sb = new StringBuilder("Storm log for ").Append(Format.ToString(ServerEndPoint.EndPoint)).Append(" / ").Append(ConnectionType) - .Append(" at ").Append(DateTime.UtcNow) - .AppendLine().AppendLine(); - queue.GetStormLog(sb); - physical?.GetStormLog(sb); - completionManager.GetStormLog(sb); - sb.Append("Circular op-count snapshot:"); - AppendProfile(sb); - sb.AppendLine(); - return sb.ToString(); - } - - internal void IncrementOpCount() - { - Interlocked.Increment(ref operationCount); - } - - internal void KeepAlive() - { - var commandMap = Multiplexer.CommandMap; - Message msg = null; - switch (ConnectionType) - { - case ConnectionType.Interactive: - msg = ServerEndPoint.GetTracerMessage(false); - msg.SetSource(ResultProcessor.Tracer, null); - break; - case ConnectionType.Subscription: - if (commandMap.IsAvailable(RedisCommand.UNSUBSCRIBE)) - { - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.UNSUBSCRIBE, - (RedisChannel)Guid.NewGuid().ToByteArray()); - msg.SetSource(ResultProcessor.TrackSubscriptions, null); - } - break; - } - if (msg != null) - { - msg.SetInternalCall(); - Multiplexer.Trace("Enqueue: " + msg); - if (!TryEnqueue(msg, ServerEndPoint.IsSlave)) - { - OnInternalError(ExceptionFactory.NoConnectionAvailable(Multiplexer.IncludeDetailInExceptions, Multiplexer.IncludePerformanceCountersInExceptions, msg.Command, msg, ServerEndPoint, Multiplexer.GetServerSnapshot())); - } - } - } - - internal void OnConnected(PhysicalConnection connection, TextWriter log) - { - Trace("OnConnected"); - if (physical == connection && !isDisposed && ChangeState(State.Connecting, State.ConnectedEstablishing)) - { - ServerEndPoint.OnEstablishing(connection, log); - } - else - { - try - { - connection.Dispose(); - } - catch - { } - } - } - - - internal void ResetNonConnected() - { - var tmp = physical; - if (tmp != null && state != (int)State.ConnectedEstablished) - { - tmp.RecordConnectionFailed(ConnectionFailureType.UnableToConnect); - } - GetConnection(null); - } - - internal void OnConnectionFailed(PhysicalConnection connection, ConnectionFailureType failureType, Exception innerException) - { - if (reportNextFailure) - { - LastException = innerException; - reportNextFailure = false; // until it is restored - var endpoint = ServerEndPoint.EndPoint; - Multiplexer.OnConnectionFailed(endpoint, ConnectionType, failureType, innerException, reconfigureNextFailure); - } - } - - internal void OnDisconnected(ConnectionFailureType failureType, PhysicalConnection connection, out bool isCurrent, out State oldState) - { - Trace("OnDisconnected"); - - // if the next thing in the pipe is a PING, we can tell it that we failed (this really helps spot doomed connects) - int count; - var ping = queue.DequeueUnsentPing(out count); - if (ping != null) - { - Trace("Marking PING as failed (queue length: " + count + ")"); - ping.Fail(failureType, null); - CompleteSyncOrAsync(ping); - } - oldState = default(State); // only defined when isCurrent = true - if (isCurrent = (physical == connection)) - { - Trace("Bridge noting disconnect from active connection" + (isDisposed ? " (disposed)" : "")); - oldState = ChangeState(State.Disconnected); - physical = null; - - if (!isDisposed && Interlocked.Increment(ref failConnectCount) == 1) - { - GetConnection(null); // try to connect immediately - } - } - else if (physical == null) - { - Trace("Bridge noting disconnect (already terminated)"); - } - else - { - Trace("Bridge noting disconnect, but from different connection"); - } - } - - internal void OnFullyEstablished(PhysicalConnection connection) - { - Trace("OnFullyEstablished"); - if (physical == connection && !isDisposed && ChangeState(State.ConnectedEstablishing, State.ConnectedEstablished)) - { - reportNextFailure = reconfigureNextFailure = true; - LastException = null; - Interlocked.Exchange(ref failConnectCount, 0); - ServerEndPoint.OnFullyEstablished(connection); - Multiplexer.RequestWrite(this, true); - if(ConnectionType == ConnectionType.Interactive) ServerEndPoint.CheckInfoReplication(); - } - else - { - try { connection.Dispose(); } catch { } - } - } - - private int connectStartTicks; - private long connectTimeoutRetryCount = 0; - - internal void OnHeartbeat(bool ifConnectedOnly) - { - bool runThisTime = false; - try - { - runThisTime = !isDisposed && Interlocked.CompareExchange(ref beating, 1, 0) == 0; - if (!runThisTime) return; - - uint index = (uint)Interlocked.Increment(ref profileLogIndex); - long newSampleCount = Interlocked.Read(ref operationCount); - Interlocked.Exchange(ref profileLog[index % ProfileLogSamples], newSampleCount); - Interlocked.Exchange(ref profileLastLog, newSampleCount); - Trace("OnHeartbeat: " + (State)state); - switch (state) - { - case (int)State.Connecting: - int connectTimeMilliseconds = unchecked(Environment.TickCount - VolatileWrapper.Read(ref connectStartTicks)); - bool shouldRetry = Multiplexer.RawConfig.ReconnectRetryPolicy.ShouldRetry(Interlocked.Read(ref connectTimeoutRetryCount), connectTimeMilliseconds); - if (shouldRetry) - { - Interlocked.Increment(ref connectTimeoutRetryCount); - LastException = ExceptionFactory.UnableToConnect(Multiplexer.RawConfig.AbortOnConnectFail, "ConnectTimeout"); - Trace("Aborting connect"); - // abort and reconnect - var snapshot = physical; - bool isCurrent; - State oldState; - OnDisconnected(ConnectionFailureType.UnableToConnect, snapshot, out isCurrent, out oldState); - using (snapshot) { } // dispose etc - TryConnect(null); - } - if (!ifConnectedOnly) - { - AbortUnsent(); - } - break; - case (int)State.ConnectedEstablishing: - case (int)State.ConnectedEstablished: - var tmp = physical; - if (tmp != null) - { - if(state == (int)State.ConnectedEstablished) - { - Interlocked.Exchange(ref connectTimeoutRetryCount, 0); - tmp.Bridge.ServerEndPoint.ClearUnselectable(UnselectableFlags.DidNotRespond); - } - tmp.OnHeartbeat(); - int writeEverySeconds = ServerEndPoint.WriteEverySeconds, - checkConfigSeconds = Multiplexer.RawConfig.ConfigCheckSeconds; - - if(state == (int)State.ConnectedEstablished && ConnectionType == ConnectionType.Interactive - && checkConfigSeconds > 0 && ServerEndPoint.LastInfoReplicationCheckSecondsAgo >= checkConfigSeconds - && ServerEndPoint.CheckInfoReplication()) - { - // that serves as a keep-alive, if it is accepted - } - else if (writeEverySeconds > 0 && tmp.LastWriteSecondsAgo >= writeEverySeconds) - { - Trace("OnHeartbeat - overdue"); - if (state == (int)State.ConnectedEstablished) - { - KeepAlive(); - } - else - { - bool ignore; - State oldState; - OnDisconnected(ConnectionFailureType.SocketFailure, tmp, out ignore, out oldState); - } - } - else if (!queue.Any() && tmp.GetSentAwaitingResponseCount() != 0) - { - // there's a chance this is a dead socket; sending data will shake that - // up a bit, so if we have an empty unsent queue and a non-empty sent - // queue, test the socket - KeepAlive(); - } - } - break; - case (int)State.Disconnected: - Interlocked.Exchange(ref connectTimeoutRetryCount, 0); - if (!ifConnectedOnly) - { - AbortUnsent(); - Multiplexer.Trace("Resurrecting " + this.ToString()); - GetConnection(null); - } - break; - default: - Interlocked.Exchange(ref connectTimeoutRetryCount, 0); - if (!ifConnectedOnly) - { - AbortUnsent(); - } - break; - } - } - catch (Exception ex) - { - OnInternalError(ex); - Trace("OnHeartbeat error: " + ex.Message); - } - finally - { - if (runThisTime) Interlocked.Exchange(ref beating, 0); - } - } - - internal void RemovePhysical(PhysicalConnection connection) - { -#pragma warning disable 0420 - Interlocked.CompareExchange(ref physical, null, connection); -#pragma warning restore 0420 - } - - [Conditional("VERBOSE")] - internal void Trace(string message) - { - Multiplexer.Trace(message, ToString()); - } - - [Conditional("VERBOSE")] - internal void Trace(bool condition, string message) - { - if (condition) Multiplexer.Trace(message, ToString()); - } - - internal bool TryEnqueue(List messages, bool isSlave) - { - if (messages == null || messages.Count == 0) return true; - - if (isDisposed) throw new ObjectDisposedException(Name); - - if (!IsConnected) - { - return false; - } - bool reqWrite = false; - foreach (var message in messages) - { // deliberately not taking a single lock here; we don't care if - // other threads manage to interleave - in fact, it would be desirable - // (to avoid a batch monopolising the connection) - if (queue.Push(message)) reqWrite = true; - LogNonPreferred(message.Flags, isSlave); - } - Trace("Now pending: " + GetPendingCount()); - if (reqWrite) // was empty before - { - Multiplexer.RequestWrite(this, false); - } - return true; - } - - /// - /// This writes a message **directly** to the output stream; note - /// that this ignores the queue, so should only be used *either* - /// from the regular dequeue loop, *or* from the "I've just - /// connected" handshake (when there is no dequeue loop) - otherwise, - /// you can pretty much assume you're going to destroy the stream - /// - internal bool WriteMessageDirect(PhysicalConnection tmp, Message next) - { - Trace("Writing: " + next); - if (next is IMultiMessage) - { - SelectDatabase(tmp, next); // need to switch database *before* the transaction - foreach (var subCommand in ((IMultiMessage)next).GetMessages(tmp)) - { - if (!WriteMessageToServer(tmp, subCommand)) - { - // we screwed up; abort; note that WriteMessageToServer already - // killed the underlying connection - Trace("Unable to write to server"); - next.Fail(ConnectionFailureType.ProtocolFailure, null); - CompleteSyncOrAsync(next); - return false; - } - } - - next.SetRequestSent(); - - return true; - } - else - { - return WriteMessageToServer(tmp, next); - } - } - - internal WriteResult WriteQueue(int maxWork) - { - bool weAreWriter = false; - PhysicalConnection conn = null; - try - { - Trace("Writing queue from bridge"); - - weAreWriter = Interlocked.CompareExchange(ref activeWriters, 1, 0) == 0; - if (!weAreWriter) - { - Trace("(aborting: existing writer)"); - return WriteResult.CompetingWriter; - } - - conn = GetConnection(null); - if (conn == null) - { - AbortUnsent(); - Trace("Connection not available; exiting"); - return WriteResult.NoConnection; - } - - Message last; - int count = 0; - while (true) - { - var next = queue.Dequeue(); - if (next == null) - { - Trace("Nothing to write; exiting"); - if(count == 0) - { - conn.Flush(); // only flush on an empty run - return WriteResult.NothingToDo; - } - return WriteResult.QueueEmptyAfterWrite; - } - last = next; - - Trace("Now pending: " + GetPendingCount()); - if (!WriteMessageDirect(conn, next)) - { - AbortUnsent(); - Trace("write failed; connection is toast; exiting"); - return WriteResult.NoConnection; - } - count++; - if (maxWork > 0 && count >= maxWork) - { - Trace("Work limit; exiting"); - Trace(last != null, "Flushed up to: " + last); - conn.Flush(); - break; - } - } - } - catch (IOException ex) - { - if (conn != null) - { - conn.RecordConnectionFailed(ConnectionFailureType.SocketFailure, ex); - conn = null; - } - AbortUnsent(); - } - catch (Exception ex) - { - AbortUnsent(); - OnInternalError(ex); - } - finally - { - if (weAreWriter) - { - Interlocked.Exchange(ref activeWriters, 0); - Trace("Exiting writer"); - } - } - return queue.Any() ? WriteResult.MoreWork : WriteResult.QueueEmptyAfterWrite; - } - - private void AbortUnsent() - { - var dead = queue.DequeueAll(); - Trace(dead.Length != 0, "Aborting " + dead.Length + " messages"); - for (int i = 0; i < dead.Length; i++) - { - var msg = dead[i]; - msg.Fail(ConnectionFailureType.UnableToResolvePhysicalConnection, null); - CompleteSyncOrAsync(msg); - } - } - - private State ChangeState(State newState) - { -#pragma warning disable 0420 - var oldState = (State)Interlocked.Exchange(ref state, (int)newState); -#pragma warning restore 0420 - if (oldState != newState) - { - Multiplexer.Trace(ConnectionType + " state changed from " + oldState + " to " + newState); - - if (newState == State.Disconnected) - { - AbortUnsent(); - } - } - return oldState; - } - - private bool ChangeState(State oldState, State newState) - { -#pragma warning disable 0420 - bool result = Interlocked.CompareExchange(ref state, (int)newState, (int)oldState) == (int)oldState; -#pragma warning restore 0420 - if (result) - { - Multiplexer.Trace(ConnectionType + " state changed from " + oldState + " to " + newState); - } - return result; - } - - private PhysicalConnection GetConnection(TextWriter log) - { - if (state == (int)State.Disconnected) - { - try - { - if (!Multiplexer.IsDisposed) - { - Multiplexer.LogLocked(log, "Connecting {0}...", Name); - Multiplexer.Trace("Connecting...", Name); - if (ChangeState(State.Disconnected, State.Connecting)) - { - Interlocked.Increment(ref socketCount); - Interlocked.Exchange(ref connectStartTicks, Environment.TickCount); - // separate creation and connection for case when connection completes synchronously - // in that case PhysicalConnection will call back to PhysicalBridge, and most of PhysicalBridge methods assumes that physical is not null; - physical = new PhysicalConnection(this); - physical.BeginConnect(log); - } - } - return null; - } - catch (Exception ex) - { - Multiplexer.LogLocked(log, "Connect {0} failed: {1}", Name, ex.Message); - Multiplexer.Trace("Connect failed: " + ex.Message, Name); - ChangeState(State.Disconnected); - OnInternalError(ex); - throw; - } - } - return physical; - } - - private void LogNonPreferred(CommandFlags flags, bool isSlave) - { - if ((flags & Message.InternalCallFlag) == 0) // don't log internal-call - { - if (isSlave) - { - if (Message.GetMasterSlaveFlags(flags) == CommandFlags.PreferMaster) - Interlocked.Increment(ref nonPreferredEndpointCount); - } - else - { - if (Message.GetMasterSlaveFlags(flags) == CommandFlags.PreferSlave) - Interlocked.Increment(ref nonPreferredEndpointCount); - } - } - } - private void OnInternalError(Exception exception, [CallerMemberName] string origin = null) - { - Multiplexer.OnInternalError(exception, ServerEndPoint.EndPoint, ConnectionType, origin); - } - private void SelectDatabase(PhysicalConnection connection, Message message) - { - int db = message.Db; - if (db >= 0) - { - var sel = connection.GetSelectDatabaseCommand(db, message); - if (sel != null) - { - connection.Enqueue(sel); - sel.WriteImpl(connection); - sel.SetRequestSent(); - IncrementOpCount(); - } - } - } - private bool WriteMessageToServer(PhysicalConnection connection, Message message) - { - if (message == null) return true; - - try - { - var cmd = message.Command; - bool isMasterOnly = message.IsMasterOnly(); - if (isMasterOnly && ServerEndPoint.IsSlave && (ServerEndPoint.SlaveReadOnly || !ServerEndPoint.AllowSlaveWrites)) - { - throw ExceptionFactory.MasterOnly(Multiplexer.IncludeDetailInExceptions, message.Command, message, ServerEndPoint); - } - - SelectDatabase(connection, message); - - if (!connection.TransactionActive) - { - var readmode = connection.GetReadModeCommand(isMasterOnly); - if (readmode != null) - { - connection.Enqueue(readmode); - readmode.WriteTo(connection); - readmode.SetRequestSent(); - IncrementOpCount(); - } - - if (message.IsAsking) - { - var asking = ReusableAskingCommand; - connection.Enqueue(asking); - asking.WriteImpl(connection); - asking.SetRequestSent(); - IncrementOpCount(); - } - } - switch (cmd) - { - case RedisCommand.WATCH: - case RedisCommand.MULTI: - connection.TransactionActive = true; - break; - case RedisCommand.UNWATCH: - case RedisCommand.EXEC: - case RedisCommand.DISCARD: - connection.TransactionActive = false; - break; - } - - connection.Enqueue(message); - message.WriteImpl(connection); - message.SetRequestSent(); - IncrementOpCount(); - - // some commands smash our ability to trust the database; some commands - // demand an immediate flush - switch (cmd) - { - case RedisCommand.EVAL: - case RedisCommand.EVALSHA: - if(!ServerEndPoint.GetFeatures().ScriptingDatabaseSafe) - { - connection.SetUnknownDatabase(); - } - break; - case RedisCommand.UNKNOWN: - case RedisCommand.DISCARD: - case RedisCommand.EXEC: - connection.SetUnknownDatabase(); - break; - } - return true; - } - catch (RedisCommandException ex) - { - Trace("Write failed: " + ex.Message); - message.Fail(ConnectionFailureType.ProtocolFailure, ex); - CompleteSyncOrAsync(message); - // this failed without actually writing; we're OK with that... unless there's a transaction - - if (connection != null && connection.TransactionActive) - { - // we left it in a broken state; need to kill the connection - connection.RecordConnectionFailed(ConnectionFailureType.ProtocolFailure, ex); - return false; - } - return true; - } - catch (Exception ex) - { - Trace("Write failed: " + ex.Message); - message.Fail(ConnectionFailureType.InternalFailure, ex); - CompleteSyncOrAsync(message); - - // we're not sure *what* happened here; probably an IOException; kill the connection - connection?.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); - return false; - } - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/PhysicalConnection.cs b/StackExchange.Redis/StackExchange/Redis/PhysicalConnection.cs deleted file mode 100644 index 6808b4f8f..000000000 --- a/StackExchange.Redis/StackExchange/Redis/PhysicalConnection.cs +++ /dev/null @@ -1,1170 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Security; -using System.Net.Sockets; -using System.Runtime.CompilerServices; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading; -#if CORE_CLR -using System.Threading.Tasks; -#endif - -namespace StackExchange.Redis -{ - - internal sealed partial class PhysicalConnection : IDisposable, ISocketCallback - { - - internal readonly byte[] ChannelPrefix; - - private const int DefaultRedisDatabaseCount = 16; - - private static readonly byte[] Crlf = Encoding.ASCII.GetBytes("\r\n"); - -#if CORE_CLR - readonly Action> endRead; - private static Action> EndReadFactory(PhysicalConnection physical) - { - return result => - { // can't capture AsyncState on SocketRead, so we'll do it once per physical instead - if (result.IsFaulted) - { - GC.KeepAlive(result.Exception); - } - try - { - physical.Multiplexer.Trace("Completed asynchronously: processing in callback", physical.physicalName); - if (physical.EndReading(result)) physical.BeginReading(); - } - catch (Exception ex) - { - physical.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); - } - }; - } -#else - static readonly AsyncCallback endRead = result => - { - PhysicalConnection physical; - if (result.CompletedSynchronously || (physical = result.AsyncState as PhysicalConnection) == null) return; - try - { - physical.Multiplexer.Trace("Completed asynchronously: processing in callback", physical.physicalName); - if (physical.EndReading(result)) physical.BeginReading(); - } - catch (Exception ex) - { - physical.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); - } - }; -#endif - - private static readonly byte[] message = Encoding.UTF8.GetBytes("message"), pmessage = Encoding.UTF8.GetBytes("pmessage"); - - static readonly Message[] ReusableChangeDatabaseCommands = Enumerable.Range(0, DefaultRedisDatabaseCount).Select( - i => Message.Create(i, CommandFlags.FireAndForget, RedisCommand.SELECT)).ToArray(); - - private static readonly Message - ReusableReadOnlyCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.READONLY), - ReusableReadWriteCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.READWRITE); - - private static int totalCount; - - private readonly ConnectionType connectionType; - - // things sent to this physical, but not yet received - private readonly Queue outstanding = new Queue(); - - readonly string physicalName; - - volatile int currentDatabase = 0; - - ReadMode currentReadMode = ReadMode.NotSpecified; - - int failureReported; - - byte[] ioBuffer = new byte[512]; - - int ioBufferBytes = 0; - - int lastWriteTickCount, lastReadTickCount, lastBeatTickCount; - int firstUnansweredWriteTickCount; - - private Stream netStream, outStream; - - private SocketToken socketToken; - - public PhysicalConnection(PhysicalBridge bridge) - { - lastWriteTickCount = lastReadTickCount = Environment.TickCount; - lastBeatTickCount = 0; - this.connectionType = bridge.ConnectionType; - this.Multiplexer = bridge.Multiplexer; - this.ChannelPrefix = Multiplexer.RawConfig.ChannelPrefix; - if (this.ChannelPrefix != null && this.ChannelPrefix.Length == 0) this.ChannelPrefix = null; // null tests are easier than null+empty - var endpoint = bridge.ServerEndPoint.EndPoint; - physicalName = connectionType + "#" + Interlocked.Increment(ref totalCount) + "@" + Format.ToString(endpoint); - this.Bridge = bridge; -#if CORE_CLR - endRead = EndReadFactory(this); -#endif - OnCreateEcho(); - } - - public void BeginConnect(TextWriter log) - { - VolatileWrapper.Write(ref firstUnansweredWriteTickCount, 0); - var endpoint = this.Bridge.ServerEndPoint.EndPoint; - - Multiplexer.Trace("Connecting...", physicalName); - this.socketToken = Multiplexer.SocketManager.BeginConnect(endpoint, this, Multiplexer, log); - } - - private enum ReadMode : byte - { - NotSpecified, - ReadOnly, - ReadWrite - } - - public PhysicalBridge Bridge { get; } - - public long LastWriteSecondsAgo => unchecked(Environment.TickCount - VolatileWrapper.Read(ref lastWriteTickCount)) / 1000; - - public ConnectionMultiplexer Multiplexer { get; } - - public long SubscriptionCount { get; set; } - - public bool TransactionActive { get; internal set; } - - public void Dispose() - { - if (outStream != null) - { - Multiplexer.Trace("Disconnecting...", physicalName); -#if !CORE_CLR - try { outStream.Close(); } catch { } -#endif - try { outStream.Dispose(); } catch { } - outStream = null; - } - if (netStream != null) - { -#if !CORE_CLR - try { netStream.Close(); } catch { } -#endif - try { netStream.Dispose(); } catch { } - netStream = null; - } - if (socketToken.HasValue) - { - Multiplexer.SocketManager?.Shutdown(socketToken); - socketToken = default(SocketToken); - Multiplexer.Trace("Disconnected", physicalName); - RecordConnectionFailed(ConnectionFailureType.ConnectionDisposed); - } - OnCloseEcho(); - } - - public void Flush() - { - var tmp = outStream; - if (tmp != null) - { - tmp.Flush(); - Interlocked.Exchange(ref lastWriteTickCount, Environment.TickCount); - } - } - public void RecordConnectionFailed(ConnectionFailureType failureType, Exception innerException = null, [CallerMemberName] string origin = null) - { - SocketManager.ManagerState mgrState = SocketManager.ManagerState.CheckForStaleConnections; - RecordConnectionFailed(failureType, ref mgrState, innerException, origin); - } - public void RecordConnectionFailed(ConnectionFailureType failureType, ref SocketManager.ManagerState managerState, Exception innerException = null, [CallerMemberName] string origin = null) - { - IdentifyFailureType(innerException, ref failureType); - - managerState = SocketManager.ManagerState.RecordConnectionFailed_OnInternalError; - if (failureType == ConnectionFailureType.InternalFailure) OnInternalError(innerException, origin); - - // stop anything new coming in... - Bridge.Trace("Failed: " + failureType); - bool isCurrent; - PhysicalBridge.State oldState; - int @in = -1, ar = -1; - managerState = SocketManager.ManagerState.RecordConnectionFailed_OnDisconnected; - Bridge.OnDisconnected(failureType, this, out isCurrent, out oldState); - if(oldState == PhysicalBridge.State.ConnectedEstablished) - { - try - { - @in = GetAvailableInboundBytes(out ar); - } - catch { /* best effort only */ } - } - - if (isCurrent && Interlocked.CompareExchange(ref failureReported, 1, 0) == 0) - { - managerState = SocketManager.ManagerState.RecordConnectionFailed_ReportFailure; - int now = Environment.TickCount, lastRead = VolatileWrapper.Read(ref lastReadTickCount), lastWrite = VolatileWrapper.Read(ref lastWriteTickCount), - lastBeat = VolatileWrapper.Read(ref lastBeatTickCount); - int unansweredRead = VolatileWrapper.Read(ref firstUnansweredWriteTickCount); - - var exMessage = new StringBuilder(failureType + " on " + Format.ToString(Bridge.ServerEndPoint.EndPoint) + "/" + connectionType); - var data = new List> - { - Tuple.Create("FailureType", failureType.ToString()), - Tuple.Create("EndPoint", Format.ToString(Bridge.ServerEndPoint.EndPoint)) - }; - Action add = (lk, sk, v) => - { - data.Add(Tuple.Create(lk, v)); - exMessage.Append(", " + sk + ": " + v); - }; - - add("Origin", "origin", origin); - add("Input-Buffer", "input-buffer", ioBufferBytes.ToString()); - add("Outstanding-Responses", "outstanding", GetSentAwaitingResponseCount().ToString()); - add("Last-Read", "last-read", unchecked(now - lastRead) / 1000 + "s ago"); - add("Last-Write", "last-write", unchecked(now - lastWrite) / 1000 + "s ago"); - add("Unanswered-Write", "unanswered-write", unchecked(now - unansweredRead) / 1000 + "s ago"); - add("Keep-Alive", "keep-alive", Bridge.ServerEndPoint.WriteEverySeconds + "s"); - add("Pending", "pending", Bridge.GetPendingCount().ToString()); - add("Previous-Physical-State", "state", oldState.ToString()); - - if(@in >= 0) - { - add("Inbound-Bytes", "in", @in.ToString()); - add("Active-Readers", "ar", ar.ToString()); - } - - add("Last-Heartbeat", "last-heartbeat", (lastBeat == 0 ? "never" : (unchecked(now - lastBeat)/1000 + "s ago"))+ (Bridge.IsBeating ? " (mid-beat)" : "") ); - add("Last-Multiplexer-Heartbeat", "last-mbeat", Multiplexer.LastHeartbeatSecondsAgo + "s ago"); - add("Last-Global-Heartbeat", "global", ConnectionMultiplexer.LastGlobalHeartbeatSecondsAgo + "s ago"); -#if FEATURE_SOCKET_MODE_POLL - var mgr = Bridge.Multiplexer.SocketManager; - add("SocketManager-State", "mgr", mgr.State.ToString()); - add("Last-Error", "err", mgr.LastErrorTimeRelative()); -#endif - - var ex = innerException == null - ? new RedisConnectionException(failureType, exMessage.ToString()) - : new RedisConnectionException(failureType, exMessage.ToString(), innerException); - - foreach (var kv in data) - { - ex.Data["Redis-" + kv.Item1] = kv.Item2; - } - - managerState = SocketManager.ManagerState.RecordConnectionFailed_OnConnectionFailed; - Bridge.OnConnectionFailed(this, failureType, ex); - } - - // cleanup - managerState = SocketManager.ManagerState.RecordConnectionFailed_FailOutstanding; - lock (outstanding) - { - Bridge.Trace(outstanding.Count != 0, "Failing outstanding messages: " + outstanding.Count); - while (outstanding.Count != 0) - { - var next = outstanding.Dequeue(); - Bridge.Trace("Failing: " + next); - next.Fail(failureType, innerException); - Bridge.CompleteSyncOrAsync(next); - } - } - - // burn the socket - managerState = SocketManager.ManagerState.RecordConnectionFailed_ShutdownSocket; - Multiplexer.SocketManager?.Shutdown(socketToken); - } - - public override string ToString() - { - return physicalName; - } - - internal static void IdentifyFailureType(Exception exception, ref ConnectionFailureType failureType) - { - if (exception != null && failureType == ConnectionFailureType.InternalFailure) - { - if (exception is AggregateException) exception = exception.InnerException ?? exception; - if (exception is AuthenticationException) failureType = ConnectionFailureType.AuthenticationFailure; - else if (exception is SocketException || exception is IOException) failureType = ConnectionFailureType.SocketFailure; - else if (exception is EndOfStreamException) failureType = ConnectionFailureType.SocketClosed; - else if (exception is ObjectDisposedException) failureType = ConnectionFailureType.SocketClosed; - } - } - - internal void Enqueue(Message next) - { - lock (outstanding) - { - outstanding.Enqueue(next); - } - } - - internal void GetCounters(ConnectionCounters counters) - { - lock (outstanding) - { - counters.SentItemsAwaitingResponse = outstanding.Count; - } - counters.Subscriptions = SubscriptionCount; - } - - internal Message GetReadModeCommand(bool isMasterOnly) - { - var serverEndpoint = Bridge.ServerEndPoint; - if (serverEndpoint.RequiresReadMode) - { - ReadMode requiredReadMode = isMasterOnly ? ReadMode.ReadWrite : ReadMode.ReadOnly; - if (requiredReadMode != currentReadMode) - { - currentReadMode = requiredReadMode; - switch (requiredReadMode) - { - case ReadMode.ReadOnly: return ReusableReadOnlyCommand; - case ReadMode.ReadWrite: return ReusableReadWriteCommand; - } - } - } - else if (currentReadMode == ReadMode.ReadOnly) - { // we don't need it (because we're not a cluster, or not a slave), - // but we are in read-only mode; switch to read-write - currentReadMode = ReadMode.ReadWrite; - return ReusableReadWriteCommand; - } - return null; - } - - internal Message GetSelectDatabaseCommand(int targetDatabase, Message message) - { - if (targetDatabase < 0) return null; - if (targetDatabase != currentDatabase) - { - var serverEndpoint = Bridge.ServerEndPoint; - int available = serverEndpoint.Databases; - - if (!serverEndpoint.HasDatabases) // only db0 is available on cluster/twemproxy - { - if (targetDatabase != 0) - { // should never see this, since the API doesn't allow it; thus not too worried about ExceptionFactory - throw new RedisCommandException("Multiple databases are not supported on this server; cannot switch to database: " + targetDatabase); - } - return null; - } - - if(message.Command == RedisCommand.SELECT) - { - // this could come from an EVAL/EVALSHA inside a transaction, for example; we'll accept it - Bridge.Trace("Switching database: " + targetDatabase); - currentDatabase = targetDatabase; - return null; - } - - if (TransactionActive) - {// should never see this, since the API doesn't allow it; thus not too worried about ExceptionFactory - throw new RedisCommandException("Multiple databases inside a transaction are not currently supported: " + targetDatabase); - } - - if (available != 0 && targetDatabase >= available) // we positively know it is out of range - { - throw ExceptionFactory.DatabaseOutfRange(Multiplexer.IncludeDetailInExceptions, targetDatabase, message, serverEndpoint); - } - Bridge.Trace("Switching database: " + targetDatabase); - currentDatabase = targetDatabase; - return GetSelectDatabaseCommand(targetDatabase); - } - return null; - } - internal static Message GetSelectDatabaseCommand(int targetDatabase) - { - return targetDatabase < DefaultRedisDatabaseCount - ? ReusableChangeDatabaseCommands[targetDatabase] // 0-15 by default - : Message.Create(targetDatabase, CommandFlags.FireAndForget, RedisCommand.SELECT); - } - - internal int GetSentAwaitingResponseCount() - { - lock (outstanding) - { - return outstanding.Count; - } - } - - internal void GetStormLog(StringBuilder sb) - { - lock (outstanding) - { - if (outstanding.Count == 0) return; - sb.Append("Sent, awaiting response from server: ").Append(outstanding.Count).AppendLine(); - int total = 0; - foreach (var item in outstanding) - { - if (++total >= 500) break; - item.AppendStormLog(sb); - sb.AppendLine(); - } - } - } - - internal void OnHeartbeat() - { - Interlocked.Exchange(ref lastBeatTickCount, Environment.TickCount); - } - - internal void OnInternalError(Exception exception, [CallerMemberName] string origin = null) - { - Multiplexer.OnInternalError(exception, Bridge.ServerEndPoint.EndPoint, connectionType, origin); - } - - internal void SetUnknownDatabase() - { // forces next db-specific command to issue a select - currentDatabase = -1; - } - - internal void Write(RedisKey key) - { - var val = key.KeyValue; - if (val is string) - { - WriteUnified(outStream, key.KeyPrefix, (string)val); - } - else - { - WriteUnified(outStream, key.KeyPrefix, (byte[])val); - } - } - - internal void Write(RedisChannel channel) - { - WriteUnified(outStream, ChannelPrefix, channel.Value); - } - - internal void Write(RedisValue value) - { - if (value.IsInteger) - { - WriteUnified(outStream, (long)value); - } - else - { - WriteUnified(outStream, (byte[])value); - } - } - - internal void WriteHeader(RedisCommand command, int arguments) - { - var commandBytes = Multiplexer.CommandMap.GetBytes(command); - if (commandBytes == null) - { - throw ExceptionFactory.CommandDisabled(Multiplexer.IncludeDetailInExceptions, command, null, Bridge.ServerEndPoint); - } - outStream.WriteByte((byte)'*'); - - // remember the time of the first write that still not followed by read - Interlocked.CompareExchange(ref firstUnansweredWriteTickCount, Environment.TickCount, 0); - - WriteRaw(outStream, arguments + 1); - WriteUnified(outStream, commandBytes); - } - internal const int REDIS_MAX_ARGS = 1024 * 1024; // there is a <= 1024*1024 max constraint inside redis itself: https://github.com/antirez/redis/blob/6c60526db91e23fb2d666fc52facc9a11780a2a3/src/networking.c#L1024 - - internal void WriteHeader(string command, int arguments) - { - if(arguments >= REDIS_MAX_ARGS) // using >= here because we will be adding 1 for the command itself (which is an arg for the purposes of the multi-bulk protocol) - { - throw ExceptionFactory.TooManyArgs(Multiplexer.IncludeDetailInExceptions, command, null, Bridge.ServerEndPoint, arguments + 1); - } - var commandBytes = Multiplexer.CommandMap.GetBytes(command); - if (commandBytes == null) - { - throw ExceptionFactory.CommandDisabled(Multiplexer.IncludeDetailInExceptions, command, null, Bridge.ServerEndPoint); - } - outStream.WriteByte((byte)'*'); - - // remember the time of the first write that still not followed by read - Interlocked.CompareExchange(ref firstUnansweredWriteTickCount, Environment.TickCount, 0); - - WriteRaw(outStream, arguments + 1); - WriteUnified(outStream, commandBytes); - } - - static void WriteRaw(Stream stream, long value, bool withLengthPrefix = false) - { - if (value >= 0 && value <= 9) - { - if (withLengthPrefix) - { - stream.WriteByte((byte)'1'); - stream.Write(Crlf, 0, 2); - } - stream.WriteByte((byte)((int)'0' + (int)value)); - } - else if (value >= 10 && value < 100) - { - if (withLengthPrefix) - { - stream.WriteByte((byte)'2'); - stream.Write(Crlf, 0, 2); - } - stream.WriteByte((byte)((int)'0' + (int)value / 10)); - stream.WriteByte((byte)((int)'0' + (int)value % 10)); - } - else if (value >= 100 && value < 1000) - { - int v = (int)value; - int units = v % 10; - v /= 10; - int tens = v % 10, hundreds = v / 10; - if (withLengthPrefix) - { - stream.WriteByte((byte)'3'); - stream.Write(Crlf, 0, 2); - } - stream.WriteByte((byte)((int)'0' + hundreds)); - stream.WriteByte((byte)((int)'0' + tens)); - stream.WriteByte((byte)((int)'0' + units)); - } - else if (value < 0 && value >= -9) - { - if (withLengthPrefix) - { - stream.WriteByte((byte)'2'); - stream.Write(Crlf, 0, 2); - } - stream.WriteByte((byte)'-'); - stream.WriteByte((byte)((int)'0' - (int)value)); - } - else if (value <= -10 && value > -100) - { - if (withLengthPrefix) - { - stream.WriteByte((byte)'3'); - stream.Write(Crlf, 0, 2); - } - value = -value; - stream.WriteByte((byte)'-'); - stream.WriteByte((byte)((int)'0' + (int)value / 10)); - stream.WriteByte((byte)((int)'0' + (int)value % 10)); - } - else - { - var bytes = Encoding.ASCII.GetBytes(Format.ToString(value)); - if (withLengthPrefix) - { - WriteRaw(stream, bytes.Length, false); - } - stream.Write(bytes, 0, bytes.Length); - } - stream.Write(Crlf, 0, 2); - } - - static void WriteUnified(Stream stream, byte[] value) - { - stream.WriteByte((byte)'$'); - if (value == null) - { - WriteRaw(stream, -1); // note that not many things like this... - } - else - { - WriteRaw(stream, value.Length); - stream.Write(value, 0, value.Length); - stream.Write(Crlf, 0, 2); - } - } - - internal void WriteAsHex(byte[] value) - { - var stream = outStream; - stream.WriteByte((byte)'$'); - if (value == null) - { - WriteRaw(stream, -1); - } else - { - WriteRaw(stream, value.Length * 2); - for(int i = 0; i < value.Length; i++) - { - stream.WriteByte(ToHexNibble(value[i] >> 4)); - stream.WriteByte(ToHexNibble(value[i] & 15)); - } - stream.Write(Crlf, 0, 2); - } - } - internal static byte ToHexNibble(int value) - { - return value < 10 ? (byte)('0' + value) : (byte)('a' - 10 + value); - } - - void WriteUnified(Stream stream, byte[] prefix, string value) - { - stream.WriteByte((byte)'$'); - if (value == null) - { - WriteRaw(stream, -1); // note that not many things like this... - } - else - { - int encodedLength = Encoding.UTF8.GetByteCount(value); - if (prefix == null) - { - WriteRaw(stream, encodedLength); - WriteRaw(stream, value, encodedLength); - stream.Write(Crlf, 0, 2); - } - else - { - WriteRaw(stream, prefix.Length + encodedLength); - stream.Write(prefix, 0, prefix.Length); - WriteRaw(stream, value, encodedLength); - stream.Write(Crlf, 0, 2); - } - } - - } - unsafe void WriteRaw(Stream stream, string value, int encodedLength) - { - if (encodedLength <= ScratchSize) - { - int bytes = Encoding.UTF8.GetBytes(value, 0, value.Length, outScratch, 0); - stream.Write(outScratch, 0, bytes); - } - else - { -#if !CORE_CLR - fixed (char* c = value) - fixed (byte* b = outScratch) - { - int charsRemaining = value.Length, charOffset = 0, bytesWritten; - while (charsRemaining > Scratch_CharsPerBlock) - { - bytesWritten = outEncoder.GetBytes(c + charOffset, Scratch_CharsPerBlock, b, ScratchSize, false); - stream.Write(outScratch, 0, bytesWritten); - charOffset += Scratch_CharsPerBlock; - charsRemaining -= Scratch_CharsPerBlock; - } - bytesWritten = outEncoder.GetBytes(c + charOffset, charsRemaining, b, ScratchSize, true); - if (bytesWritten != 0) stream.Write(outScratch, 0, bytesWritten); - } -#else - int charsRemaining = value.Length, charOffset = 0, bytesWritten; - var valueCharArray = value.ToCharArray(); - while (charsRemaining > Scratch_CharsPerBlock) - { - bytesWritten = outEncoder.GetBytes(valueCharArray, charOffset, Scratch_CharsPerBlock, outScratch, 0, false); - stream.Write(outScratch, 0, bytesWritten); - charOffset += Scratch_CharsPerBlock; - charsRemaining -= Scratch_CharsPerBlock; - } - bytesWritten = outEncoder.GetBytes(valueCharArray, charOffset, charsRemaining, outScratch, 0, true); - if (bytesWritten != 0) stream.Write(outScratch, 0, bytesWritten); -#endif - } - } - const int ScratchSize = 512; - static readonly int Scratch_CharsPerBlock = ScratchSize / Encoding.UTF8.GetMaxByteCount(1); - private readonly byte[] outScratch = new byte[ScratchSize]; - private readonly Encoder outEncoder = Encoding.UTF8.GetEncoder(); - static void WriteUnified(Stream stream, byte[] prefix, byte[] value) - { - stream.WriteByte((byte)'$'); - if (value == null) - { - WriteRaw(stream, -1); // note that not many things like this... - } - else if (prefix == null) - { - WriteRaw(stream, value.Length); - stream.Write(value, 0, value.Length); - stream.Write(Crlf, 0, 2); - } - else - { - WriteRaw(stream, prefix.Length + value.Length); - stream.Write(prefix, 0, prefix.Length); - stream.Write(value, 0, value.Length); - stream.Write(Crlf, 0, 2); - } - } - - static void WriteUnified(Stream stream, long value) - { - // note from specification: A client sends to the Redis server a RESP Array consisting of just Bulk Strings. - // (i.e. we can't just send ":123\r\n", we need to send "$3\r\n123\r\n" - stream.WriteByte((byte)'$'); - WriteRaw(stream, value, withLengthPrefix: true); - } - - void BeginReading() - { - bool keepReading; - try - { - do - { - keepReading = false; - int space = EnsureSpaceAndComputeBytesToRead(); - Multiplexer.Trace("Beginning async read...", physicalName); -#if CORE_CLR - var result = netStream.ReadAsync(ioBuffer, ioBufferBytes, space); - switch (result.Status) - { - case TaskStatus.RanToCompletion: - case TaskStatus.Faulted: - Multiplexer.Trace("Completed synchronously: processing immediately", physicalName); - keepReading = EndReading(result); - break; - default: - result.ContinueWith(endRead); - break; - } -#else - var result = netStream.BeginRead(ioBuffer, ioBufferBytes, space, endRead, this); - if (result.CompletedSynchronously) - { - Multiplexer.Trace("Completed synchronously: processing immediately", physicalName); - keepReading = EndReading(result); - } -#endif - } while (keepReading); - } -#if CORE_CLR - catch (AggregateException ex) - { - throw ex.InnerException; - } -#endif - catch (System.IO.IOException ex) - { - Multiplexer.Trace("Could not connect: " + ex.Message, physicalName); - } - } - int haveReader; - - internal int GetAvailableInboundBytes(out int activeReaders) - { - activeReaders = Interlocked.CompareExchange(ref haveReader, 0, 0); - return this.socketToken.Available; - } - - static LocalCertificateSelectionCallback GetAmbientCertificateCallback() - { - try - { - var pfxPath = Environment.GetEnvironmentVariable("SERedis_ClientCertPfxPath"); - var pfxPassword = Environment.GetEnvironmentVariable("SERedis_ClientCertPassword"); - var pfxStorageFlags = Environment.GetEnvironmentVariable("SERedis_ClientCertStorageFlags"); - - X509KeyStorageFlags? flags = null; - if (!string.IsNullOrEmpty(pfxStorageFlags)) - { - flags = Enum.Parse(typeof(X509KeyStorageFlags), pfxStorageFlags) as X509KeyStorageFlags?; - } - - if (!string.IsNullOrEmpty(pfxPath) && File.Exists(pfxPath)) - { - return delegate { return new X509Certificate2(pfxPath, pfxPassword ?? "", flags ?? X509KeyStorageFlags.DefaultKeySet); }; - } - } catch - { } - return null; - } - SocketMode ISocketCallback.Connected(Stream stream, TextWriter log) - { - try - { - var socketMode = SocketManager.DefaultSocketMode; - - // disallow connection in some cases - OnDebugAbort(); - - // the order is important here: - // [network]<==[ssl]<==[logging]<==[buffered] - var config = Multiplexer.RawConfig; - - if(config.Ssl) - { - Multiplexer.LogLocked(log, "Configuring SSL"); - var host = config.SslHost; - if (string.IsNullOrWhiteSpace(host)) host = Format.ToStringHostOnly(Bridge.ServerEndPoint.EndPoint); - - var ssl = new SslStream(stream, false, config.CertificateValidationCallback, - config.CertificateSelectionCallback ?? GetAmbientCertificateCallback() -#if !__MonoCS__ - , EncryptionPolicy.RequireEncryption -#endif - ); - try - { - ssl.AuthenticateAsClient(host, config.SslProtocols); - - Multiplexer.LogLocked(log, $"SSL connection established successfully using protocol: {ssl.SslProtocol}"); - } - catch (AuthenticationException authexception) - { - RecordConnectionFailed(ConnectionFailureType.AuthenticationFailure, authexception); - Multiplexer.Trace("Encryption failure"); - return SocketMode.Abort; - } - stream = ssl; - socketMode = SocketMode.Async; - } - OnWrapForLogging(ref stream, physicalName); - - int bufferSize = config.WriteBuffer; - this.netStream = stream; - this.outStream = bufferSize <= 0 ? stream : new BufferedStream(stream, bufferSize); - Multiplexer.LogLocked(log, "Connected {0}", Bridge); - - Bridge.OnConnected(this, log); - return socketMode; - } - catch (Exception ex) - { - RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); // includes a bridge.OnDisconnected - Multiplexer.Trace("Could not connect: " + ex.Message, physicalName); - return SocketMode.Abort; - } - } - -#if CORE_CLR - private bool EndReading(Task result) - { - try - { - var tmp = netStream; - int bytesRead = tmp == null ? 0 : result.Result; // note we expect this to be completed - return ProcessReadBytes(bytesRead); - } - catch (Exception ex) - { - RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); - return false; - } - } -#else - private bool EndReading(IAsyncResult result) - { - try - { - int bytesRead = netStream?.EndRead(result) ?? 0; - return ProcessReadBytes(bytesRead); - } - catch (Exception ex) - { - RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); - return false; - } - } -#endif - int EnsureSpaceAndComputeBytesToRead() - { - int space = ioBuffer.Length - ioBufferBytes; - if (space == 0) - { - Array.Resize(ref ioBuffer, ioBuffer.Length * 2); - space = ioBuffer.Length - ioBufferBytes; - } - return space; - } - - void ISocketCallback.Error() - { - RecordConnectionFailed(ConnectionFailureType.SocketFailure); - } - void MatchResult(RawResult result) - { - // check to see if it could be an out-of-band pubsub message - if (connectionType == ConnectionType.Subscription && result.Type == ResultType.MultiBulk) - { // out of band message does not match to a queued message - var items = result.GetItems(); - if (items.Length >= 3 && items[0].IsEqual(message)) - { - // special-case the configuration change broadcasts (we don't keep that in the usual pub/sub registry) - var configChanged = Multiplexer.ConfigurationChangedChannel; - if (configChanged != null && items[1].IsEqual(configChanged)) - { - EndPoint blame = null; - try - { - if (!items[2].IsEqual(RedisLiterals.ByteWildcard)) - { - blame = Format.TryParseEndPoint(items[2].GetString()); - } - } - catch { /* no biggie */ } - Multiplexer.Trace("Configuration changed: " + Format.ToString(blame), physicalName); - Multiplexer.ReconfigureIfNeeded(blame, true, "broadcast"); - } - - // invoke the handlers - var channel = items[1].AsRedisChannel(ChannelPrefix, RedisChannel.PatternMode.Literal); - Multiplexer.Trace("MESSAGE: " + channel, physicalName); - if (!channel.IsNull) - { - Multiplexer.OnMessage(channel, channel, items[2].AsRedisValue()); - } - return; // AND STOP PROCESSING! - } - else if (items.Length >= 4 && items[0].IsEqual(pmessage)) - { - var channel = items[2].AsRedisChannel(ChannelPrefix, RedisChannel.PatternMode.Literal); - Multiplexer.Trace("PMESSAGE: " + channel, physicalName); - if (!channel.IsNull) - { - var sub = items[1].AsRedisChannel(ChannelPrefix, RedisChannel.PatternMode.Pattern); - Multiplexer.OnMessage(sub, channel, items[3].AsRedisValue()); - } - return; // AND STOP PROCESSING! - } - - // if it didn't look like "[p]message", then we still need to process the pending queue - } - Multiplexer.Trace("Matching result...", physicalName); - Message msg; - lock (outstanding) - { - Multiplexer.Trace(outstanding.Count == 0, "Nothing to respond to!", physicalName); - msg = outstanding.Dequeue(); - } - - Multiplexer.Trace("Response to: " + msg.ToString(), physicalName); - if (msg.ComputeResult(this, result)) - { - Bridge.CompleteSyncOrAsync(msg); - } - } - partial void OnCloseEcho(); - - partial void OnCreateEcho(); - partial void OnDebugAbort(); - void ISocketCallback.OnHeartbeat() - { - try - { - Bridge.OnHeartbeat(true); // all the fun code is here - } - catch (Exception ex) - { - OnInternalError(ex); - } - } - - partial void OnWrapForLogging(ref Stream stream, string name); - private int ProcessBuffer(byte[] underlying, ref int offset, ref int count) - { - int messageCount = 0; - RawResult result; - do - { - int tmpOffset = offset, tmpCount = count; - // we want TryParseResult to be able to mess with these without consequence - result = TryParseResult(underlying, ref tmpOffset, ref tmpCount); - if (result.HasValue) - { - messageCount++; - // entire message: update the external counters - offset = tmpOffset; - count = tmpCount; - - Multiplexer.Trace(result.ToString(), physicalName); - MatchResult(result); - } - } while (result.HasValue); - return messageCount; - } - private bool ProcessReadBytes(int bytesRead) - { - if (bytesRead <= 0) - { - Multiplexer.Trace("EOF", physicalName); - RecordConnectionFailed(ConnectionFailureType.SocketClosed); - return false; - } - - Interlocked.Exchange(ref lastReadTickCount, Environment.TickCount); - - // reset unanswered write timestamp - VolatileWrapper.Write(ref firstUnansweredWriteTickCount, 0); - - ioBufferBytes += bytesRead; - Multiplexer.Trace("More bytes available: " + bytesRead + " (" + ioBufferBytes + ")", physicalName); - int offset = 0, count = ioBufferBytes; - int handled = ProcessBuffer(ioBuffer, ref offset, ref count); - Multiplexer.Trace("Processed: " + handled, physicalName); - if (handled != 0) - { - // read stuff - if (count != 0) - { - Multiplexer.Trace("Copying remaining bytes: " + count, physicalName); - // if anything was left over, we need to copy it to - // the start of the buffer so it can be used next time - Buffer.BlockCopy(ioBuffer, offset, ioBuffer, 0, count); - } - ioBufferBytes = count; - } - return true; - } - - void ISocketCallback.Read() - { - Interlocked.Increment(ref haveReader); - try - { - do - { - int space = EnsureSpaceAndComputeBytesToRead(); - int bytesRead = netStream?.Read(ioBuffer, ioBufferBytes, space) ?? 0; - - if (!ProcessReadBytes(bytesRead)) return; // EOF - } while (socketToken.Available != 0); - Multiplexer.Trace("Buffer exhausted", physicalName); - // ^^^ note that the socket manager will call us again when there is something to do - } - catch (Exception ex) - { - RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); - }finally - { - Interlocked.Decrement(ref haveReader); - } - } - - bool ISocketCallback.IsDataAvailable - { - get - { - try { return socketToken.Available > 0; } - catch { return false; } - } - } - private RawResult ReadArray(byte[] buffer, ref int offset, ref int count) - { - var itemCount = ReadLineTerminatedString(ResultType.Integer, buffer, ref offset, ref count); - if (itemCount.HasValue) - { - long i64; - if (!itemCount.TryGetInt64(out i64)) throw ExceptionFactory.ConnectionFailure(Multiplexer.IncludeDetailInExceptions, ConnectionFailureType.ProtocolFailure, "Invalid array length", Bridge.ServerEndPoint); - int itemCountActual = checked((int)i64); - - if (itemCountActual < 0) - { - //for null response by command like EXEC, RESP array: *-1\r\n - return new RawResult(ResultType.SimpleString, null, 0, 0); - } - else if (itemCountActual == 0) - { - //for zero array response by command like SCAN, Resp array: *0\r\n - return RawResult.EmptyArray; - } - - var arr = new RawResult[itemCountActual]; - for (int i = 0; i < itemCountActual; i++) - { - if (!(arr[i] = TryParseResult(buffer, ref offset, ref count)).HasValue) - return RawResult.Nil; - } - return new RawResult(arr); - } - return RawResult.Nil; - } - - private RawResult ReadBulkString(byte[] buffer, ref int offset, ref int count) - { - var prefix = ReadLineTerminatedString(ResultType.Integer, buffer, ref offset, ref count); - if (prefix.HasValue) - { - long i64; - if (!prefix.TryGetInt64(out i64)) throw ExceptionFactory.ConnectionFailure(Multiplexer.IncludeDetailInExceptions, ConnectionFailureType.ProtocolFailure, "Invalid bulk string length", Bridge.ServerEndPoint); - int bodySize = checked((int)i64); - if (bodySize < 0) - { - return new RawResult(ResultType.BulkString, null, 0, 0); - } - else if (count >= bodySize + 2) - { - if (buffer[offset + bodySize] != '\r' || buffer[offset + bodySize + 1] != '\n') - { - throw ExceptionFactory.ConnectionFailure(Multiplexer.IncludeDetailInExceptions, ConnectionFailureType.ProtocolFailure, "Invalid bulk string terminator", Bridge.ServerEndPoint); - } - var result = new RawResult(ResultType.BulkString, buffer, offset, bodySize); - offset += bodySize + 2; - count -= bodySize + 2; - return result; - } - } - return RawResult.Nil; - } - - private RawResult ReadLineTerminatedString(ResultType type, byte[] buffer, ref int offset, ref int count) - { - int max = offset + count - 2; - for (int i = offset; i < max; i++) - { - if (buffer[i + 1] == '\r' && buffer[i + 2] == '\n') - { - int len = i - offset + 1; - var result = new RawResult(type, buffer, offset, len); - count -= (len + 2); - offset += (len + 2); - return result; - } - } - return RawResult.Nil; - } - - void ISocketCallback.StartReading() - { - BeginReading(); - } - RawResult TryParseResult(byte[] buffer, ref int offset, ref int count) - { - if(count == 0) return RawResult.Nil; - - char resultType = (char)buffer[offset++]; - count--; - switch(resultType) - { - case '+': // simple string - return ReadLineTerminatedString(ResultType.SimpleString, buffer, ref offset, ref count); - case '-': // error - return ReadLineTerminatedString(ResultType.Error, buffer, ref offset, ref count); - case ':': // integer - return ReadLineTerminatedString(ResultType.Integer, buffer, ref offset, ref count); - case '$': // bulk string - return ReadBulkString(buffer, ref offset, ref count); - case '*': // array - return ReadArray(buffer, ref offset, ref count); - default: - throw new InvalidOperationException("Unexpected response prefix: " + (char)resultType); - } - } - - partial void DebugEmulateStaleConnection(ref int firstUnansweredWrite); - - public void CheckForStaleConnection(ref SocketManager.ManagerState managerState) - { - int firstUnansweredWrite = VolatileWrapper.Read(ref firstUnansweredWriteTickCount); - - DebugEmulateStaleConnection(ref firstUnansweredWrite); - - int now = Environment.TickCount; - - if (firstUnansweredWrite != 0 && (now - firstUnansweredWrite) > this.Multiplexer.RawConfig.ResponseTimeout) - { - this.RecordConnectionFailed(ConnectionFailureType.SocketFailure, ref managerState, origin: "CheckForStaleConnection"); - } - } - } - - -} diff --git a/StackExchange.Redis/StackExchange/Redis/ProfileContextTracker.cs b/StackExchange.Redis/StackExchange/Redis/ProfileContextTracker.cs deleted file mode 100644 index fc392ba76..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ProfileContextTracker.cs +++ /dev/null @@ -1,238 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; - -namespace StackExchange.Redis -{ - /// - /// Big ol' wrapper around most of the profiling storage logic, 'cause it got too big to just live in ConnectionMultiplexer. - /// - sealed class ProfileContextTracker - { - /// - /// Necessary, because WeakReference can't be readily comparable (since the reference is... weak). - /// - /// This lets us detect leaks* with some reasonable confidence, and cleanup periodically. - /// - /// Some calisthenics are done to avoid allocating WeakReferences for no reason, as often - /// we're just looking up ProfileStorage. - /// - /// * Somebody starts profiling, but for whatever reason never *stops* with a context object - /// - struct ProfileContextCell : IEquatable - { - // This is a union of (object|WeakReference); if it's a WeakReference - // then we're actually interested in it's Target, otherwise - // we're concerned about the actual value of Reference - object Reference; - - // It is absolutely crucial that this value **never change** once instantiated - readonly int HashCode; - - public bool IsContextLeaked - { - get - { - object ignored; - return !TryGetTarget(out ignored); - } - } - - private ProfileContextCell(object forObj, bool isEphemeral) - { - HashCode = forObj.GetHashCode(); - - if (isEphemeral) - { - Reference = forObj; - } - else - { - Reference = new WeakReference(forObj, trackResurrection: true); // ughhh, have to handle finalizers - } - } - - /// - /// Suitable for use as a key into something. - /// - /// This instance **WILL NOT** keep forObj alive, so it can - /// be copied out of the calling method's scope. - /// - public static ProfileContextCell ToStoreUnder(object forObj) - { - return new ProfileContextCell(forObj, isEphemeral: false); - } - - /// - /// Only suitable for looking up. - /// - /// This instance **ABSOLUTELY WILL** keep forObj alive, so this - /// had better not be copied into anything outside the scope of the - /// calling method. - /// - public static ProfileContextCell ToLookupBy(object forObj) - { - return new ProfileContextCell(forObj, isEphemeral: true); - } - - bool TryGetTarget(out object target) - { - var asWeakRef = Reference as WeakReference; - - if (asWeakRef == null) - { - target = Reference; - return true; - } - - // Do not use IsAlive here, it's race city - target = asWeakRef.Target; - return target != null; - } - - public override bool Equals(object obj) - { - if (!(obj is ProfileContextCell)) return false; - - return Equals((ProfileContextCell)obj); - } - - public override int GetHashCode() - { - return HashCode; - } - - public bool Equals(ProfileContextCell other) - { - object thisObj, otherObj; - - if (other.TryGetTarget(out otherObj) != TryGetTarget(out thisObj)) return false; - - // dead references are equal - if (thisObj == null) return true; - - return thisObj.Equals(otherObj); - } - } - - // provided so default behavior doesn't do any boxing, for sure - sealed class ProfileContextCellComparer : IEqualityComparer - { - public static readonly ProfileContextCellComparer Singleton = new ProfileContextCellComparer(); - - private ProfileContextCellComparer() { } - - public bool Equals(ProfileContextCell x, ProfileContextCell y) - { - return x.Equals(y); - } - - public int GetHashCode(ProfileContextCell obj) - { - return obj.GetHashCode(); - } - } - - private long lastCleanupSweep; - private ConcurrentDictionary profiledCommands; - - public int ContextCount => profiledCommands.Count; - - public ProfileContextTracker() - { - profiledCommands = new ConcurrentDictionary(ProfileContextCellComparer.Singleton); - lastCleanupSweep = DateTime.UtcNow.Ticks; - } - - /// - /// Registers the passed context with a collection that can be retried with subsequent calls to TryGetValue. - /// - /// Returns false if the passed context object is already registered. - /// - public bool TryCreate(object ctx) - { - var cell = ProfileContextCell.ToStoreUnder(ctx); - - // we can't pass this as a delegate, because TryAdd may invoke the factory multiple times, - // which would lead to over allocation. - var storage = ConcurrentProfileStorageCollection.GetOrCreate(); - return profiledCommands.TryAdd(cell, storage); - } - - /// - /// Returns true and sets val to the tracking collection associated with the given context if the context - /// was registered with TryCreate. - /// - /// Otherwise returns false and sets val to null. - /// - public bool TryGetValue(object ctx, out ConcurrentProfileStorageCollection val) - { - var cell = ProfileContextCell.ToLookupBy(ctx); - return profiledCommands.TryGetValue(cell, out val); - } - - /// - /// Removes a context, setting all commands to a (non-thread safe) enumerable of - /// all the commands attached to that context. - /// - /// If the context was never registered, will return false and set commands to null. - /// - /// Subsequent calls to TryRemove with the same context will return false unless it is - /// re-registered with TryCreate. - /// - public bool TryRemove(object ctx, out ProfiledCommandEnumerable commands) - { - var cell = ProfileContextCell.ToLookupBy(ctx); - ConcurrentProfileStorageCollection storage; - if (!profiledCommands.TryRemove(cell, out storage)) - { - commands = default(ProfiledCommandEnumerable); - return false; - } - - commands = storage.EnumerateAndReturnForReuse(); - return true; - } - - /// - /// If enough time has passed (1 minute) since the last call, this does walk of all contexts - /// and removes those that the GC has collected. - /// - public bool TryCleanup() - { - const long SweepEveryTicks = 600000000; // once a minute, tops - - var now = DateTime.UtcNow.Ticks; // resolution on this isn't great, but it's good enough - var last = lastCleanupSweep; - var since = now - last; - if (since < SweepEveryTicks) return false; - - // this is just to keep other threads from wasting time, in theory - // it'd be perfectly safe for this to run concurrently - var saw = Interlocked.CompareExchange(ref lastCleanupSweep, now, last); - if (saw != last) return false; - - if (profiledCommands.Count == 0) return false; - - using(var e = profiledCommands.GetEnumerator()) - { - while(e.MoveNext()) - { - var pair = e.Current; - if(pair.Key.IsContextLeaked) - { - ConcurrentProfileStorageCollection abandoned; - if(profiledCommands.TryRemove(pair.Key, out abandoned)) - { - // shove it back in the pool, but don't bother enumerating - abandoned.ReturnForReuse(); - } - } - } - } - - return true; - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ProfileStorage.cs b/StackExchange.Redis/StackExchange/Redis/ProfileStorage.cs deleted file mode 100644 index 22570b2b7..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ProfileStorage.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Diagnostics; -using System.Net; -using System.Threading; - -namespace StackExchange.Redis -{ - class ProfileStorage : IProfiledCommand - { - #region IProfiledCommand Impl - public EndPoint EndPoint => Server.EndPoint; - - public int Db => Message.Db; - - public string Command => Message.Command.ToString(); - - public CommandFlags Flags => Message.Flags; - - public DateTime CommandCreated => MessageCreatedDateTime; - - public TimeSpan CreationToEnqueued => TimeSpan.FromTicks(EnqueuedTimeStamp - MessageCreatedTimeStamp); - - public TimeSpan EnqueuedToSending => TimeSpan.FromTicks(RequestSentTimeStamp - EnqueuedTimeStamp); - - public TimeSpan SentToResponse => TimeSpan.FromTicks(ResponseReceivedTimeStamp - RequestSentTimeStamp); - - public TimeSpan ResponseToCompletion => TimeSpan.FromTicks(CompletedTimeStamp - ResponseReceivedTimeStamp); - - public TimeSpan ElapsedTime => TimeSpan.FromTicks(CompletedTimeStamp - MessageCreatedTimeStamp); - - public IProfiledCommand RetransmissionOf => OriginalProfiling; - - public RetransmissionReasonType? RetransmissionReason { get; } - - #endregion - - public ProfileStorage NextElement { get; set; } - - private Message Message; - private ServerEndPoint Server; - private ProfileStorage OriginalProfiling; - - private DateTime MessageCreatedDateTime; - private long MessageCreatedTimeStamp; - private long EnqueuedTimeStamp; - private long RequestSentTimeStamp; - private long ResponseReceivedTimeStamp; - private long CompletedTimeStamp; - - private ConcurrentProfileStorageCollection PushToWhenFinished; - - private ProfileStorage(ConcurrentProfileStorageCollection pushTo, ServerEndPoint server, ProfileStorage resentFor, RetransmissionReasonType? reason) - { - PushToWhenFinished = pushTo; - OriginalProfiling = resentFor; - Server = server; - RetransmissionReason = reason; - } - - public static ProfileStorage NewWithContext(ConcurrentProfileStorageCollection pushTo, ServerEndPoint server) - { - return new ProfileStorage(pushTo, server, null, null); - } - - public static ProfileStorage NewAttachedToSameContext(ProfileStorage resentFor, ServerEndPoint server, bool isMoved) - { - return new ProfileStorage(resentFor.PushToWhenFinished, server, resentFor, isMoved ? RetransmissionReasonType.Moved : RetransmissionReasonType.Ask); - } - - public void SetMessage(Message msg) - { - // This method should never be called twice - if (Message != null) throw new InvalidOperationException(); - - Message = msg; - MessageCreatedDateTime = msg.createdDateTime; - MessageCreatedTimeStamp = msg.createdTimestamp; - } - - public void SetEnqueued() - { - // This method should never be called twice - if (EnqueuedTimeStamp > 0) throw new InvalidOperationException(); - - EnqueuedTimeStamp = Stopwatch.GetTimestamp(); - } - - public void SetRequestSent() - { - // This method should never be called twice - if (RequestSentTimeStamp > 0) throw new InvalidOperationException(); - - RequestSentTimeStamp = Stopwatch.GetTimestamp(); - } - - public void SetResponseReceived() - { - if (ResponseReceivedTimeStamp > 0) throw new InvalidOperationException(); - - ResponseReceivedTimeStamp = Stopwatch.GetTimestamp(); - } - - public void SetCompleted() - { - // this method can be called multiple times, depending on how the task completed (async vs not) - // so we actually have to guard against it. - - var now = Stopwatch.GetTimestamp(); - var oldVal = Interlocked.CompareExchange(ref CompletedTimeStamp, now, 0); - - // second call - if (oldVal != 0) return; - - // only push on the first call, no dupes! - PushToWhenFinished.Add(this); - } - - public override string ToString() - { - return - $@"EndPoint = {EndPoint} -Db = {Db} -Command = {Command} -CommandCreated = {CommandCreated:u} -CreationToEnqueued = {CreationToEnqueued} -EnqueuedToSending = {EnqueuedToSending} -SentToResponse = {SentToResponse} -ResponseToCompletion = {ResponseToCompletion} -ElapsedTime = {ElapsedTime} -Flags = {Flags} -RetransmissionOf = ({RetransmissionOf})"; - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/RawResult.cs b/StackExchange.Redis/StackExchange/Redis/RawResult.cs deleted file mode 100644 index 49969820e..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RawResult.cs +++ /dev/null @@ -1,364 +0,0 @@ -using System; -using System.Text; - -namespace StackExchange.Redis -{ - - - internal struct RawResult - { - public static readonly RawResult EmptyArray = new RawResult(new RawResult[0]); - public static readonly RawResult Nil = new RawResult(); - private static readonly byte[] emptyBlob = new byte[0]; - private readonly int offset, count; - private Array arr; - public RawResult(ResultType resultType, byte[] buffer, int offset, int count) - { - switch (resultType) - { - case ResultType.SimpleString: - case ResultType.Error: - case ResultType.Integer: - case ResultType.BulkString: - break; - default: - throw new ArgumentOutOfRangeException(nameof(resultType)); - } - Type = resultType; - arr = buffer; - this.offset = offset; - this.count = count; - } - - public RawResult(RawResult[] arr) - { - if (arr == null) throw new ArgumentNullException(nameof(arr)); - Type = ResultType.MultiBulk; - offset = 0; - count = arr.Length; - this.arr = arr; - } - - public bool HasValue => Type != ResultType.None; - - public bool IsError => Type == ResultType.Error; - - public ResultType Type { get; } - - internal bool IsNull => arr == null; - - public override string ToString() - { - if (arr == null) - { - return "(null)"; - } - switch (Type) - { - case ResultType.SimpleString: - case ResultType.Integer: - case ResultType.Error: - return $"{Type}: {GetString()}"; - case ResultType.BulkString: - return $"{Type}: {count} bytes"; - case ResultType.MultiBulk: - return $"{Type}: {count} items"; - default: - return "(unknown)"; - } - } - internal RedisChannel AsRedisChannel(byte[] channelPrefix, RedisChannel.PatternMode mode) - { - switch (Type) - { - case ResultType.SimpleString: - case ResultType.BulkString: - if (channelPrefix == null) - { - return new RedisChannel(GetBlob(), mode); - } - if (AssertStarts(channelPrefix)) - { - var src = (byte[])arr; - - byte[] copy = new byte[count - channelPrefix.Length]; - Buffer.BlockCopy(src, offset + channelPrefix.Length, copy, 0, copy.Length); - return new RedisChannel(copy, mode); - } - return default(RedisChannel); - default: - throw new InvalidCastException("Cannot convert to RedisChannel: " + Type); - } - } - - internal RedisKey AsRedisKey() - { - switch (Type) - { - case ResultType.SimpleString: - case ResultType.BulkString: - return (RedisKey)GetBlob(); - default: - throw new InvalidCastException("Cannot convert to RedisKey: " + Type); - } - } - internal RedisValue AsRedisValue() - { - switch (Type) - { - case ResultType.Integer: - long i64; - if (TryGetInt64(out i64)) return (RedisValue)i64; - break; - case ResultType.SimpleString: - case ResultType.BulkString: - return (RedisValue)GetBlob(); - } - throw new InvalidCastException("Cannot convert to RedisValue: " + Type); - } - - internal unsafe bool IsEqual(byte[] expected) - { - if (expected == null) throw new ArgumentNullException(nameof(expected)); - if (expected.Length != count) return false; - var actual = arr as byte[]; - if (actual == null) return false; - - int octets = count / 8, spare = count % 8; - fixed (byte* actual8 = &actual[offset]) - fixed (byte* expected8 = expected) - { - long* actual64 = (long*)actual8; - long* expected64 = (long*)expected8; - - for (int i = 0; i < octets; i++) - { - if (actual64[i] != expected64[i]) return false; - } - int index = count - spare; - while (spare-- != 0) - { - if (actual8[index] != expected8[index]) return false; - } - } - return true; - } - - internal bool AssertStarts(byte[] expected) - { - if (expected == null) throw new ArgumentNullException(nameof(expected)); - if (expected.Length > count) return false; - var actual = arr as byte[]; - if (actual == null) return false; - - for (int i = 0; i < expected.Length; i++) - { - if (expected[i] != actual[offset + i]) return false; - } - return true; - } - internal byte[] GetBlob() - { - var src = (byte[])arr; - if (src == null) return null; - - if (count == 0) return emptyBlob; - - byte[] copy = new byte[count]; - Buffer.BlockCopy(src, offset, copy, 0, count); - return copy; - } - - internal bool GetBoolean() - { - if (count != 1) throw new InvalidCastException(); - byte[] actual = arr as byte[]; - if (actual == null) throw new InvalidCastException(); - switch (actual[offset]) - { - case (byte)'1': return true; - case (byte)'0': return false; - default: throw new InvalidCastException(); - } - } - - internal RawResult[] GetItems() - { - return (RawResult[])arr; - } - - internal RedisKey[] GetItemsAsKeys() - { - RawResult[] items = GetItems(); - if (items == null) - { - return null; - } - else if (items.Length == 0) - { - return RedisKey.EmptyArray; - } - else - { - var arr = new RedisKey[items.Length]; - for (int i = 0; i < arr.Length; i++) - { - arr[i] = items[i].AsRedisKey(); - } - return arr; - } - } - - internal RedisValue[] GetItemsAsValues() - { - RawResult[] items = GetItems(); - if (items == null) - { - return null; - } - else if (items.Length == 0) - { - return RedisValue.EmptyArray; - } - else - { - var arr = new RedisValue[items.Length]; - for (int i = 0; i < arr.Length; i++) - { - arr[i] = items[i].AsRedisValue(); - } - return arr; - } - } - static readonly string[] NilStrings = new string[0]; - internal string[] GetItemsAsStrings() - { - RawResult[] items = GetItems(); - if (items == null) - { - return null; - } - else if (items.Length == 0) - { - return NilStrings; - } - else - { - var arr = new string[items.Length]; - for (int i = 0; i < arr.Length; i++) - { - arr[i] = (string)(items[i].AsRedisValue()); - } - return arr; - } - } - internal GeoPosition? GetItemsAsGeoPosition() - { - RawResult[] items = GetItems(); - if (items == null || items.Length == 0) - { - return null; - } - - var coords = items[0].GetArrayOfRawResults(); - if (coords == null) - { - return null; - } - return new GeoPosition((double)coords[0].AsRedisValue(), (double)coords[1].AsRedisValue()); - } - internal GeoPosition?[] GetItemsAsGeoPositionArray() - { - RawResult[] items = GetItems(); - if (items == null) - { - return null; - } - else if (items.Length == 0) - { - return new GeoPosition?[0]; - } - else - { - var arr = new GeoPosition?[items.Length]; - for (int i = 0; i < arr.Length; i++) - { - RawResult[] item = items[i].GetArrayOfRawResults(); - if (item == null) - { - arr[i] = null; - } - else - { - arr[i] = new GeoPosition((double)item[0].AsRedisValue(), (double)item[1].AsRedisValue()); - } - } - return arr; - } - } - - internal RawResult[] GetItemsAsRawResults() - { - return GetItems(); - } - - - // returns an array of RawResults - internal RawResult[] GetArrayOfRawResults() - { - if (arr == null) - { - return null; - } - else if (arr.Length == 0) - { - return new RawResult[0]; - } - else - { - var rawResultArray = new RawResult[arr.Length]; - for (int i = 0; i < arr.Length; i++) - { - var rawResult = (RawResult)arr.GetValue(i); - rawResultArray.SetValue(rawResult, i); - } - return rawResultArray; - } - } - - internal string GetString() - { - if (arr == null) return null; - var blob = (byte[])arr; - if (blob.Length == 0) return ""; - return Encoding.UTF8.GetString(blob, offset, count); - } - - internal bool TryGetDouble(out double val) - { - if (arr == null) - { - val = 0; - return false; - } - long i64; - if (TryGetInt64(out i64)) - { - val = i64; - return true; - } - return Format.TryParseDouble(GetString(), out val); - } - - internal bool TryGetInt64(out long value) - { - if (arr == null) - { - value = 0; - return false; - } - return RedisValue.TryParseInt64(arr as byte[], offset, count, out value); - } - } -} - diff --git a/StackExchange.Redis/StackExchange/Redis/RedisBase.cs b/StackExchange.Redis/StackExchange/Redis/RedisBase.cs deleted file mode 100644 index 97568ad98..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisBase.cs +++ /dev/null @@ -1,340 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - internal abstract partial class RedisBase : IRedis - { - internal static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - internal readonly ConnectionMultiplexer multiplexer; - protected readonly object asyncState; - - internal RedisBase(ConnectionMultiplexer multiplexer, object asyncState) - { - this.multiplexer = multiplexer; - this.asyncState = asyncState; - } - - ConnectionMultiplexer IRedisAsync.Multiplexer => multiplexer; - - public virtual TimeSpan Ping(CommandFlags flags = CommandFlags.None) - { - var msg = GetTimerMessage(flags); - return ExecuteSync(msg, ResultProcessor.ResponseTimer); - } - - public virtual Task PingAsync(CommandFlags flags = CommandFlags.None) - { - var msg = GetTimerMessage(flags); - return ExecuteAsync(msg, ResultProcessor.ResponseTimer); - } - - public void Quit(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.QUIT); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - - public Task QuitAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.QUIT); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public override string ToString() - { - return multiplexer.ToString(); - } - - public bool TryWait(Task task) - { - return task.Wait(multiplexer.TimeoutMilliseconds); - } - - public void Wait(Task task) - { - multiplexer.Wait(task); - } - - public T Wait(Task task) - { - return multiplexer.Wait(task); - } - - public void WaitAll(params Task[] tasks) - { - multiplexer.WaitAll(tasks); - } - - internal virtual Task ExecuteAsync(Message message, ResultProcessor processor, ServerEndPoint server = null) - { - if (message == null) return CompletedTask.Default(asyncState); - multiplexer.CheckMessage(message); - return multiplexer.ExecuteAsyncImpl(message, processor, asyncState, server); - } - - internal virtual T ExecuteSync(Message message, ResultProcessor processor, ServerEndPoint server = null) - { - if (message == null) return default(T); // no-op - multiplexer.CheckMessage(message); - return multiplexer.ExecuteSyncImpl(message, processor, server); - } - - internal virtual RedisFeatures GetFeatures(int db, RedisKey key, CommandFlags flags, out ServerEndPoint server) - { - server = multiplexer.SelectServer(db, RedisCommand.PING, flags, key); - var version = server == null ? multiplexer.RawConfig.DefaultVersion : server.Version; - return new RedisFeatures(version); - } - - protected void WhenAlwaysOrExists(When when) - { - switch (when) - { - case When.Always: - case When.Exists: - break; - default: - throw new ArgumentException(when + " is not valid in this context; the permitted values are: Always, Exists"); - } - } - - protected void WhenAlwaysOrExistsOrNotExists(When when) - { - switch (when) - { - case When.Always: - case When.Exists: - case When.NotExists: - break; - default: - throw new ArgumentException(when + " is not valid in this context; the permitted values are: Always, Exists, NotExists"); - } - } - - protected void WhenAlwaysOrNotExists(When when) - { - switch (when) - { - case When.Always: - case When.NotExists: - break; - default: - throw new ArgumentException(when + " is not valid in this context; the permitted values are: Always, NotExists"); - } - } - - private ResultProcessor.TimingProcessor.TimerMessage GetTimerMessage(CommandFlags flags) - { - // do the best we can with available commands - var map = multiplexer.CommandMap; - if(map.IsAvailable(RedisCommand.PING)) - return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.PING); - if(map.IsAvailable(RedisCommand.TIME)) - return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.TIME); - if (map.IsAvailable(RedisCommand.ECHO)) - return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.ECHO, RedisLiterals.PING); - // as our fallback, we'll do something odd... we'll treat a key like a value, out of sheer desperation - // note: this usually means: twemproxy - in which case we're fine anyway, since the proxy does the routing - return ResultProcessor.TimingProcessor.CreateMessage(0, flags, RedisCommand.EXISTS, (RedisValue)multiplexer.UniqueId); - } - - - internal static class CursorUtils - { - internal const int Origin = 0, DefaultPageSize = 10; - internal static bool IsNil(RedisValue pattern) - { - if (pattern.IsNullOrEmpty) return true; - if (pattern.IsInteger) return false; - byte[] rawValue = pattern; - return rawValue.Length == 1 && rawValue[0] == '*'; - } - } - internal abstract class CursorEnumerable : IEnumerable, IScanningCursor - { - private readonly RedisBase redis; - private readonly ServerEndPoint server; - protected readonly int db; - protected readonly CommandFlags flags; - protected readonly int pageSize, initialOffset; - protected readonly long initialCursor; - private volatile IScanningCursor activeCursor; - - protected CursorEnumerable(RedisBase redis, ServerEndPoint server, int db, int pageSize, long cursor, int pageOffset, CommandFlags flags) - { - if (pageOffset < 0) throw new ArgumentOutOfRangeException(nameof(pageOffset)); - this.redis = redis; - this.server = server; - this.db = db; - this.pageSize = pageSize; - this.flags = flags; - initialCursor = cursor; - initialOffset = pageOffset; - } - - public IEnumerator GetEnumerator() - { - return new CursorEnumerator(this); - } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - internal struct ScanResult - { - public readonly long Cursor; - public readonly T[] Values; - public ScanResult(long cursor, T[] values) - { - Cursor = cursor; - Values = values; - } - } - - protected abstract Message CreateMessage(long cursor); - - - protected abstract ResultProcessor Processor { get; } - - protected ScanResult GetNextPageSync(IScanningCursor obj, long cursor) - { - activeCursor = obj; - return redis.ExecuteSync(CreateMessage(cursor), Processor, server); - } - protected Task GetNextPageAsync(IScanningCursor obj, long cursor) - { - activeCursor = obj; - return redis.ExecuteAsync(CreateMessage(cursor), Processor, server); - } - protected ScanResult Wait(Task pending) - { - return redis.Wait(pending); - } - - class CursorEnumerator : IEnumerator, IScanningCursor - { - private CursorEnumerable parent; - public CursorEnumerator(CursorEnumerable parent) - { - if (parent == null) throw new ArgumentNullException(nameof(parent)); - this.parent = parent; - Reset(); - } - public T Current => page[pageIndex]; - - void IDisposable.Dispose() { parent = null; state = State.Disposed; } - - object System.Collections.IEnumerator.Current => page[pageIndex]; - - private void LoadNextPageAsync() - { - if(pending == null && nextCursor != 0) - pending = parent.GetNextPageAsync(this, nextCursor); - } - - private bool SimpleNext() - { - if (page != null && ++pageIndex < page.Length) - { - // first of a new page? cool; start a new background op, because we're about to exit the iterator - if (pageIndex == 0) LoadNextPageAsync(); - return true; - } - return false; - } - - T[] page; - Task pending; - int pageIndex; - private long currentCursor, nextCursor; - - private State state; - private enum State : byte - { - Initial, - Running, - Complete, - Disposed, - } - - void ProcessReply(ScanResult result) - { - currentCursor = nextCursor; - nextCursor = result.Cursor; - pageIndex = -1; - page = result.Values; - pending = null; - } - - public bool MoveNext() - { - switch(state) - { - case State.Complete: - return false; - case State.Initial: - ProcessReply(parent.GetNextPageSync(this, nextCursor)); - pageIndex = parent.initialOffset - 1; // will be incremented in a moment - state = State.Running; - LoadNextPageAsync(); - goto case State.Running; - case State.Running: - // are we working through the current buffer? - if (SimpleNext()) return true; - - // do we have an outstanding operation? wait for the background task to finish - while (pending != null) - { - ProcessReply(parent.Wait(pending)); - if (SimpleNext()) return true; - } - - // nothing outstanding? wait synchronously - while(nextCursor != 0) - { - ProcessReply(parent.GetNextPageSync(this, nextCursor)); - if (SimpleNext()) return true; - } - - // we're exhausted - state = State.Complete; - return false; - case State.Disposed: - default: - throw new ObjectDisposedException(GetType().Name); - } - } - - public void Reset() - { - if(state == State.Disposed) throw new ObjectDisposedException(GetType().Name); - nextCursor = currentCursor = parent.initialCursor; - pageIndex = parent.initialOffset; // don't -1 here; this makes it look "right" before incremented - state = State.Initial; - page = null; - pending = null; - } - - long IScanningCursor.Cursor => currentCursor; - - int IScanningCursor.PageSize => parent.pageSize; - - int IScanningCursor.PageOffset => pageIndex; - } - - long IScanningCursor.Cursor - { - get { var tmp = activeCursor; return tmp?.Cursor ?? initialCursor; } - } - - int IScanningCursor.PageSize => pageSize; - - int IScanningCursor.PageOffset - { - get { var tmp = activeCursor; return tmp?.PageOffset ?? initialOffset; } - } - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisBatch.cs b/StackExchange.Redis/StackExchange/Redis/RedisBatch.cs deleted file mode 100644 index b018974a2..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisBatch.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - class RedisBatch : RedisDatabase, IBatch - { - List pending; - - public RedisBatch(RedisDatabase wrapped, object asyncState) : base(wrapped.multiplexer, wrapped.Database, asyncState ?? wrapped.AsyncState) - { - } - - public void Execute() - { - var snapshot = pending; - pending = null; - if (snapshot == null || snapshot.Count == 0) return; - - // group into per-bridge chunks - var byBridge = new Dictionary>(); - - // optimisation: assume most things are in a single bridge - PhysicalBridge lastBridge = null; - List lastList = null; - foreach (var message in snapshot) - { - var server = multiplexer.SelectServer(message); - if (server == null) - { - FailNoServer(snapshot); - throw ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, multiplexer.IncludePerformanceCountersInExceptions, message.Command, message, server,multiplexer.GetServerSnapshot()); - } - var bridge = server.GetBridge(message.Command); - if (bridge == null) - { - FailNoServer(snapshot); - throw ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, multiplexer.IncludePerformanceCountersInExceptions, message.Command, message, server, multiplexer.GetServerSnapshot()); - } - - // identity a list - List list; - if (bridge == lastBridge) - { - list = lastList; - } - else if (!byBridge.TryGetValue(bridge, out list)) - { - list = new List(); - byBridge.Add(bridge, list); - } - lastBridge = bridge; - lastList = list; - - list.Add(message); - } - - foreach (var pair in byBridge) - { - if (!pair.Key.TryEnqueue(pair.Value, pair.Key.ServerEndPoint.IsSlave)) - { - FailNoServer(pair.Value); - } - } - } - - internal override Task ExecuteAsync(Message message, ResultProcessor processor, ServerEndPoint server = null) - { - if (message == null) return CompletedTask.Default(asyncState); - multiplexer.CheckMessage(message); - - // prepare the inner command as a task - Task task; - if (message.IsFireAndForget) - { - task = CompletedTask.Default(null); // F+F explicitly does not get async-state - } - else - { - var tcs = TaskSource.CreateDenyExecSync(asyncState); - var source = ResultBox.Get(tcs); - message.SetSource(source, processor); - task = tcs.Task; - } - - // store it - (pending ?? (pending = new List())).Add(message); - return task; - } - - internal override T ExecuteSync(Message message, ResultProcessor processor, ServerEndPoint server = null) - { - throw new NotSupportedException("ExecuteSync cannot be used inside a transaction"); - } - private void FailNoServer(List messages) - { - if (messages == null) return; - var completion = multiplexer.UnprocessableCompletionManager; - foreach(var msg in messages) - { - msg.Fail(ConnectionFailureType.UnableToResolvePhysicalConnection, null); - completion.CompleteSyncOrAsync(msg); - - } - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisChannel.cs b/StackExchange.Redis/StackExchange/Redis/RedisChannel.cs deleted file mode 100644 index 47caf7910..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisChannel.cs +++ /dev/null @@ -1,261 +0,0 @@ -using System; -using System.Text; - -namespace StackExchange.Redis -{ - /// - /// Represents a pub/sub channel name - /// - public struct RedisChannel : IEquatable - { - internal static readonly RedisChannel[] EmptyArray = new RedisChannel[0]; - - private readonly byte[] value; - - /// - /// Create a new redis channel from a buffer, explicitly controlling the pattern mode - /// - public RedisChannel(byte[] value, PatternMode mode) : this(value, DeterminePatternBased(value, mode)) - { - } - - /// - /// Create a new redis channel from a string, explicitly controlling the pattern mode - /// - public RedisChannel(string value, PatternMode mode) : this(value == null ? null : Encoding.UTF8.GetBytes(value), mode) - { - } - - private RedisChannel(byte[] value, bool isPatternBased) - { - this.value = value; - IsPatternBased = isPatternBased; - } - private static bool DeterminePatternBased(byte[] value, PatternMode mode) - { - switch (mode) - { - case PatternMode.Auto: - return value != null && Array.IndexOf(value, (byte)'*') >= 0; - case PatternMode.Literal: return false; - case PatternMode.Pattern: return true; - default: - throw new ArgumentOutOfRangeException(nameof(mode)); - } - } - - /// - /// Indicates whether the channel-name is either null or a zero-length value - /// - public bool IsNullOrEmpty => value == null || value.Length == 0; - - internal bool IsNull => value == null; - - internal byte[] Value => value; - - /// - /// Indicate whether two channel names are not equal - /// - public static bool operator !=(RedisChannel x, RedisChannel y) - { - return !(x == y); - } - - /// - /// Indicate whether two channel names are not equal - /// - public static bool operator !=(string x, RedisChannel y) - { - return !(x == y); - } - - /// - /// Indicate whether two channel names are not equal - /// - public static bool operator !=(byte[] x, RedisChannel y) - { - return !(x == y); - } - - /// - /// Indicate whether two channel names are not equal - /// - public static bool operator !=(RedisChannel x, string y) - { - return !(x == y); - } - - /// - /// Indicate whether two channel names are not equal - /// - public static bool operator !=(RedisChannel x, byte[] y) - { - return !(x == y); - } - - /// - /// Indicate whether two channel names are equal - /// - public static bool operator ==(RedisChannel x, RedisChannel y) - { - return x.IsPatternBased == y.IsPatternBased && RedisValue.Equals(x.value, y.value); - } - - /// - /// Indicate whether two channel names are equal - /// - public static bool operator ==(string x, RedisChannel y) - { - return RedisValue.Equals(x == null ? null : Encoding.UTF8.GetBytes(x), y.value); - } - - /// - /// Indicate whether two channel names are equal - /// - public static bool operator ==(byte[] x, RedisChannel y) - { - return RedisValue.Equals(x, y.value); - } - - /// - /// Indicate whether two channel names are equal - /// - public static bool operator ==(RedisChannel x, string y) - { - return RedisValue.Equals(x.value, y == null ? null : Encoding.UTF8.GetBytes(y)); - } - - /// - /// Indicate whether two channel names are equal - /// - public static bool operator ==(RedisChannel x, byte[] y) - { - return RedisValue.Equals(x.value, y); - } - - /// - /// See Object.Equals - /// - public override bool Equals(object obj) - { - if (obj is RedisChannel) - { - return RedisValue.Equals(value, ((RedisChannel)obj).value); - } - if (obj is string) - { - return RedisValue.Equals(value, Encoding.UTF8.GetBytes((string)obj)); - } - if (obj is byte[]) - { - return RedisValue.Equals(value, (byte[])obj); - } - return false; - } - - /// - /// Indicate whether two channel names are equal - /// - public bool Equals(RedisChannel other) - { - return IsPatternBased == other.IsPatternBased && - RedisValue.Equals(value, other.value); - } - - /// - /// See Object.GetHashCode - /// - public override int GetHashCode() - { - return RedisValue.GetHashCode(value) + (IsPatternBased ? 1 : 0); - } - - /// - /// Obtains a string representation of the channel name - /// - public override string ToString() - { - return ((string)this) ?? "(null)"; - } - - internal static bool AssertStarts(byte[] value, byte[] expected) - { - for (int i = 0; i < expected.Length; i++) - { - if (expected[i] != value[i]) return false; - } - return true; - } - - internal void AssertNotNull() - { - if (IsNull) throw new ArgumentException("A null key is not valid in this context"); - } - - internal RedisChannel Clone() - { - byte[] clone = (byte[]) value?.Clone(); - return clone; - } - - internal readonly bool IsPatternBased; - - /// - /// The matching pattern for this channel - /// - public enum PatternMode - { - /// - /// Will be treated as a pattern if it includes * - /// - Auto = 0, - /// - /// Never a pattern - /// - Literal = 1, - /// - /// Always a pattern - /// - Pattern = 2 - } - /// - /// Create a channel name from a String - /// - public static implicit operator RedisChannel(string key) - { - if (key == null) return default(RedisChannel); - return new RedisChannel(Encoding.UTF8.GetBytes(key), PatternMode.Auto); - } - /// - /// Create a channel name from a Byte[] - /// - public static implicit operator RedisChannel(byte[] key) - { - if (key == null) return default(RedisChannel); - return new RedisChannel(key, PatternMode.Auto); - } - /// - /// Obtain the channel name as a Byte[] - /// - public static implicit operator byte[] (RedisChannel key) - { - return key.value; - } - /// - /// Obtain the channel name as a String - /// - public static implicit operator string (RedisChannel key) - { - var arr = key.value; - if (arr == null) return null; - try - { - return Encoding.UTF8.GetString(arr); - } - catch - { - return BitConverter.ToString(arr); - } - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisCommand.cs b/StackExchange.Redis/StackExchange/Redis/RedisCommand.cs deleted file mode 100644 index 2fbc975d3..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisCommand.cs +++ /dev/null @@ -1,189 +0,0 @@ -namespace StackExchange.Redis -{ - enum RedisCommand - { - APPEND, - ASKING, - AUTH, - - BGREWRITEAOF, - BGSAVE, - BITCOUNT, - BITOP, - BITPOS, - BLPOP, - BRPOP, - BRPOPLPUSH, - - CLIENT, - CLUSTER, - CONFIG, - - DBSIZE, - DEBUG, - DECR, - DECRBY, - DEL, - DISCARD, - DUMP, - - ECHO, - EVAL, - EVALSHA, - EXEC, - EXISTS, - EXPIRE, - EXPIREAT, - - FLUSHALL, - FLUSHDB, - - GEOADD, - GEODIST, - GEOHASH, - GEOPOS, - GEORADIUS, - GEORADIUSBYMEMBER, - - GET, - GETBIT, - GETRANGE, - GETSET, - - HDEL, - HEXISTS, - HGET, - HGETALL, - HINCRBY, - HINCRBYFLOAT, - HKEYS, - HLEN, - HMGET, - HMSET, - HSCAN, - HSET, - HSETNX, - HVALS, - - INCR, - INCRBY, - INCRBYFLOAT, - INFO, - - KEYS, - - LASTSAVE, - LINDEX, - LINSERT, - LLEN, - LPOP, - LPUSH, - LPUSHX, - LRANGE, - LREM, - LSET, - LTRIM, - - MGET, - MIGRATE, - MONITOR, - MOVE, - MSET, - MSETNX, - MULTI, - - OBJECT, - - PERSIST, - PEXPIRE, - PEXPIREAT, - PFADD, - PFCOUNT, - PFMERGE, - PING, - PSETEX, - PSUBSCRIBE, - PTTL, - PUBLISH, - PUBSUB, - PUNSUBSCRIBE, - - QUIT, - - RANDOMKEY, - READONLY, - READWRITE, - RENAME, - RENAMENX, - RESTORE, - RPOP, - RPOPLPUSH, - RPUSH, - RPUSHX, - - SADD, - SAVE, - SCAN, - SCARD, - SCRIPT, - SDIFF, - SDIFFSTORE, - SELECT, - SENTINEL, - SET, - SETBIT, - SETEX, - SETNX, - SETRANGE, - SHUTDOWN, - SINTER, - SINTERSTORE, - SISMEMBER, - SLAVEOF, - SLOWLOG, - SMEMBERS, - SMOVE, - SORT, - SPOP, - SRANDMEMBER, - SREM, - STRLEN, - SUBSCRIBE, - SUNION, - SUNIONSTORE, - SSCAN, - SYNC, - - TIME, - TTL, - TYPE, - - UNSUBSCRIBE, - UNWATCH, - - WATCH, - - ZADD, - ZCARD, - ZCOUNT, - ZINCRBY, - ZINTERSTORE, - ZLEXCOUNT, - ZRANGE, - ZRANGEBYLEX, - ZRANGEBYSCORE, - ZRANK, - ZREM, - ZREMRANGEBYLEX, - ZREMRANGEBYRANK, - ZREMRANGEBYSCORE, - ZREVRANGE, - ZREVRANGEBYSCORE, - ZREVRANK, - ZSCAN, - ZSCORE, - ZUNIONSTORE, - - UNKNOWN, - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs b/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs deleted file mode 100644 index 059f5c39b..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisDatabase.cs +++ /dev/null @@ -1,2702 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - internal class RedisDatabase : RedisBase, IDatabase - { - internal RedisDatabase(ConnectionMultiplexer multiplexer, int db, object asyncState) - : base(multiplexer, asyncState) - { - Database = db; - } - - public object AsyncState => asyncState; - - public int Database { get; } - - public IBatch CreateBatch(object asyncState) - { - if (this is IBatch) throw new NotSupportedException("Nested batches are not supported"); - return new RedisBatch(this, asyncState); - } - - public ITransaction CreateTransaction(object asyncState) - { - if (this is IBatch) throw new NotSupportedException("Nested transactions are not supported"); - return new RedisTransaction(this, asyncState); - } - - private ITransaction CreateTransactionIfAvailable(object asyncState) - { - var map = multiplexer.CommandMap; - if (!map.IsAvailable(RedisCommand.MULTI) || !map.IsAvailable(RedisCommand.EXEC)) - { - return null; - } - return CreateTransaction(asyncState); - } - - public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - public Task DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None) - { - return GeoAdd(key, new GeoEntry(longitude, latitude, member), flags); - } - - public Task GeoAddAsync(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None) - { - return GeoAddAsync(key, new GeoEntry(longitude, latitude, member), flags); - } - - public bool GeoAdd(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, value.Longitude, value.Latitude, value.Member); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - public Task GeoAddAsync(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, value.Longitude, value.Latitude, value.Member); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public long GeoAdd(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, values); - return ExecuteSync(msg, ResultProcessor.Int64); - } - public Task GeoAddAsync(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, values); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - return SortedSetRemove(key, member, flags); - } - public Task GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - return SortedSetRemoveAsync(key, member, flags); - } - - public double? GeoDistance(RedisKey key, RedisValue value0, RedisValue value1, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GEODIST, key, value0, value1, StackExchange.Redis.GeoPosition.GetRedisUnit(unit)); - return ExecuteSync(msg, ResultProcessor.NullableDouble); - } - public Task GeoDistanceAsync(RedisKey key, RedisValue value0, RedisValue value1, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GEODIST, key, value0, value1, StackExchange.Redis.GeoPosition.GetRedisUnit(unit)); - return ExecuteAsync(msg, ResultProcessor.NullableDouble); - } - public string[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) - { - if (members == null) throw new ArgumentNullException(nameof(members)); - var redisValues = new RedisValue[members.Length]; - for (var i = 0; i < members.Length; i++) redisValues[i] = members[i]; - var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues); - return ExecuteSync(msg, ResultProcessor.StringArray); - } - public Task GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) - { - if (members == null) throw new ArgumentNullException(nameof(members)); - var redisValues = new RedisValue[members.Length]; - for (var i = 0; i < members.Length; i++) redisValues[i] = members[i]; - var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues); - return ExecuteAsync(msg, ResultProcessor.StringArray); - } - - public string GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, member); - return ExecuteSync(msg, ResultProcessor.String); - } - public Task GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, member); - return ExecuteAsync(msg, ResultProcessor.String); - } - - public GeoPosition?[] GeoPosition(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) - { - if (members == null) throw new ArgumentNullException(nameof(members)); - var redisValues = new RedisValue[members.Length]; - for (var i = 0; i < members.Length; i++) redisValues[i] = members[i]; - var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, redisValues); - return ExecuteSync(msg, ResultProcessor.RedisGeoPositionArray); - } - public Task GeoPositionAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) - { - if (members == null) throw new ArgumentNullException(nameof(members)); - var redisValues = new RedisValue[members.Length]; - for (var i = 0; i < members.Length; i++) redisValues[i] = members[i]; - var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, redisValues); - return ExecuteAsync(msg, ResultProcessor.RedisGeoPositionArray); - } - - public GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, member); - return ExecuteSync(msg, ResultProcessor.RedisGeoPosition); - } - public Task GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, member); - return ExecuteAsync(msg, ResultProcessor.RedisGeoPosition); - } - static readonly RedisValue - WITHCOORD = Encoding.ASCII.GetBytes("WITHCOORD"), - WITHDIST = Encoding.ASCII.GetBytes("WITHDIST"), - WITHHASH = Encoding.ASCII.GetBytes("WITHHASH"), - COUNT = Encoding.ASCII.GetBytes("COUNT"), - ASC = Encoding.ASCII.GetBytes("ASC"), - DESC = Encoding.ASCII.GetBytes("DESC"); - private Message GetGeoRadiusMessage(RedisKey key, RedisValue? member, double longitude, double latitude, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags) - { - var redisValues = new List(); - RedisCommand command; - if (member == null) - { - redisValues.Add(longitude); - redisValues.Add(latitude); - command = RedisCommand.GEORADIUS; - } - else - { - redisValues.Add(member.Value); - command = RedisCommand.GEORADIUSBYMEMBER; - } - redisValues.Add(radius); - redisValues.Add(StackExchange.Redis.GeoPosition.GetRedisUnit(unit)); - if ((options & GeoRadiusOptions.WithCoordinates) != 0) redisValues.Add(WITHCOORD); - if ((options & GeoRadiusOptions.WithDistance) != 0) redisValues.Add(WITHDIST); - if ((options & GeoRadiusOptions.WithGeoHash) != 0) redisValues.Add(WITHHASH); - if (count > 0) - { - redisValues.Add(COUNT); - redisValues.Add(count); - } - if (order != null) - { - switch (order.Value) - { - case Order.Ascending: redisValues.Add(ASC); break; - case Order.Descending: redisValues.Add(DESC); break; - default: throw new ArgumentOutOfRangeException(nameof(order)); - } - } - - return Message.Create(Database, flags, command, key, redisValues.ToArray()); - } - public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags) - { - return ExecuteSync(GetGeoRadiusMessage(key, member, double.NaN, double.NaN, radius, unit, count, order, options, flags), ResultProcessor.GeoRadiusArray(options)); - } - public Task GeoRadiusAsync(RedisKey key, RedisValue member, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags) - { - return ExecuteAsync(GetGeoRadiusMessage(key, member, double.NaN, double.NaN, radius, unit, count, order, options, flags), ResultProcessor.GeoRadiusArray(options)); - } - public GeoRadiusResult[] GeoRadius(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags) - { - return ExecuteSync(GetGeoRadiusMessage(key, null, longitude, latitude, radius, unit, count, order, options, flags), ResultProcessor.GeoRadiusArray(options)); - } - public Task GeoRadiusAsync(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags) - { - return ExecuteAsync(GetGeoRadiusMessage(key, null, longitude, latitude, radius, unit, count, order, options, flags), ResultProcessor.GeoRadiusArray(options)); - } - - public long HashDecrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) - { - return HashIncrement(key, hashField, -value, flags); - } - - public double HashDecrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) - { - return HashIncrement(key, hashField, -value, flags); - } - - public Task HashDecrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) - { - return HashIncrementAsync(key, hashField, -value, flags); - } - - public Task HashDecrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) - { - return HashIncrementAsync(key, hashField, -value, flags); - } - - public bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HDEL, key, hashField); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public long HashDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) - { - if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - var msg = hashFields.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.HDEL, key, hashFields); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HDEL, key, hashField); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public Task HashDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) - { - if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - - var msg = hashFields.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.HDEL, key, hashFields); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HEXISTS, key, hashField); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HEXISTS, key, hashField); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) - { - if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - if (hashFields.Length == 0) return new RedisValue[0]; - var msg = Message.Create(Database, flags, RedisCommand.HMGET, key, hashFields); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HGETALL, key); - return ExecuteSync(msg, ResultProcessor.HashEntryArray); - } - - public Task HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HGETALL, key); - return ExecuteAsync(msg, ResultProcessor.HashEntryArray); - } - - public Task HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public Task HashGetAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) - { - if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - if (hashFields.Length == 0) return CompletedTask.FromResult(new RedisValue[0], asyncState); - var msg = Message.Create(Database, flags, RedisCommand.HMGET, key, hashFields); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - - public long HashIncrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) - { - var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 - ? null : Message.Create(Database, flags, RedisCommand.HINCRBY, key, hashField, value); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public double HashIncrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) - { - var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 - ? null : Message.Create(Database, flags, RedisCommand.HINCRBYFLOAT, key, hashField, value); - return ExecuteSync(msg, ResultProcessor.Double); - } - - public Task HashIncrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) - { - var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 - ? null : Message.Create(Database, flags, RedisCommand.HINCRBY, key, hashField, value); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public Task HashIncrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) - { - var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 - ? null : Message.Create(Database, flags, RedisCommand.HINCRBYFLOAT, key, hashField, value); - return ExecuteAsync(msg, ResultProcessor.Double); - } - - public RedisValue[] HashKeys(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HKEYS, key); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public Task HashKeysAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HKEYS, key); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - - public long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HLEN, key); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HLEN, key); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - IEnumerable IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) - { - return HashScan(key, pattern, pageSize, CursorUtils.Origin, 0, flags); - } - - public IEnumerable HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = CursorUtils.DefaultPageSize, long cursor = CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) - { - var scan = TryScan(key, pattern, pageSize, cursor, pageOffset, flags, RedisCommand.HSCAN, HashScanResultProcessor.Default); - if (scan != null) return scan; - - if (cursor != 0 || pageOffset != 0) throw ExceptionFactory.NoCursor(RedisCommand.HGETALL); - if (pattern.IsNull) return HashGetAll(key, flags); - throw ExceptionFactory.NotSupported(true, RedisCommand.HSCAN); - } - - public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - WhenAlwaysOrNotExists(when); - var msg = value.IsNull - ? Message.Create(Database, flags, RedisCommand.HDEL, key, hashField) - : Message.Create(Database, flags, when == When.Always ? RedisCommand.HSET : RedisCommand.HSETNX, key, hashField, value); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public void HashSet(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None) - { - var msg = GetHashSetMessage(key, hashFields, flags); - if (msg == null) return; - ExecuteSync(msg, ResultProcessor.DemandOK); - } - - public Task HashSetAsync(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - WhenAlwaysOrNotExists(when); - var msg = value.IsNull - ? Message.Create(Database, flags, RedisCommand.HDEL, key, hashField) - : Message.Create(Database, flags, when == When.Always ? RedisCommand.HSET : RedisCommand.HSETNX, key, hashField, value); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None) - { - var msg = GetHashSetMessage(key, hashFields, flags); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public Task HashSetIfNotExistsAsync(RedisKey key, RedisValue hashField, RedisValue value, CommandFlags flags) - { - var msg = Message.Create(Database, flags, RedisCommand.HSETNX, key, hashField, value); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public RedisValue[] HashValues(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HVALS, key); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public Task HashValuesAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.HVALS, key); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - - public bool HyperLogLogAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, value); - return ExecuteSync(cmd, ResultProcessor.Boolean); - } - - public bool HyperLogLogAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, values); - return ExecuteSync(cmd, ResultProcessor.Boolean); - } - - public Task HyperLogLogAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, value); - return ExecuteAsync(cmd, ResultProcessor.Boolean); - } - - public Task HyperLogLogAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, values); - return ExecuteAsync(cmd, ResultProcessor.Boolean); - } - - public long HyperLogLogLength(RedisKey key, CommandFlags flags = CommandFlags.None) - { - ServerEndPoint server; - var features = GetFeatures(Database, key, flags, out server); - var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, key); - // technically a write / master-only command until 2.8.18 - if (server != null && !features.HyperLogLogCountSlaveSafe) cmd.SetMasterOnly(); - return ExecuteSync(cmd, ResultProcessor.Int64, server); - } - - public long HyperLogLogLength(RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - if (keys == null) throw new ArgumentNullException(nameof(keys)); - ServerEndPoint server = null; - var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, keys); - if (keys.Length != 0) - { - var features = GetFeatures(Database, keys[0], flags, out server); - // technically a write / master-only command until 2.8.18 - if (server != null && !features.HyperLogLogCountSlaveSafe) cmd.SetMasterOnly(); - } - return ExecuteSync(cmd, ResultProcessor.Int64, server); - } - - public Task HyperLogLogLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - ServerEndPoint server; - var features = GetFeatures(Database, key, flags, out server); - var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, key); - // technically a write / master-only command until 2.8.18 - if (server != null && !features.HyperLogLogCountSlaveSafe) cmd.SetMasterOnly(); - return ExecuteAsync(cmd, ResultProcessor.Int64, server); - } - - public Task HyperLogLogLengthAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - if (keys == null) throw new ArgumentNullException(nameof(keys)); - ServerEndPoint server = null; - var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, keys); - if (keys.Length != 0) - { - var features = GetFeatures(Database, keys[0], flags, out server); - // technically a write / master-only command until 2.8.18 - if (server != null && !features.HyperLogLogCountSlaveSafe) cmd.SetMasterOnly(); - } - return ExecuteAsync(cmd, ResultProcessor.Int64, server); - } - - public void HyperLogLogMerge(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, first, second); - ExecuteSync(cmd, ResultProcessor.DemandOK); - } - - public void HyperLogLogMerge(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) - { - var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, sourceKeys); - ExecuteSync(cmd, ResultProcessor.DemandOK); - } - - public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, first, second); - return ExecuteAsync(cmd, ResultProcessor.DemandOK); - } - - public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) - { - var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, sourceKeys); - return ExecuteAsync(cmd, ResultProcessor.DemandOK); - } - - public EndPoint IdentifyEndpoint(RedisKey key = default(RedisKey), CommandFlags flags = CommandFlags.None) - { - var msg = key.IsNull ? Message.Create(-1, flags, RedisCommand.PING) : Message.Create(Database, flags, RedisCommand.EXISTS, key); - return ExecuteSync(msg, ResultProcessor.ConnectionIdentity); - } - - public Task IdentifyEndpointAsync(RedisKey key = default(RedisKey), CommandFlags flags = CommandFlags.None) - { - var msg = key.IsNull ? Message.Create(-1, flags, RedisCommand.PING) : Message.Create(Database, flags, RedisCommand.EXISTS, key); - return ExecuteAsync(msg, ResultProcessor.ConnectionIdentity); - } - - public bool IsConnected(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var server = multiplexer.SelectServer(Database, RedisCommand.PING, flags, key); - return server != null && server.IsConnected; - } - - public bool KeyDelete(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.DEL, key); - return ExecuteSync(msg, ResultProcessor.DemandZeroOrOne); - } - - public long KeyDelete(RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - if (keys == null) throw new ArgumentNullException(nameof(keys)); - var msg = keys.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.DEL, keys); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task KeyDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.DEL, key); - return ExecuteAsync(msg, ResultProcessor.DemandZeroOrOne); - } - - public Task KeyDeleteAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - if (keys == null) throw new ArgumentNullException(nameof(keys)); - - var msg = keys.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.DEL, keys); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public byte[] KeyDump(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.DUMP, key); - return ExecuteSync(msg, ResultProcessor.ByteArray); - } - - public Task KeyDumpAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.DUMP, key); - return ExecuteAsync(msg, ResultProcessor.ByteArray); - } - - public bool KeyExists(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.EXISTS, key); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public Task KeyExistsAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.EXISTS, key); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) - { - ServerEndPoint server; - var msg = GetExpiryMessage(key, flags, expiry, out server); - return ExecuteSync(msg, ResultProcessor.Boolean, server: server); - } - - public bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None) - { - ServerEndPoint server; - var msg = GetExpiryMessage(key, flags, expiry, out server); - return ExecuteSync(msg, ResultProcessor.Boolean, server: server); - } - - public Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) - { - ServerEndPoint server; - var msg = GetExpiryMessage(key, flags, expiry, out server); - return ExecuteAsync(msg, ResultProcessor.Boolean, server: server); - } - - public Task KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None) - { - ServerEndPoint server; - var msg = GetExpiryMessage(key, flags, expiry, out server); - return ExecuteAsync(msg, ResultProcessor.Boolean, server: server); - } - - public void KeyMigrate(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) - { - if (timeoutMilliseconds <= 0) timeoutMilliseconds = multiplexer.TimeoutMilliseconds; - var msg = new KeyMigrateCommandMessage(Database, key, toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - public Task KeyMigrateAsync(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) - { - if (timeoutMilliseconds <= 0) timeoutMilliseconds = multiplexer.TimeoutMilliseconds; - var msg = new KeyMigrateCommandMessage(Database, key, toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - sealed class KeyMigrateCommandMessage : Message.CommandKeyBase // MIGRATE is atypical - { - private MigrateOptions migrateOptions; - private int timeoutMilliseconds; - private int toDatabase; - RedisValue toHost, toPort; - - public KeyMigrateCommandMessage(int db, RedisKey key, EndPoint toServer, int toDatabase, int timeoutMilliseconds, MigrateOptions migrateOptions, CommandFlags flags) - : base(db, flags, RedisCommand.MIGRATE, key) - { - if (toServer == null) throw new ArgumentNullException(nameof(toServer)); - string toHost; - int toPort; - if (!Format.TryGetHostPort(toServer, out toHost, out toPort)) throw new ArgumentException("toServer"); - this.toHost = toHost; - this.toPort = toPort; - if (toDatabase < 0) throw new ArgumentOutOfRangeException(nameof(toDatabase)); - this.toDatabase = toDatabase; - this.timeoutMilliseconds = timeoutMilliseconds; - this.migrateOptions = migrateOptions; - } - - internal override void WriteImpl(PhysicalConnection physical) - { - bool isCopy = (migrateOptions & MigrateOptions.Copy) != 0; - bool isReplace = (migrateOptions & MigrateOptions.Replace) != 0; - physical.WriteHeader(Command, 5 + (isCopy ? 1 : 0) + (isReplace ? 1 : 0)); - physical.Write(toHost); - physical.Write(toPort); - physical.Write(Key); - physical.Write(toDatabase); - physical.Write(timeoutMilliseconds); - if (isCopy) physical.Write(RedisLiterals.COPY); - if (isReplace) physical.Write(RedisLiterals.REPLACE); - } - } - - public bool KeyMove(RedisKey key, int database, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.MOVE, key, database); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public Task KeyMoveAsync(RedisKey key, int database, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.MOVE, key, database); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public bool KeyPersist(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.PERSIST, key); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public Task KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.PERSIST, key); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public RedisKey KeyRandom(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.RANDOMKEY); - return ExecuteSync(msg, ResultProcessor.RedisKey); - } - - public Task KeyRandomAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.RANDOMKEY); - return ExecuteAsync(msg, ResultProcessor.RedisKey); - } - - public bool KeyRename(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - WhenAlwaysOrNotExists(when); - var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RENAME : RedisCommand.RENAMENX, key, newKey); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public Task KeyRenameAsync(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - WhenAlwaysOrNotExists(when); - var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RENAME : RedisCommand.RENAMENX, key, newKey); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public void KeyRestore(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None) - { - var msg = GetRestoreMessage(key, value, expiry, flags); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - - public Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None) - { - var msg = GetRestoreMessage(key, value, expiry, flags); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public TimeSpan? KeyTimeToLive(RedisKey key, CommandFlags flags = CommandFlags.None) - { - ServerEndPoint server; - var features = GetFeatures(Database, key, flags, out server); - Message msg; - if (server != null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(RedisCommand.PTTL)) - { - msg = Message.Create(Database, flags, RedisCommand.PTTL, key); - return ExecuteSync(msg, ResultProcessor.TimeSpanFromMilliseconds, server); - } - msg = Message.Create(Database, flags, RedisCommand.TTL, key); - return ExecuteSync(msg, ResultProcessor.TimeSpanFromSeconds); - } - - public Task KeyTimeToLiveAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - ServerEndPoint server; - var features = GetFeatures(Database, key, flags, out server); - Message msg; - if (server != null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(RedisCommand.PTTL)) - { - msg = Message.Create(Database, flags, RedisCommand.PTTL, key); - return ExecuteAsync(msg, ResultProcessor.TimeSpanFromMilliseconds, server); - } - msg = Message.Create(Database, flags, RedisCommand.TTL, key); - return ExecuteAsync(msg, ResultProcessor.TimeSpanFromSeconds); - - } - - public RedisType KeyType(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.TYPE, key); - return ExecuteSync(msg, ResultProcessor.RedisType); - } - - public Task KeyTypeAsync(RedisKey key, CommandFlags flags) - { - var msg = Message.Create(Database, flags, RedisCommand.TYPE, key); - return ExecuteAsync(msg, ResultProcessor.RedisType); - } - - public RedisValue ListGetByIndex(RedisKey key, long index, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LINDEX, key, index); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public Task ListGetByIndexAsync(RedisKey key, long index, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LINDEX, key, index); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public long ListInsertAfter(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.AFTER, pivot, value); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task ListInsertAfterAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.AFTER, pivot, value); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public long ListInsertBefore(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.BEFORE, pivot, value); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task ListInsertBeforeAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.BEFORE, pivot, value); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public RedisValue ListLeftPop(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LPOP, key); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public Task ListLeftPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LPOP, key); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public long ListLeftPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - WhenAlwaysOrExists(when); - var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX, key, value); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public long ListLeftPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - if (values == null) throw new ArgumentNullException(nameof(values)); - var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, RedisCommand.LPUSH, key, values); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task ListLeftPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - WhenAlwaysOrExists(when); - var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX, key, value); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public Task ListLeftPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - if (values == null) throw new ArgumentNullException(nameof(values)); - var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, RedisCommand.LPUSH, key, values); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LLEN, key); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LLEN, key); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LRANGE, key, start, stop); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public Task ListRangeAsync(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LRANGE, key, start, stop); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - - public long ListRemove(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LREM, key, count, value); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task ListRemoveAsync(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LREM, key, count, value); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public RedisValue ListRightPop(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.RPOP, key); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public Task ListRightPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.RPOP, key); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public RedisValue ListRightPopLeftPush(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.RPOPLPUSH, source, destination); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public Task ListRightPopLeftPushAsync(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.RPOPLPUSH, source, destination); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public long ListRightPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - WhenAlwaysOrExists(when); - var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX, key, value); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public long ListRightPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - if (values == null) throw new ArgumentNullException(nameof(values)); - var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, RedisCommand.RPUSH, key, values); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task ListRightPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - WhenAlwaysOrExists(when); - var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX, key, value); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public Task ListRightPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - if (values == null) throw new ArgumentNullException(nameof(values)); - var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, RedisCommand.RPUSH, key, values); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public void ListSetByIndex(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LSET, key, index, value); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - - public Task ListSetByIndexAsync(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LSET, key, index, value); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public void ListTrim(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LTRIM, key, start, stop); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - - public Task ListTrimAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.LTRIM, key, start, stop); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public bool LockExtend(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) - { - if (value.IsNull) throw new ArgumentNullException(nameof(value)); - var tran = GetLockExtendTransaction(key, value, expiry); - - if (tran != null) return tran.Execute(flags); - - // without transactions (twemproxy etc), we can't enforce the "value" part - return KeyExpire(key, expiry, flags); - } - - public Task LockExtendAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) - { - if (value.IsNull) throw new ArgumentNullException(nameof(value)); - var tran = GetLockExtendTransaction(key, value, expiry); - if (tran != null) return tran.ExecuteAsync(flags); - - // without transactions (twemproxy etc), we can't enforce the "value" part - return KeyExpireAsync(key, expiry, flags); - } - - public RedisValue LockQuery(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return StringGet(key, flags); - } - - public Task LockQueryAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - return StringGetAsync(key, flags); - } - - public bool LockRelease(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - if (value.IsNull) throw new ArgumentNullException(nameof(value)); - var tran = GetLockReleaseTransaction(key, value); - if (tran != null) return tran.Execute(flags); - - // without transactions (twemproxy etc), we can't enforce the "value" part - return KeyDelete(key, flags); - } - - public Task LockReleaseAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - if (value.IsNull) throw new ArgumentNullException(nameof(value)); - var tran = GetLockReleaseTransaction(key, value); - if (tran != null) return tran.ExecuteAsync(flags); - - // without transactions (twemproxy etc), we can't enforce the "value" part - return KeyDeleteAsync(key, flags); - } - - public bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) - { - if (value.IsNull) throw new ArgumentNullException(nameof(value)); - return StringSet(key, value, expiry, When.NotExists, flags); - } - - public Task LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) - { - if (value.IsNull) throw new ArgumentNullException(nameof(value)); - return StringSetAsync(key, value, expiry, When.NotExists, flags); - } - - public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) - { - if (channel.IsNullOrEmpty) throw new ArgumentNullException(nameof(channel)); - var msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, message); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) - { - if (channel.IsNullOrEmpty) throw new ArgumentNullException(nameof(channel)); - var msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, message); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - public RedisResult ScriptEvaluate(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) - { - var msg = new ScriptEvalMessage(Database, flags, script, keys, values); - try - { - return ExecuteSync(msg, ResultProcessor.ScriptResult); - - } - catch (RedisServerException) - { - // could be a NOSCRIPT; for a sync call, we can re-issue that without problem - if (msg.IsScriptUnavailable) return ExecuteSync(msg, ResultProcessor.ScriptResult); - throw; - } - } - public RedisResult Execute(string command, params object[] args) - => Execute(command, args, CommandFlags.None); - public RedisResult Execute(string command, ICollection args, CommandFlags flags = CommandFlags.None) - { - var msg = new ExecuteMessage(Database, flags, command, args); - return ExecuteSync(msg, ResultProcessor.ScriptResult); - } - public Task ExecuteAsync(string command, params object[] args) - => ExecuteAsync(command, args, CommandFlags.None); - public Task ExecuteAsync(string command, ICollection args, CommandFlags flags = CommandFlags.None) - { - var msg = new ExecuteMessage(Database, flags, command, args); - return ExecuteAsync(msg, ResultProcessor.ScriptResult); - } - public RedisResult ScriptEvaluate(byte[] hash, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) - { - var msg = new ScriptEvalMessage(Database, flags, hash, keys, values); - return ExecuteSync(msg, ResultProcessor.ScriptResult); - } - - public RedisResult ScriptEvaluate(LuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None) - { - return script.Evaluate(this, parameters, null, flags); - } - public RedisResult ScriptEvaluate(LoadedLuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None) - { - return script.Evaluate(this, parameters, null, flags); - } - - - public Task ScriptEvaluateAsync(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) - { - var msg = new ScriptEvalMessage(Database, flags, script, keys, values); - return ExecuteAsync(msg, ResultProcessor.ScriptResult); - } - public Task ScriptEvaluateAsync(byte[] hash, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) - { - var msg = new ScriptEvalMessage(Database, flags, hash, keys, values); - return ExecuteAsync(msg, ResultProcessor.ScriptResult); - } - - public Task ScriptEvaluateAsync(LuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None) - { - return script.EvaluateAsync(this, parameters, null, flags); - } - public Task ScriptEvaluateAsync(LoadedLuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None) - { - return script.EvaluateAsync(this, parameters, null, flags); - } - public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SADD, key, value); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SADD, key, values); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SetAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SADD, key, value); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public Task SetAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SADD, key, values); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public RedisValue[] SetCombine(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), first, second); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public RedisValue[] SetCombine(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), keys); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, first, second); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, keys); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, first, second); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, keys); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public Task SetCombineAsync(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), first, second); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - - public Task SetCombineAsync(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), keys); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - - public bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SISMEMBER, key, value); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public Task SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SISMEMBER, key, value); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - - public long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SCARD, key); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SCARD, key); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public RedisValue[] SetMembers(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SMEMBERS, key); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public Task SetMembersAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SMEMBERS, key); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - - public bool SetMove(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SMOVE, source, destination, value); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public Task SetMoveAsync(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SMOVE, source, destination, value); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public RedisValue SetPop(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SPOP, key); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public Task SetPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SPOP, key); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public RedisValue SetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public Task SetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public RedisValue[] SetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key, count); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public Task SetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key, count); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - - public bool SetRemove(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SREM, key, value); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public long SetRemove(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SREM, key, values); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SetRemoveAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SREM, key, value); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public Task SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SREM, key, values); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - IEnumerable IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) - { - return SetScan(key, pattern, pageSize, CursorUtils.Origin, 0, flags); - } - - public IEnumerable SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = CursorUtils.DefaultPageSize, long cursor = CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) - { - var scan = TryScan(key, pattern, pageSize, cursor, pageOffset, flags, RedisCommand.SSCAN, SetScanResultProcessor.Default); - if (scan != null) return scan; - - if (cursor != 0 || pageOffset != 0) throw ExceptionFactory.NoCursor(RedisCommand.SMEMBERS); - if (pattern.IsNull) return SetMembers(key, flags); - throw ExceptionFactory.NotSupported(true, RedisCommand.SSCAN); - } - public RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetAddMessage(default(RedisKey), key, skip, take, order, sortType, by, get, flags); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public long SortAndStore(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetAddMessage(destination, key, skip, take, order, sortType, by, get, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SortAndStoreAsync(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetAddMessage(destination, key, skip, take, order, sortType, by, get, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public Task SortAsync(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetAddMessage(default(RedisKey), key, skip, take, order, sortType, by, get, flags); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - public bool SortedSetAdd(RedisKey key, RedisValue member, double score, CommandFlags flags) - { - var msg = GetSortedSetAddMessage(key, member, score, When.Always, flags); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - public bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetAddMessage(key, member, score, when, flags); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, CommandFlags flags) - { - var msg = GetSortedSetAddMessage(key, values, When.Always, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetAddMessage(key, values, when, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, CommandFlags flags) - { - var msg = GetSortedSetAddMessage(key, member, score, When.Always, flags); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetAddMessage(key, member, score, when, flags); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, CommandFlags flags) - { - var msg = GetSortedSetAddMessage(key, values, When.Always, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetAddMessage(key, values, when, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetCombineAndStoreCommandMessage(operation, destination, new[] { first, second }, null, aggregate, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetCombineAndStoreCommandMessage(operation, destination, keys, weights, aggregate, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetCombineAndStoreCommandMessage(operation, destination, new[] { first, second }, null, aggregate, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetCombineAndStoreCommandMessage(operation, destination, keys, weights, aggregate, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public double SortedSetDecrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) - { - return SortedSetIncrement(key, member, -value, flags); - } - - public Task SortedSetDecrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) - { - return SortedSetIncrementAsync(key, member, -value, flags); - } - - public double SortedSetIncrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.ZINCRBY, key, value, member); - return ExecuteSync(msg, ResultProcessor.Double); - } - - public Task SortedSetIncrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.ZINCRBY, key, value, member); - return ExecuteAsync(msg, ResultProcessor.Double); - } - - public long SortedSetLength(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetLengthMessage(key, min, max, exclude, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SortedSetLengthAsync(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetLengthMessage(key, min, max, exclude, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public Task SortedSetRangeByRankAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - - public SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop, RedisLiterals.WITHSCORES); - return ExecuteSync(msg, ResultProcessor.SortedSetWithScores); - } - - public Task SortedSetRangeByRankWithScoresAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop, RedisLiterals.WITHSCORES); - return ExecuteAsync(msg, ResultProcessor.SortedSetWithScores); - } - - public RedisValue[] SortedSetRangeByScore(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetRangeByScoreMessage(key, start, stop, exclude, order, skip, take, flags, false); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public Task SortedSetRangeByScoreAsync(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetRangeByScoreMessage(key, start, stop, exclude, order, skip, take, flags, false); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - - public SortedSetEntry[] SortedSetRangeByScoreWithScores(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetRangeByScoreMessage(key, start, stop, exclude, order, skip, take, flags, true); - return ExecuteSync(msg, ResultProcessor.SortedSetWithScores); - } - - public Task SortedSetRangeByScoreWithScoresAsync(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetRangeByScoreMessage(key, start, stop, exclude, order, skip, take, flags, true); - return ExecuteAsync(msg, ResultProcessor.SortedSetWithScores); - } - - public long? SortedSetRank(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANK : RedisCommand.ZRANK, key, member); - return ExecuteSync(msg, ResultProcessor.NullableInt64); - } - - public Task SortedSetRankAsync(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANK : RedisCommand.ZRANK, key, member); - return ExecuteAsync(msg, ResultProcessor.NullableInt64); - } - - public bool SortedSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, member); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public long SortedSetRemove(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, members); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SortedSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, member); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public Task SortedSetRemoveAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, members); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public long SortedSetRemoveRangeByRank(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.ZREMRANGEBYRANK, key, start, stop); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SortedSetRemoveRangeByRankAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.ZREMRANGEBYRANK, key, start, stop); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public long SortedSetRemoveRangeByScore(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetRemoveRangeByScoreMessage(key, start, stop, exclude, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - var msg = GetSortedSetRemoveRangeByScoreMessage(key, start, stop, exclude, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - IEnumerable IDatabase.SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) - { - return SortedSetScan(key, pattern, pageSize, CursorUtils.Origin, 0, flags); - } - - public IEnumerable SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = CursorUtils.DefaultPageSize, long cursor = CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) - { - var scan = TryScan(key, pattern, pageSize, cursor, pageOffset, flags, RedisCommand.ZSCAN, SortedSetScanResultProcessor.Default); - if (scan != null) return scan; - - if (cursor != 0 || pageOffset != 0) throw ExceptionFactory.NoCursor(RedisCommand.ZRANGE); - if (pattern.IsNull) return SortedSetRangeByRankWithScores(key, flags: flags); - throw ExceptionFactory.NotSupported(true, RedisCommand.ZSCAN); - } - - public double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.ZSCORE, key, member); - return ExecuteSync(msg, ResultProcessor.NullableDouble); - } - - public Task SortedSetScoreAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.ZSCORE, key, member); - return ExecuteAsync(msg, ResultProcessor.NullableDouble); - } - - public long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.APPEND, key, value); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task StringAppendAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.APPEND, key, value); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public long StringBitCount(RedisKey key, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task StringBitCountAsync(RedisKey key, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - var msg = GetStringBitOperationMessage(operation, destination, first, second, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - var msg = GetStringBitOperationMessage(operation, destination, keys, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) - { - var msg = GetStringBitOperationMessage(operation, destination, first, second, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - var msg = GetStringBitOperationMessage(operation, destination, keys, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public long StringBitPosition(RedisKey key, bool value, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.BITPOS, key, value, start, end); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task StringBitPositionAsync(RedisKey key, bool value, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.BITPOS, key, value, start, end); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public long StringDecrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) - { - return StringIncrement(key, -value, flags); - } - - public double StringDecrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) - { - return StringIncrement(key, -value, flags); - } - - public Task StringDecrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) - { - return StringIncrementAsync(key, -value, flags); - } - - public Task StringDecrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) - { - return StringIncrementAsync(key, -value, flags); - } - - public RedisValue StringGet(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GET, key); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public RedisValue[] StringGet(RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - if (keys == null) throw new ArgumentNullException(nameof(keys)); - if (keys.Length == 0) return new RedisValue[0]; - var msg = Message.Create(Database, flags, RedisCommand.MGET, keys); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public Task StringGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GET, key); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public Task StringGetAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) - { - if (keys == null) throw new ArgumentNullException(nameof(keys)); - if (keys.Length == 0) return CompletedTask.FromResult(new RedisValue[0], asyncState); - var msg = Message.Create(Database, flags, RedisCommand.MGET, keys); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - - public bool StringGetBit(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GETBIT, key, offset); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public Task StringGetBitAsync(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GETBIT, key, offset); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public RedisValue StringGetRange(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GETRANGE, key, start, end); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public Task StringGetRangeAsync(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GETRANGE, key, start, end); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public RedisValue StringGetSet(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GETSET, key, value); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public Task StringGetSetAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.GETSET, key, value); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public RedisValueWithExpiry StringGetWithExpiry(RedisKey key, CommandFlags flags = CommandFlags.None) - { - ServerEndPoint server; - ResultProcessor processor; - var msg = GetStringGetWithExpiryMessage(key, flags, out processor, out server); - return ExecuteSync(msg, processor, server); - } - - public Task StringGetWithExpiryAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - ServerEndPoint server; - ResultProcessor processor; - var msg = GetStringGetWithExpiryMessage(key, flags, out processor, out server); - return ExecuteAsync(msg, processor, server); - } - - public long StringIncrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) - { - var msg = IncrMessage(key, value, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public double StringIncrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) - { - var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 - ? null : Message.Create(Database, flags, RedisCommand.INCRBYFLOAT, key, value); - return ExecuteSync(msg, ResultProcessor.Double); - } - - public Task StringIncrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) - { - var msg = IncrMessage(key, value, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public Task StringIncrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) - { - var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 - ? null : Message.Create(Database, flags, RedisCommand.INCRBYFLOAT, key, value); - return ExecuteAsync(msg, ResultProcessor.Double); - } - - public long StringLength(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.STRLEN, key); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.STRLEN, key); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - var msg = GetStringSetMessage(key, value, expiry, when, flags); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public bool StringSet(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - var msg = GetStringSetMessage(values, when, flags); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - var msg = GetStringSetMessage(key, value, expiry, when, flags); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public Task StringSetAsync(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - var msg = GetStringSetMessage(values, when, flags); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public bool StringSetBit(RedisKey key, long offset, bool value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SETBIT, key, offset, value); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public Task StringSetBitAsync(RedisKey key, long offset, bool value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SETBIT, key, offset, value); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SETRANGE, key, offset, value); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public Task StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(Database, flags, RedisCommand.SETRANGE, key, offset, value); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - Message GetExpiryMessage(RedisKey key, CommandFlags flags, TimeSpan? expiry, out ServerEndPoint server) - { - TimeSpan duration; - if (expiry == null || (duration = expiry.Value) == TimeSpan.MaxValue) - { - server = null; - return Message.Create(Database, flags, RedisCommand.PERSIST, key); - } - long milliseconds = duration.Ticks / TimeSpan.TicksPerMillisecond; - if ((milliseconds % 1000) != 0) - { - var features = GetFeatures(Database, key, flags, out server); - if (server != null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(RedisCommand.PEXPIRE)) - { - return Message.Create(Database, flags, RedisCommand.PEXPIRE, key, milliseconds); - } - } - server = null; - long seconds = milliseconds / 1000; - return Message.Create(Database, flags, RedisCommand.EXPIRE, key, seconds); - } - - Message GetExpiryMessage(RedisKey key, CommandFlags flags, DateTime? expiry, out ServerEndPoint server) - { - DateTime when; - if (expiry == null || (when = expiry.Value) == DateTime.MaxValue) - { - server = null; - return Message.Create(Database, flags, RedisCommand.PERSIST, key); - } - switch (when.Kind) - { - case DateTimeKind.Local: - case DateTimeKind.Utc: - break; // fine, we can work with that - default: - throw new ArgumentException("Expiry time must be either Utc or Local", nameof(expiry)); - } - long milliseconds = (when.ToUniversalTime() - RedisBase.UnixEpoch).Ticks / TimeSpan.TicksPerMillisecond; - - if ((milliseconds % 1000) != 0) - { - var features = GetFeatures(Database, key, flags, out server); - if (server != null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(RedisCommand.PEXPIREAT)) - { - return Message.Create(Database, flags, RedisCommand.PEXPIREAT, key, milliseconds); - } - } - server = null; - long seconds = milliseconds / 1000; - return Message.Create(Database, flags, RedisCommand.EXPIREAT, key, seconds); - } - - private Message GetHashSetMessage(RedisKey key, HashEntry[] hashFields, CommandFlags flags) - { - if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - switch (hashFields.Length) - { - case 0: return null; - case 1: - return Message.Create(Database, flags, RedisCommand.HMSET, key, - hashFields[0].name, hashFields[0].value); - case 2: - return Message.Create(Database, flags, RedisCommand.HMSET, key, - hashFields[0].name, hashFields[0].value, - hashFields[1].name, hashFields[1].value); - default: - var arr = new RedisValue[hashFields.Length * 2]; - int offset = 0; - for (int i = 0; i < hashFields.Length; i++) - { - arr[offset++] = hashFields[i].name; - arr[offset++] = hashFields[i].value; - } - return Message.Create(Database, flags, RedisCommand.HMSET, key, arr); - } - } - - ITransaction GetLockExtendTransaction(RedisKey key, RedisValue value, TimeSpan expiry) - { - var tran = CreateTransactionIfAvailable(asyncState); - if (tran != null) - { - tran.AddCondition(Condition.StringEqual(key, value)); - tran.KeyExpireAsync(key, expiry, CommandFlags.FireAndForget); - } - return tran; - } - - ITransaction GetLockReleaseTransaction(RedisKey key, RedisValue value) - { - var tran = CreateTransactionIfAvailable(asyncState); - if (tran != null) - { - tran.AddCondition(Condition.StringEqual(key, value)); - tran.KeyDeleteAsync(key, CommandFlags.FireAndForget); - } - return tran; - } - - private RedisValue GetLexRange(RedisValue value, Exclude exclude, bool isStart) - { - if (value.IsNull) - { - return isStart ? RedisLiterals.MinusSymbol : RedisLiterals.PlusSumbol; - } - byte[] orig = value; - - byte[] result = new byte[orig.Length + 1]; - // no defaults here; must always explicitly specify [ / ( - result[0] = (exclude & (isStart ? Exclude.Start : Exclude.Stop)) == 0 ? (byte)'[' : (byte)'('; - Buffer.BlockCopy(orig, 0, result, 1, orig.Length); - return result; - } - private RedisValue GetRange(double value, Exclude exclude, bool isStart) - { - if (isStart) - { - if ((exclude & Exclude.Start) == 0) return value; // inclusive is default - } - else - { - if ((exclude & Exclude.Stop) == 0) return value; // inclusive is default - } - return "(" + Format.ToString(value); // '(' prefix means exclusive - } - - Message GetRestoreMessage(RedisKey key, byte[] value, TimeSpan? expiry, CommandFlags flags) - { - long pttl = (expiry == null || expiry.Value == TimeSpan.MaxValue) ? 0 : (expiry.Value.Ticks / TimeSpan.TicksPerMillisecond); - return Message.Create(Database, flags, RedisCommand.RESTORE, key, pttl, value); - } - - private Message GetSortedSetAddMessage(RedisKey key, RedisValue member, double score, When when, CommandFlags flags) - { - WhenAlwaysOrExistsOrNotExists(when); - switch (when) - { - case When.Always: return Message.Create(Database, flags, RedisCommand.ZADD, key, score, member); - case When.NotExists: return Message.Create(Database, flags, RedisCommand.ZADD, key, RedisLiterals.NX, score, member); - case When.Exists: return Message.Create(Database, flags, RedisCommand.ZADD, key, RedisLiterals.XX, score, member); - default: throw new ArgumentOutOfRangeException(nameof(when)); - } - } - - private Message GetSortedSetAddMessage(RedisKey key, SortedSetEntry[] values, When when, CommandFlags flags) - { - WhenAlwaysOrExistsOrNotExists(when); - if (values == null) throw new ArgumentNullException(nameof(values)); - switch (values.Length) - { - case 0: return null; - case 1: - return GetSortedSetAddMessage(key, values[0].element, values[0].score, when, flags); - default: - RedisValue[] arr; - int index = 0; - switch (when) - { - case When.Always: - arr = new RedisValue[values.Length * 2]; - break; - case When.NotExists: - arr = new RedisValue[values.Length * 2 + 1]; - arr[index++] = RedisLiterals.NX; - break; - case When.Exists: - arr = new RedisValue[values.Length * 2 + 1]; - arr[index++] = RedisLiterals.XX; - break; - default: throw new ArgumentOutOfRangeException(nameof(when)); - } - for (int i = 0; i < values.Length; i++) - { - arr[index++] = values[i].score; - arr[index++] = values[i].element; - } - return Message.Create(Database, flags, RedisCommand.ZADD, key, arr); - } - } - - private Message GetSortedSetAddMessage(RedisKey destination, RedisKey key, long skip, long take, Order order, SortType sortType, RedisValue by, RedisValue[] get, CommandFlags flags) - { - // most common cases; no "get", no "by", no "destination", no "skip", no "take" - if (destination.IsNull && skip == 0 && take == -1 && by.IsNull && (get == null || get.Length == 0)) - { - switch (order) - { - case Order.Ascending: - switch (sortType) - { - case SortType.Numeric: return Message.Create(Database, flags, RedisCommand.SORT, key); - case SortType.Alphabetic: return Message.Create(Database, flags, RedisCommand.SORT, key, RedisLiterals.ALPHA); - } - break; - case Order.Descending: - switch (sortType) - { - case SortType.Numeric: return Message.Create(Database, flags, RedisCommand.SORT, key, RedisLiterals.DESC); - case SortType.Alphabetic: return Message.Create(Database, flags, RedisCommand.SORT, key, RedisLiterals.DESC, RedisLiterals.ALPHA); - } - break; - } - } - - // and now: more complicated scenarios... - List values = new List(); - if (!by.IsNull) - { - values.Add(RedisLiterals.BY); - values.Add(by); - } - if (skip != 0 || take != -1)// these are our defaults that mean "everything"; anything else needs to be sent explicitly - { - values.Add(RedisLiterals.LIMIT); - values.Add(skip); - values.Add(take); - } - switch (order) - { - case Order.Ascending: - break; // default - case Order.Descending: - values.Add(RedisLiterals.DESC); - break; - default: - throw new ArgumentOutOfRangeException(nameof(order)); - } - switch (sortType) - { - case SortType.Numeric: - break; // default - case SortType.Alphabetic: - values.Add(RedisLiterals.ALPHA); - break; - default: - throw new ArgumentOutOfRangeException(nameof(sortType)); - } - if (get != null && get.Length != 0) - { - foreach (var item in get) - { - values.Add(RedisLiterals.GET); - values.Add(item); - } - } - if (destination.IsNull) return Message.Create(Database, flags, RedisCommand.SORT, key, values.ToArray()); - - // because we are using STORE, we need to push this to a master - if (Message.GetMasterSlaveFlags(flags) == CommandFlags.DemandSlave) - { - throw ExceptionFactory.MasterOnly(multiplexer.IncludeDetailInExceptions, RedisCommand.SORT, null, null); - } - flags = Message.SetMasterSlaveFlags(flags, CommandFlags.DemandMaster); - values.Add(RedisLiterals.STORE); - return Message.Create(Database, flags, RedisCommand.SORT, key, values.ToArray(), destination); - } - - private Message GetSortedSetCombineAndStoreCommandMessage(SetOperation operation, RedisKey destination, RedisKey[] keys, double[] weights, Aggregate aggregate, CommandFlags flags) - { - RedisCommand command; - switch (operation) - { - case SetOperation.Intersect: command = RedisCommand.ZINTERSTORE; break; - case SetOperation.Union: command = RedisCommand.ZUNIONSTORE; break; - default: throw new ArgumentOutOfRangeException(nameof(operation)); - } - if (keys == null) throw new ArgumentNullException(nameof(keys)); - - List values = null; - if (weights != null && weights.Length != 0) - { - (values ?? (values = new List())).Add(RedisLiterals.WEIGHTS); - foreach (var weight in weights) - values.Add(weight); - } - switch (aggregate) - { - case Aggregate.Sum: break; // default - case Aggregate.Min: - (values ?? (values = new List())).Add(RedisLiterals.AGGREGATE); - values.Add(RedisLiterals.MIN); - break; - case Aggregate.Max: - (values ?? (values = new List())).Add(RedisLiterals.AGGREGATE); - values.Add(RedisLiterals.MAX); - break; - default: - throw new ArgumentOutOfRangeException(nameof(aggregate)); - } - return new SortedSetCombineAndStoreCommandMessage(Database, flags, command, destination, keys, values?.ToArray() ?? RedisValue.EmptyArray); - - } - - private Message GetSortedSetLengthMessage(RedisKey key, double min, double max, Exclude exclude, CommandFlags flags) - { - if (double.IsNegativeInfinity(min) && double.IsPositiveInfinity(max)) - return Message.Create(Database, flags, RedisCommand.ZCARD, key); - - var from = GetRange(min, exclude, true); - var to = GetRange(max, exclude, false); - return Message.Create(Database, flags, RedisCommand.ZCOUNT, key, from, to); - } - - private Message GetSortedSetRangeByScoreMessage(RedisKey key, double start, double stop, Exclude exclude, Order order, long skip, long take, CommandFlags flags, bool withScores) - { - // usage: {ZRANGEBYSCORE|ZREVRANGEBYSCORE} key from to [WITHSCORES] [LIMIT offset count] - // there's basically only 4 layouts; with/without each of scores/limit - var command = order == Order.Descending ? RedisCommand.ZREVRANGEBYSCORE : RedisCommand.ZRANGEBYSCORE; - bool unlimited = skip == 0 && take == -1; // these are our defaults that mean "everything"; anything else needs to be sent explicitly - - bool reverseLimits = (order == Order.Ascending) == (start > stop); - if (reverseLimits) - { - var tmp = start; - start = stop; - stop = tmp; - switch (exclude) - { - case Exclude.Start: exclude = Exclude.Stop; break; - case Exclude.Stop: exclude = Exclude.Start; break; - } - } - - RedisValue from = GetRange(start, exclude, true), to = GetRange(stop, exclude, false); - if (withScores) - { - return unlimited ? Message.Create(Database, flags, command, key, from, to, RedisLiterals.WITHSCORES) - : Message.Create(Database, flags, command, key, new[] { from, to, RedisLiterals.WITHSCORES, RedisLiterals.LIMIT, skip, take }); - } - else - { - return unlimited ? Message.Create(Database, flags, command, key, from, to) - : Message.Create(Database, flags, command, key, new[] { from, to, RedisLiterals.LIMIT, skip, take }); - } - } - - private Message GetSortedSetRemoveRangeByScoreMessage(RedisKey key, double start, double stop, Exclude exclude, CommandFlags flags) - { - return Message.Create(Database, flags, RedisCommand.ZREMRANGEBYSCORE, key, - GetRange(start, exclude, true), GetRange(stop, exclude, false)); - } - - private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags) - { - if (keys == null) throw new ArgumentNullException(nameof(keys)); - if (keys.Length == 0) return null; - - // these ones are too bespoke to warrant custom Message implementations - var serverSelectionStrategy = multiplexer.ServerSelectionStrategy; - int slot = serverSelectionStrategy.HashSlot(destination); - var values = new RedisValue[keys.Length + 2]; - values[0] = RedisLiterals.Get(operation); - values[1] = destination.AsRedisValue(); - for (int i = 0; i < keys.Length; i++) - { - values[i + 2] = keys[i].AsRedisValue(); - slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); - } - return Message.CreateInSlot(Database, slot, flags, RedisCommand.BITOP, values); - } - - private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags) - { - // these ones are too bespoke to warrant custom Message implementations - var op = RedisLiterals.Get(operation); - var serverSelectionStrategy = multiplexer.ServerSelectionStrategy; - int slot = serverSelectionStrategy.HashSlot(destination); - slot = serverSelectionStrategy.CombineSlot(slot, first); - if (second.IsNull || operation == Bitwise.Not) - { // unary - return Message.CreateInSlot(Database, slot, flags, RedisCommand.BITOP, new[] { op, destination.AsRedisValue(), first.AsRedisValue() }); - } - // binary - slot = serverSelectionStrategy.CombineSlot(slot, second); - return Message.CreateInSlot(Database, slot, flags, RedisCommand.BITOP, new[] { op, destination.AsRedisValue(), first.AsRedisValue(), second.AsRedisValue() }); - } - - Message GetStringGetWithExpiryMessage(RedisKey key, CommandFlags flags, out ResultProcessor processor, out ServerEndPoint server) - { - if (this is IBatch) - { - throw new NotSupportedException("This operation is not possible inside a transaction or batch; please issue separate GetString and KeyTimeToLive requests"); - } - var features = GetFeatures(Database, key, flags, out server); - processor = StringGetWithExpiryProcessor.Default; - if (server != null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(RedisCommand.PTTL)) - { - return new StringGetWithExpiryMessage(Database, flags, RedisCommand.PTTL, key); - } - // if we use TTL, it doesn't matter which server - server = null; - return new StringGetWithExpiryMessage(Database, flags, RedisCommand.TTL, key); - } - - private Message GetStringSetMessage(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - if (values == null) throw new ArgumentNullException(nameof(values)); - switch (values.Length) - { - case 0: return null; - case 1: return GetStringSetMessage(values[0].Key, values[0].Value, null, when, flags); - default: - WhenAlwaysOrNotExists(when); - int slot = ServerSelectionStrategy.NoSlot, offset = 0; - var args = new RedisValue[values.Length * 2]; - var serverSelectionStrategy = multiplexer.ServerSelectionStrategy; - for (int i = 0; i < values.Length; i++) - { - args[offset++] = values[i].Key.AsRedisValue(); - args[offset++] = values[i].Value; - slot = serverSelectionStrategy.CombineSlot(slot, values[i].Key); - } - return Message.CreateInSlot(Database, slot, flags, when == When.NotExists ? RedisCommand.MSETNX : RedisCommand.MSET, args); - } - } - - Message GetStringSetMessage(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None) - { - WhenAlwaysOrExistsOrNotExists(when); - if (value.IsNull) return Message.Create(Database, flags, RedisCommand.DEL, key); - - if (expiry == null || expiry.Value == TimeSpan.MaxValue) - { // no expiry - switch (when) - { - case When.Always: return Message.Create(Database, flags, RedisCommand.SET, key, value); - case When.NotExists: return Message.Create(Database, flags, RedisCommand.SETNX, key, value); - case When.Exists: return Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX); - } - } - long milliseconds = expiry.Value.Ticks / TimeSpan.TicksPerMillisecond; - - if ((milliseconds % 1000) == 0) - { - // a nice round number of seconds - long seconds = milliseconds / 1000; - switch (when) - { - case When.Always: return Message.Create(Database, flags, RedisCommand.SETEX, key, seconds, value); - case When.Exists: return Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.XX); - case When.NotExists: return Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.NX); - } - } - - switch (when) - { - case When.Always: return Message.Create(Database, flags, RedisCommand.PSETEX, key, milliseconds, value); - case When.Exists: return Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.XX); - case When.NotExists: return Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.NX); - } - throw new NotSupportedException(); - } - - Message IncrMessage(RedisKey key, long value, CommandFlags flags) - { - switch (value) - { - case 0: - if ((flags & CommandFlags.FireAndForget) != 0) return null; - return Message.Create(Database, flags, RedisCommand.INCRBY, key, value); - case 1: - return Message.Create(Database, flags, RedisCommand.INCR, key); - case -1: - return Message.Create(Database, flags, RedisCommand.DECR, key); - default: - return value > 0 - ? Message.Create(Database, flags, RedisCommand.INCRBY, key, value) - : Message.Create(Database, flags, RedisCommand.DECRBY, key, -value); - } - } - - private RedisCommand SetOperationCommand(SetOperation operation, bool store) - { - switch (operation) - { - case SetOperation.Difference: return store ? RedisCommand.SDIFFSTORE : RedisCommand.SDIFF; - case SetOperation.Intersect: return store ? RedisCommand.SINTERSTORE : RedisCommand.SINTER; - case SetOperation.Union: return store ? RedisCommand.SUNIONSTORE : RedisCommand.SUNION; - default: throw new ArgumentOutOfRangeException(nameof(operation)); - } - } - - private IEnumerable TryScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags, RedisCommand command, ResultProcessor.ScanResult> processor) - { - if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - if (!multiplexer.CommandMap.IsAvailable(command)) return null; - - ServerEndPoint server; - var features = GetFeatures(Database, key, flags, out server); - if (!features.Scan) return null; - - if (CursorUtils.IsNil(pattern)) pattern = (byte[])null; - return new ScanIterator(this, server, key, pattern, pageSize, cursor, pageOffset, flags, command, processor); - } - - private Message GetLexMessage(RedisCommand command, RedisKey key, RedisValue min, RedisValue max, Exclude exclude, long skip, long take, CommandFlags flags) - { - RedisValue start = GetLexRange(min, exclude, true), stop = GetLexRange(max, exclude, false); - - if (skip == 0 && take == -1) - return Message.Create(Database, flags, command, key, start, stop); - - return Message.Create(Database, flags, command, key, new[] { start, stop, RedisLiterals.LIMIT, skip, take }); - } - public long SortedSetLengthByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - var msg = GetLexMessage(RedisCommand.ZLEXCOUNT, key, min, max, exclude, 0, -1, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public RedisValue[] SortedSetRangeByValue(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue), Exclude exclude = Exclude.None, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) - { - var msg = GetLexMessage(RedisCommand.ZRANGEBYLEX, key, min, max, exclude, skip, take, flags); - return ExecuteSync(msg, ResultProcessor.RedisValueArray); - } - - public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - var msg = GetLexMessage(RedisCommand.ZREMRANGEBYLEX, key, min, max, exclude, 0, -1, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SortedSetLengthByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - var msg = GetLexMessage(RedisCommand.ZLEXCOUNT, key, min, max, exclude, 0, -1, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public Task SortedSetRangeByValueAsync(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue), Exclude exclude = Exclude.None, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) - { - var msg = GetLexMessage(RedisCommand.ZRANGEBYLEX, key, min, max, exclude, skip, take, flags); - return ExecuteAsync(msg, ResultProcessor.RedisValueArray); - } - - public Task SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) - { - var msg = GetLexMessage(RedisCommand.ZREMRANGEBYLEX, key, min, max, exclude, 0, -1, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - - internal class ScanIterator : CursorEnumerable - { - private readonly RedisKey key; - private readonly RedisValue pattern; - private readonly RedisCommand command; - - public ScanIterator(RedisDatabase database, ServerEndPoint server, RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags, - RedisCommand command, ResultProcessor processor) - : base(database, server, database.Database, pageSize, cursor, pageOffset, flags) - { - this.key = key; - this.pattern = pattern; - this.command = command; - this.Processor = processor; - } - protected override ResultProcessor.ScanResult> Processor { get; } - - protected override Message CreateMessage(long cursor) - { - if (CursorUtils.IsNil(pattern)) - { - if (pageSize == CursorUtils.DefaultPageSize) - { - return Message.Create(db, flags, command, key, cursor); - } - else - { - return Message.Create(db, flags, command, key, cursor, RedisLiterals.COUNT, pageSize); - } - } - else - { - if (pageSize == CursorUtils.DefaultPageSize) - { - return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern); - } - else - { - return Message.Create(db, flags, command, key, new RedisValue[] { cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize }); - } - } - } - } - - internal sealed class ScriptLoadMessage : Message - { - internal readonly string Script; - public ScriptLoadMessage(CommandFlags flags, string script) - : base(-1, flags, RedisCommand.SCRIPT) - { - if (script == null) throw new ArgumentNullException(nameof(script)); - this.Script = script; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 2); - physical.Write(RedisLiterals.LOAD); - physical.Write((RedisValue)Script); - } - } - sealed class HashScanResultProcessor : ScanResultProcessor - { - public static readonly ResultProcessor.ScanResult> Default = new HashScanResultProcessor(); - private HashScanResultProcessor() { } - protected override HashEntry[] Parse(RawResult result) - { - HashEntry[] pairs; - if (!HashEntryArray.TryParse(result, out pairs)) pairs = null; - return pairs; - } - } - - private abstract class ScanResultProcessor : ResultProcessor.ScanResult> - { - protected abstract T[] Parse(RawResult result); - - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.MultiBulk: - var arr = result.GetItems(); - long i64; - if (arr.Length == 2 && arr[1].Type == ResultType.MultiBulk && arr[0].TryGetInt64(out i64)) - { - var sscanResult = new ScanIterator.ScanResult(i64, Parse(arr[1])); - SetResult(message, sscanResult); - return true; - } - break; - } - return false; - } - } - private sealed class ExecuteMessage : Message - { - private readonly string _command; - private static readonly object[] NoArgs = new object[0]; - private readonly ICollection args; - public ExecuteMessage(int db, CommandFlags flags, string command, ICollection args) : base(db, flags, RedisCommand.UNKNOWN) - { - _command = command; - this.args = args ?? NoArgs; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(_command, args.Count); - foreach(object arg in args) - { - if (arg is RedisKey) - { - physical.Write((RedisKey)arg); - } - else if (arg is RedisChannel) - { - physical.Write((RedisChannel)arg); - } - else - { // recognises well-known types - physical.Write(RedisValue.Parse(arg)); - } - } - } - public override string CommandAndKey => _command; - - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - int slot = ServerSelectionStrategy.NoSlot; - foreach(object arg in args) - { - if(arg is RedisKey) - { - slot = serverSelectionStrategy.CombineSlot(slot, (RedisKey)arg); - } - } - return slot; - } - } - private sealed class ScriptEvalMessage : Message, IMultiMessage - { - private readonly RedisKey[] keys; - private readonly string script; - private readonly RedisValue[] values; - private byte[] asciiHash, hexHash; - public ScriptEvalMessage(int db, CommandFlags flags, string script, RedisKey[] keys, RedisValue[] values) - : this(db, flags, ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL, script, null, keys, values) - { - if (script == null) throw new ArgumentNullException(nameof(script)); - } - public ScriptEvalMessage(int db, CommandFlags flags, byte[] hash, RedisKey[] keys, RedisValue[] values) - : this(db, flags, RedisCommand.EVAL, null, hash, keys, values) - { - if (hash == null) throw new ArgumentNullException(nameof(hash)); - } - - private ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string script, byte[] hexHash, RedisKey[] keys, RedisValue[] values) - : base(db, flags, command) - { - this.script = script; - this.hexHash = hexHash; - - if (keys == null) keys = RedisKey.EmptyArray; - if (values == null) values = RedisValue.EmptyArray; - for (int i = 0; i < keys.Length; i++) - keys[i].AssertNotNull(); - this.keys = keys; - for (int i = 0; i < values.Length; i++) - values[i].AssertNotNull(); - this.values = values; - } - - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - int slot = ServerSelectionStrategy.NoSlot; - for (int i = 0; i < keys.Length; i++) - slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); - return slot; - } - - public IEnumerable GetMessages(PhysicalConnection connection) - { - if (script != null && connection.Multiplexer.CommandMap.IsAvailable(RedisCommand.SCRIPT) - && (Flags & CommandFlags.NoScriptCache) == 0) - { - // a script was provided (rather than a hash); check it is known and supported - asciiHash = connection.Bridge.ServerEndPoint.GetScriptHash(script, command); - - if (asciiHash == null) - { - var msg = new ScriptLoadMessage(Flags, script); - msg.SetInternalCall(); - msg.SetSource(ResultProcessor.ScriptLoad, null); - yield return msg; - } - } - yield return this; - } - - internal override void WriteImpl(PhysicalConnection physical) - { - if (hexHash != null) - { - physical.WriteHeader(RedisCommand.EVALSHA, 2 + keys.Length + values.Length); - physical.WriteAsHex(hexHash); - } - else if (asciiHash != null) - { - physical.WriteHeader(RedisCommand.EVALSHA, 2 + keys.Length + values.Length); - physical.Write((RedisValue)asciiHash); - } - else - { - physical.WriteHeader(RedisCommand.EVAL, 2 + keys.Length + values.Length); - physical.Write((RedisValue)script); - } - physical.Write(keys.Length); - for (int i = 0; i < keys.Length; i++) - physical.Write(keys[i]); - for (int i = 0; i < values.Length; i++) - physical.Write(values[i]); - } - } - - sealed class SetScanResultProcessor : ScanResultProcessor - { - public static readonly ResultProcessor.ScanResult> Default = new SetScanResultProcessor(); - private SetScanResultProcessor() { } - protected override RedisValue[] Parse(RawResult result) - { - return result.GetItemsAsValues(); - } - } - sealed class SortedSetCombineAndStoreCommandMessage : Message.CommandKeyBase // ZINTERSTORE and ZUNIONSTORE have a very unusual signature - { - private readonly RedisKey[] keys; - private readonly RedisValue[] values; - public SortedSetCombineAndStoreCommandMessage(int db, CommandFlags flags, RedisCommand command, RedisKey destination, RedisKey[] keys, RedisValue[] values) - : base(db, flags, command, destination) - { - for (int i = 0; i < keys.Length; i++) - keys[i].AssertNotNull(); - this.keys = keys; - for (int i = 0; i < values.Length; i++) - values[i].AssertNotNull(); - this.values = values; - } - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - int slot = base.GetHashSlot(serverSelectionStrategy); - for (int i = 0; i < keys.Length; i++) - slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); - return slot; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 2 + keys.Length + values.Length); - physical.Write(Key); - physical.Write(keys.Length); - for (int i = 0; i < keys.Length; i++) - physical.Write(keys[i]); - for (int i = 0; i < values.Length; i++) - physical.Write(values[i]); - } - } - - sealed class SortedSetScanResultProcessor : ScanResultProcessor - { - public static readonly ResultProcessor.ScanResult> Default = new SortedSetScanResultProcessor(); - private SortedSetScanResultProcessor() { } - protected override SortedSetEntry[] Parse(RawResult result) - { - SortedSetEntry[] pairs; - if (!SortedSetWithScores.TryParse(result, out pairs)) pairs = null; - return pairs; - } - } - private class StringGetWithExpiryMessage : Message.CommandKeyBase, IMultiMessage - { - private readonly RedisCommand ttlCommand; - private ResultBox box; - - public StringGetWithExpiryMessage(int db, CommandFlags flags, RedisCommand ttlCommand, RedisKey key) - : base(db, flags | CommandFlags.NoRedirect /* <== not implemented/tested */, RedisCommand.GET, key) - { - this.ttlCommand = ttlCommand; - } - public override string CommandAndKey => ttlCommand + "+" + RedisCommand.GET + " " + (string)Key; - - public IEnumerable GetMessages(PhysicalConnection connection) - { - box = ResultBox.Get(null); - var ttl = Message.Create(Db, Flags, ttlCommand, Key); - var proc = ttlCommand == RedisCommand.PTTL ? ResultProcessor.TimeSpanFromMilliseconds : ResultProcessor.TimeSpanFromSeconds; - ttl.SetSource(proc, box); - yield return ttl; - yield return this; - } - - public bool UnwrapValue(out TimeSpan? value, out Exception ex) - { - if (box != null) - { - ResultBox.UnwrapAndRecycle(box, false, out value, out ex); - box = null; - return ex == null; - } - value = null; - ex = null; - return false; - } - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(command, 1); - physical.Write(Key); - } - } - - private class StringGetWithExpiryProcessor : ResultProcessor - { - public static readonly ResultProcessor Default = new StringGetWithExpiryProcessor(); - private StringGetWithExpiryProcessor() { } - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.Integer: - case ResultType.SimpleString: - case ResultType.BulkString: - RedisValue value = result.AsRedisValue(); - var sgwem = message as StringGetWithExpiryMessage; - TimeSpan? expiry; - Exception ex; - if (sgwem != null && sgwem.UnwrapValue(out expiry, out ex)) - { - if (ex == null) - { - SetResult(message, new RedisValueWithExpiry(value, expiry)); - } - else - { - SetException(message, ex); - } - return true; - } - break; - } - return false; - } - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisErrorEventArgs.cs b/StackExchange.Redis/StackExchange/Redis/RedisErrorEventArgs.cs deleted file mode 100644 index a5fee38c2..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisErrorEventArgs.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Net; -using System.Text; - -namespace StackExchange.Redis -{ - /// - /// Notification of errors from the redis server - /// - public sealed class RedisErrorEventArgs : EventArgs, ICompletable - { - private readonly EventHandler handler; - private readonly object sender; - internal RedisErrorEventArgs( - EventHandler handler, object sender, - EndPoint endpoint, string message) - { - this.handler = handler; - this.sender = sender; - Message = message; - EndPoint = endpoint; - } - - /// - /// The origin of the message - /// - public EndPoint EndPoint { get; } - - /// - /// The message from the server - /// - public string Message { get; } - - void ICompletable.AppendStormLog(StringBuilder sb) - { - sb.Append("event, error: ").Append(Message); - } - - bool ICompletable.TryComplete(bool isAsync) - { - return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisFeatures.cs b/StackExchange.Redis/StackExchange/Redis/RedisFeatures.cs deleted file mode 100644 index 8d7f2f926..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisFeatures.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; -using System.Text; - -namespace StackExchange.Redis -{ - /// - /// Provides basic information about the features available on a particular version of Redis - /// - public struct RedisFeatures - { - internal static readonly Version v2_0_0 = new Version(2, 0, 0), - v2_1_0 = new Version(2, 1, 0), - v2_1_1 = new Version(2, 1, 1), - v2_1_2 = new Version(2, 1, 2), - v2_1_3 = new Version(2, 1, 3), - v2_1_8 = new Version(2, 1, 8), - v2_2_0 = new Version(2, 2, 0), - v2_4_0 = new Version(2, 4, 0), - v2_5_7 = new Version(2, 5, 7), - v2_5_10 = new Version(2, 5, 10), - v2_5_14 = new Version(2, 5, 14), - v2_6_0 = new Version(2, 6, 0), - v2_6_5 = new Version(2, 6, 5), - v2_6_9 = new Version(2, 6, 9), - v2_6_12 = new Version(2, 6, 12), - v2_8_0 = new Version(2, 8, 0), - v2_8_12 = new Version(2, 8, 12), - v2_8_18 = new Version(2, 8, 18), - v2_9_5 = new Version(2, 9, 5), - v3_0_0 = new Version(3, 0, 0), - v3_2_0 = new Version(3, 2, 0); - - private readonly Version version; - /// - /// Create a new RedisFeatures instance for the given version - /// - public RedisFeatures(Version version) - { - if (version == null) throw new ArgumentNullException(nameof(version)); - this.version = version; - } - - /// - /// Does BITOP / BITCOUNT exist? - /// - public bool BitwiseOperations => Version >= v2_5_10; - - /// - /// Is CLIENT SETNAME available? - /// - public bool ClientName => Version >= v2_6_9; - - /// - /// Does EXEC support EXECABORT if there are errors? - /// - public bool ExecAbort => Version >= v2_6_5 && Version != v2_9_5; - - /// - /// Can EXPIRE be used to set expiration on a key that is already volatile (i.e. has an expiration)? - /// - public bool ExpireOverwrite => Version >= v2_1_3; - - /// - /// Does HDEL support varadic usage? - /// - public bool HashVaradicDelete => Version >= v2_4_0; - - /// - /// Does INCRBYFLOAT / HINCRBYFLOAT exist? - /// - public bool IncrementFloat => Version >= v2_5_7; - - /// - /// Does INFO support sections? - /// - public bool InfoSections => Version >= v2_8_0; - - /// - /// Is LINSERT available? - /// - public bool ListInsert => Version >= v2_1_1; - - /// - /// Indicates whether PEXPIRE and PTTL are supported - /// - public bool MillisecondExpiry => Version >= v2_6_0; - - /// - /// Does SRANDMEMBER support "count"? - /// - public bool MultipleRandom => Version >= v2_5_14; - - /// - /// Is the PERSIST operation supported? - /// - public bool Persist => Version >= v2_1_2; - - /// - /// Is RPUSHX and LPUSHX available? - /// - public bool PushIfNotExists => Version >= v2_1_1; - - /// - /// Are cursor-based scans available? - /// - public bool Scan => Version >= v2_8_0; - - /// - /// Does EVAL / EVALSHA / etc exist? - /// - public bool Scripting => Version >= v2_5_7; - - /// - /// Does SET have the EX|PX|NX|XX extensions? - /// - public bool SetConditional => Version >= v2_6_12; - - /// - /// Does SADD support varadic usage? - /// - public bool SetVaradicAddRemove => Version >= v2_4_0; - - /// - /// Is STRLEN available? - /// - public bool StringLength => Version >= v2_1_2; - - /// - /// Is SETRANGE available? - /// - public bool StringSetRange => Version >= v2_1_8; - - /// - /// Does TIME exist? - /// - public bool Time => Version >= v2_6_0; - - /// - /// Are Lua changes to the calling database transparent to the calling client? - /// - public bool ScriptingDatabaseSafe => Version >= v2_8_12; - - /// - /// Is PFCOUNT supported on slaves? - /// - public bool HyperLogLogCountSlaveSafe => Version >= v2_8_18; - - /// - /// Are the GEO commands available? - /// - public bool Geo => Version >= v3_2_0; - - /// - /// The Redis version of the server - /// - public Version Version => version ?? v2_0_0; - - /// - /// Create a string representation of the available features - /// - public override string ToString() - { - var sb = new StringBuilder().Append("Features in ").Append(Version).AppendLine() - .Append("ExpireOverwrite: ").Append(ExpireOverwrite).AppendLine() - .Append("Persist: ").Append(Persist).AppendLine(); - - return sb.ToString(); - } - // 2.9.5 (cluster beta 1) has a bug in EXECABORT re MOVED and ASK; it only affects cluster, but - // frankly if you aren't playing with cluster, why are you using 2.9.5 in the first place? - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisKey.cs b/StackExchange.Redis/StackExchange/Redis/RedisKey.cs deleted file mode 100644 index 06b665514..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisKey.cs +++ /dev/null @@ -1,318 +0,0 @@ -using System; -using System.Text; - -namespace StackExchange.Redis -{ - /// - /// Represents a key that can be stored in redis - /// - public struct RedisKey : IEquatable - { - internal static readonly RedisKey[] EmptyArray = new RedisKey[0]; - private readonly byte[] keyPrefix; - private readonly object keyValue; // always either a string or a byte[] - internal RedisKey(byte[] keyPrefix, object keyValue) - { - this.keyPrefix = (keyPrefix != null && keyPrefix.Length == 0) ? null : keyPrefix; - this.keyValue = keyValue; - } - - internal RedisKey AsPrefix() - { - return new RedisKey((byte[])this, null); - } - internal bool IsNull => keyPrefix == null && keyValue == null; - - internal bool IsEmpty - { - get - { - if (keyPrefix != null) return false; - if (keyValue == null) return true; - if (keyValue is string) return ((string)keyValue).Length == 0; - return ((byte[])keyValue).Length == 0; - } - } - - internal byte[] KeyPrefix => keyPrefix; - internal object KeyValue => keyValue; - - /// - /// Indicate whether two keys are not equal - /// - public static bool operator !=(RedisKey x, RedisKey y) - { - return !(x == y); - } - - /// - /// Indicate whether two keys are not equal - /// - public static bool operator !=(string x, RedisKey y) - { - return !(x == y); - } - - /// - /// Indicate whether two keys are not equal - /// - public static bool operator !=(byte[] x, RedisKey y) - { - return !(x == y); - } - - /// - /// Indicate whether two keys are not equal - /// - public static bool operator !=(RedisKey x, string y) - { - return !(x == y); - } - - /// - /// Indicate whether two keys are not equal - /// - public static bool operator !=(RedisKey x, byte[] y) - { - return !(x == y); - } - - /// - /// Indicate whether two keys are equal - /// - public static bool operator ==(RedisKey x, RedisKey y) - { - return CompositeEquals(x.keyPrefix, x.keyValue, y.keyPrefix, y.keyValue); - } - - /// - /// Indicate whether two keys are equal - /// - public static bool operator ==(string x, RedisKey y) - { - return CompositeEquals(null, x, y.keyPrefix, y.keyValue); - } - - /// - /// Indicate whether two keys are equal - /// - public static bool operator ==(byte[] x, RedisKey y) - { - return CompositeEquals(null, x, y.keyPrefix, y.keyValue); - } - - /// - /// Indicate whether two keys are equal - /// - public static bool operator ==(RedisKey x, string y) - { - return CompositeEquals(x.keyPrefix, x.keyValue, null, y); - } - - /// - /// Indicate whether two keys are equal - /// - public static bool operator ==(RedisKey x, byte[] y) - { - return CompositeEquals(x.keyPrefix, x.keyValue, null, y); - } - - /// - /// See Object.Equals - /// - public override bool Equals(object obj) - { - if (obj is RedisKey) - { - var other = (RedisKey)obj; - return CompositeEquals(this.keyPrefix, this.keyValue, other.keyPrefix, other.keyValue); - } - if (obj is string || obj is byte[]) - { - return CompositeEquals(this.keyPrefix, this.keyValue, null, obj); - } - return false; - } - - /// - /// Indicate whether two keys are equal - /// - public bool Equals(RedisKey other) - { - return CompositeEquals(this.keyPrefix, this.keyValue, other.keyPrefix, other.keyValue); - } - - private static bool CompositeEquals(byte[] keyPrefix0, object keyValue0, byte[] keyPrefix1, object keyValue1) - { - if(RedisValue.Equals(keyPrefix0, keyPrefix1)) - { - if (keyValue0 == keyValue1) return true; // ref equal - if (keyValue0 == null || keyValue1 == null) return false; // null vs non-null - - if (keyValue0 is string && keyValue1 is string) return ((string)keyValue0) == ((string)keyValue1); - if (keyValue0 is byte[] && keyValue1 is byte[]) return RedisValue.Equals((byte[])keyValue0, (byte[])keyValue1); - } - - return RedisValue.Equals(ConcatenateBytes(keyPrefix0, keyValue0, null), ConcatenateBytes(keyPrefix1, keyValue1, null)); - } - - /// - /// See Object.GetHashCode - /// - public override int GetHashCode() - { - int chk0 = keyPrefix == null ? 0 : RedisValue.GetHashCode(this.keyPrefix), - chk1 = keyValue is string ? keyValue.GetHashCode() : RedisValue.GetHashCode((byte[])keyValue); - - return unchecked((17 * chk0) + chk1); - } - - /// - /// Obtains a string representation of the key - /// - public override string ToString() - { - return ((string)this) ?? "(null)"; - } - - internal RedisValue AsRedisValue() - { - return (byte[])this; - } - - internal void AssertNotNull() - { - if (IsNull) throw new ArgumentException("A null key is not valid in this context"); - } - - /// - /// Create a key from a String - /// - public static implicit operator RedisKey(string key) - { - if (key == null) return default(RedisKey); - return new RedisKey(null, key); - } - /// - /// Create a key from a Byte[] - /// - public static implicit operator RedisKey(byte[] key) - { - if (key == null) return default(RedisKey); - return new RedisKey(null, key); - } - /// - /// Obtain the key as a Byte[] - /// - public static implicit operator byte[](RedisKey key) - { - return ConcatenateBytes(key.keyPrefix, key.keyValue, null); - } - /// - /// Obtain the key as a String - /// - public static implicit operator string(RedisKey key) - { - byte[] arr; - if(key.keyPrefix == null) - { - if (key.keyValue == null) return null; - - if (key.keyValue is string) return (string)key.keyValue; - - arr = (byte[])key.keyValue; - } - else - { - arr = (byte[])key; - } - if (arr == null) return null; - try - { - return Encoding.UTF8.GetString(arr); - } - catch - { - return BitConverter.ToString(arr); - } - - } - - /// - /// Concatenate two keys - /// - [Obsolete] - public static RedisKey operator +(RedisKey x, RedisKey y) - { - return new RedisKey(ConcatenateBytes(x.keyPrefix, x.keyValue, y.keyPrefix), y.keyValue); - } - - internal static RedisKey WithPrefix(byte[] prefix, RedisKey value) - { - if(prefix == null || prefix.Length == 0) return value; - if (value.keyPrefix == null) return new RedisKey(prefix, value.keyValue); - if (value.keyValue == null) return new RedisKey(prefix, value.keyPrefix); - - // two prefixes; darn - byte[] copy = new byte[prefix.Length + value.keyPrefix.Length]; - Buffer.BlockCopy(prefix, 0, copy, 0, prefix.Length); - Buffer.BlockCopy(value.keyPrefix, 0, copy, prefix.Length, value.keyPrefix.Length); - return new RedisKey(copy, value.keyValue); - } - - internal static byte[] ConcatenateBytes(byte[] a, object b, byte[] c) - { - if ((a == null || a.Length == 0) && (c == null || c.Length == 0)) - { - if (b == null) return null; - if (b is string) return Encoding.UTF8.GetBytes((string)b); - return (byte[])b; - } - - int aLen = a?.Length ?? 0, - bLen = b == null ? 0 : (b is string - ? Encoding.UTF8.GetByteCount((string)b) - : ((byte[])b).Length), - cLen = c?.Length ?? 0; - - byte[] result = new byte[aLen + bLen + cLen]; - if (aLen != 0) Buffer.BlockCopy(a, 0, result, 0, aLen); - if (bLen != 0) - { - if (b is string) - { - string s = (string)b; - Encoding.UTF8.GetBytes(s, 0, s.Length, result, aLen); - } - else - { - Buffer.BlockCopy((byte[])b, 0, result, aLen, bLen); - } - } - if (cLen != 0) Buffer.BlockCopy(c, 0, result, aLen + bLen, cLen); - return result; - } - - /// - /// Prepends p to this RedisKey, returning a new RedisKey. - /// - /// Avoids some allocations if possible, repeated Prepend/Appends make - /// it less possible. - /// - public RedisKey Prepend(RedisKey p) - { - return WithPrefix(p, this); - } - - /// - /// Appends p to this RedisKey, returning a new RedisKey. - /// - /// Avoids some allocations if possible, repeated Prepend/Appends make - /// it less possible. - /// - public RedisKey Append(RedisKey p) - { - return WithPrefix(this, p); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisLiterals.cs b/StackExchange.Redis/StackExchange/Redis/RedisLiterals.cs deleted file mode 100644 index 588e39d31..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisLiterals.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Text; - -namespace StackExchange.Redis -{ - internal static class RedisLiterals - { - // unlike primary commands, these do not get altered by the command-map; we may as - // well compute the bytes once and share them - public static readonly RedisValue - ADDR = "ADDR", - AFTER = "AFTER", - AGGREGATE = "AGGREGATE", - ALPHA = "ALPHA", - AND = "AND", - BEFORE = "BEFORE", - BY = "BY", - CHANNELS = "CHANNELS", - COPY = "COPY", - COUNT = "COUNT", - DESC = "DESC", - EX = "EX", - EXISTS = "EXISTS", - FLUSH = "FLUSH", - GET = "GET", - GETNAME = "GETNAME", - ID = "ID", - KILL = "KILL", - LIMIT = "LIMIT", - LIST = "LIST", - LOAD = "LOAD", - MATCH = "MATCH", - MAX = "MAX", - MIN = "MIN", - NODES = "NODES", - NOSAVE = "NOSAVE", - NOT = "NOT", - NUMPAT = "NUMPAT", - NUMSUB = "NUMSUB", - NX = "NX", - OBJECT = "OBJECT", - OR = "OR", - PAUSE = "PAUSE", - PING = "PING", - PX = "PX", - REPLACE = "REPLACE", - RESET = "RESET", - RESETSTAT = "RESETSTAT", - REWRITE = "REWRITE", - SAVE = "SAVE", - SEGFAULT = "SEGFAULT", - SET = "SET", - SETNAME = "SETNAME", - SKIPME = "SKIPME", - STORE = "STORE", - TYPE = "TYPE", - WEIGHTS = "WEIGHTS", - WITHSCORES = "WITHSCORES", - XOR = "XOR", - XX = "XX", - - - - // Sentinel Literals - MASTERS = "MASTERS", - MASTER = "MASTER", - SLAVES = "SLAVES", - GETMASTERADDRBYNAME = "GET-MASTER-ADDR-BY-NAME", -// RESET = "RESET", - FAILOVER = "FAILOVER", - - // Sentinel Literals as of 2.8.4 - MONITOR = "MONITOR", - REMOVE = "REMOVE", -// SET = "SET", - - // DO NOT CHANGE CASE: these are configuration settings and MUST be as-is - databases = "databases", - no = "no", - normal = "normal", - pubsub = "pubsub", - replication = "replication", - server = "server", - slave = "slave", - slave_read_only = "slave-read-only", - timeout = "timeout", - yes = "yes", - - MinusSymbol = "-", - PlusSumbol = "+", - Wildcard = "*"; - - public static readonly byte[] BytesOK = Encoding.UTF8.GetBytes("OK"); - public static readonly byte[] BytesPONG = Encoding.UTF8.GetBytes("PONG"); - public static readonly byte[] BytesBackgroundSavingStarted = Encoding.UTF8.GetBytes("Background saving started"); - public static readonly byte[] ByteWildcard = { (byte)'*' }; - internal static RedisValue Get(Bitwise operation) - { - switch(operation) - { - case Bitwise.And: return AND; - case Bitwise.Or: return OR; - case Bitwise.Xor: return XOR; - case Bitwise.Not: return NOT; - default: throw new ArgumentOutOfRangeException(nameof(operation)); - } - } - } -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/RedisResult.cs b/StackExchange.Redis/StackExchange/Redis/RedisResult.cs deleted file mode 100644 index fc5c87120..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisResult.cs +++ /dev/null @@ -1,389 +0,0 @@ -using System; - -namespace StackExchange.Redis -{ - /// - /// Represents a general-purpose result from redis, that may be cast into various anticipated types - /// - public abstract class RedisResult - { - /// - /// Create a new RedisResult. - /// - /// - public static RedisResult Create(RedisValue value) - { - return new SingleRedisResult(value); - } - - // internally, this is very similar to RawResult, except it is designed to be usable - // outside of the IO-processing pipeline: the buffers are standalone, etc - - internal static RedisResult TryCreate(PhysicalConnection connection, RawResult result) - { - try - { - switch (result.Type) - { - case ResultType.Integer: - case ResultType.SimpleString: - case ResultType.BulkString: - return new SingleRedisResult(result.AsRedisValue()); - case ResultType.MultiBulk: - var items = result.GetItems(); - var arr = new RedisResult[items.Length]; - for (int i = 0; i < arr.Length; i++) - { - var next = TryCreate(connection, items[i]); - if (next == null) return null; // means we didn't understand - arr[i] = next; - } - return new ArrayRedisResult(arr); - case ResultType.Error: - return new ErrorRedisResult(result.GetString()); - default: - return null; - } - } catch(Exception ex) - { - connection?.OnInternalError(ex); - return null; // will be logged as a protocol fail by the processor - } - } - - /// - /// Indicates whether this result was a null result - /// - public abstract bool IsNull { get; } - - /// - /// Interprets the result as a String - /// - public static explicit operator string (RedisResult result) { return result.AsString(); } - /// - /// Interprets the result as a Byte[] - /// - public static explicit operator byte[] (RedisResult result) { return result.AsByteArray(); } - /// - /// Interprets the result as a Double - /// - public static explicit operator double (RedisResult result) { return result.AsDouble(); } - /// - /// Interprets the result as an Int64 - /// - public static explicit operator long (RedisResult result) { return result.AsInt64(); } - /// - /// Interprets the result as an Int32 - /// - public static explicit operator int (RedisResult result) { return result.AsInt32(); } - /// - /// Interprets the result as a Boolean - /// - public static explicit operator bool (RedisResult result) { return result.AsBoolean(); } - /// - /// Interprets the result as a RedisValue - /// - public static explicit operator RedisValue (RedisResult result) { return result.AsRedisValue(); } - /// - /// Interprets the result as a RedisKey - /// - public static explicit operator RedisKey (RedisResult result) { return result.AsRedisKey(); } - /// - /// Interprets the result as a Nullable Double - /// - public static explicit operator double? (RedisResult result) { return result.AsNullableDouble(); } - /// - /// Interprets the result as a Nullable Int64 - /// - public static explicit operator long? (RedisResult result) { return result.AsNullableInt64(); } - /// - /// Interprets the result as a Nullable Int32 - /// - public static explicit operator int? (RedisResult result) { return result.AsNullableInt32(); } - /// - /// Interprets the result as a Nullable Boolean - /// - public static explicit operator bool? (RedisResult result) { return result.AsNullableBoolean(); } - /// - /// Interprets the result as an array of String - /// - public static explicit operator string[] (RedisResult result) { return result.AsStringArray(); } - /// - /// Interprets the result as an array of Byte[] - /// - public static explicit operator byte[][] (RedisResult result) { return result.AsByteArrayArray(); } - /// - /// Interprets the result as an array of Double - /// - public static explicit operator double[] (RedisResult result) { return result.AsDoubleArray(); } - /// - /// Interprets the result as an array of Int64 - /// - public static explicit operator long[] (RedisResult result) { return result.AsInt64Array(); } - /// - /// Interprets the result as an array of Int32 - /// - public static explicit operator int[] (RedisResult result) { return result.AsInt32Array(); } - /// - /// Interprets the result as an array of Boolean - /// - public static explicit operator bool[] (RedisResult result) { return result.AsBooleanArray(); } - /// - /// Interprets the result as an array of RedisValue - /// - public static explicit operator RedisValue[] (RedisResult result) { return result.AsRedisValueArray(); } - /// - /// Interprets the result as an array of RedisKey - /// - public static explicit operator RedisKey[] (RedisResult result) { return result.AsRedisKeyArray(); } - /// - /// Interprets the result as an array of RedisResult - /// - public static explicit operator RedisResult[] (RedisResult result) { return result.AsRedisResultArray(); } - - internal abstract bool AsBoolean(); - - internal abstract bool[] AsBooleanArray(); - - internal abstract byte[] AsByteArray(); - - internal abstract byte[][] AsByteArrayArray(); - - internal abstract double AsDouble(); - - internal abstract double[] AsDoubleArray(); - - internal abstract int AsInt32(); - - internal abstract int[] AsInt32Array(); - - internal abstract long AsInt64(); - - internal abstract long[] AsInt64Array(); - - internal abstract bool? AsNullableBoolean(); - - internal abstract double? AsNullableDouble(); - - internal abstract int? AsNullableInt32(); - - internal abstract long? AsNullableInt64(); - - internal abstract RedisKey AsRedisKey(); - - internal abstract RedisKey[] AsRedisKeyArray(); - - internal abstract RedisResult[] AsRedisResultArray(); - - internal abstract RedisValue AsRedisValue(); - - internal abstract RedisValue[] AsRedisValueArray(); - internal abstract string AsString(); - internal abstract string[] AsStringArray(); - private sealed class ArrayRedisResult : RedisResult - { - public override bool IsNull => value == null; - private readonly RedisResult[] value; - public ArrayRedisResult(RedisResult[] value) - { - if (value == null) throw new ArgumentNullException(nameof(value)); - this.value = value; - } - public override string ToString() - { - return value.Length + " element(s)"; - } - internal override bool AsBoolean() - { - if (value.Length == 1) return value[0].AsBoolean(); - throw new InvalidCastException(); - } - - internal override bool[] AsBooleanArray() { return ConvertHelper.ConvertAll(value, x => x.AsBoolean()); } - - internal override byte[] AsByteArray() - { - if (value.Length == 1) return value[0].AsByteArray(); - throw new InvalidCastException(); - } - internal override byte[][] AsByteArrayArray() { return ConvertHelper.ConvertAll(value, x => x.AsByteArray()); } - - internal override double AsDouble() - { - if (value.Length == 1) return value[0].AsDouble(); - throw new InvalidCastException(); - } - - internal override double[] AsDoubleArray() { return ConvertHelper.ConvertAll(value, x => x.AsDouble()); } - - internal override int AsInt32() - { - if (value.Length == 1) return value[0].AsInt32(); - throw new InvalidCastException(); - } - - internal override int[] AsInt32Array() { return ConvertHelper.ConvertAll(value, x => x.AsInt32()); } - - internal override long AsInt64() - { - if (value.Length == 1) return value[0].AsInt64(); - throw new InvalidCastException(); - } - - internal override long[] AsInt64Array() { return ConvertHelper.ConvertAll(value, x => x.AsInt64()); } - - internal override bool? AsNullableBoolean() - { - if (value.Length == 1) return value[0].AsNullableBoolean(); - throw new InvalidCastException(); - } - - internal override double? AsNullableDouble() - { - if (value.Length == 1) return value[0].AsNullableDouble(); - throw new InvalidCastException(); - } - - internal override int? AsNullableInt32() - { - if (value.Length == 1) return value[0].AsNullableInt32(); - throw new InvalidCastException(); - } - - internal override long? AsNullableInt64() - { - if (value.Length == 1) return value[0].AsNullableInt64(); - throw new InvalidCastException(); - } - - internal override RedisKey AsRedisKey() - { - if (value.Length == 1) return value[0].AsRedisKey(); - throw new InvalidCastException(); - } - - internal override RedisKey[] AsRedisKeyArray() { return ConvertHelper.ConvertAll(value, x => x.AsRedisKey()); } - - internal override RedisResult[] AsRedisResultArray() { return value; } - - internal override RedisValue AsRedisValue() - { - if (value.Length == 1) return value[0].AsRedisValue(); - throw new InvalidCastException(); - } - - internal override RedisValue[] AsRedisValueArray() { return ConvertHelper.ConvertAll(value, x => x.AsRedisValue()); } - - internal override string AsString() - { - if (value.Length == 1) return value[0].AsString(); - throw new InvalidCastException(); - } - internal override string[] AsStringArray() { return ConvertHelper.ConvertAll(value, x => x.AsString()); } - } - - private sealed class ErrorRedisResult : RedisResult - { - private readonly string value; - public ErrorRedisResult(string value) - { - if (value == null) throw new ArgumentNullException(nameof(value)); - this.value = value; - } - public override bool IsNull => value == null; - public override string ToString() { return value; } - internal override bool AsBoolean() { throw new RedisServerException(value); } - - internal override bool[] AsBooleanArray() { throw new RedisServerException(value); } - - internal override byte[] AsByteArray() { throw new RedisServerException(value); } - - internal override byte[][] AsByteArrayArray() { throw new RedisServerException(value); } - - internal override double AsDouble() { throw new RedisServerException(value); } - - internal override double[] AsDoubleArray() { throw new RedisServerException(value); } - - internal override int AsInt32() { throw new RedisServerException(value); } - - internal override int[] AsInt32Array() { throw new RedisServerException(value); } - - internal override long AsInt64() { throw new RedisServerException(value); } - - internal override long[] AsInt64Array() { throw new RedisServerException(value); } - - internal override bool? AsNullableBoolean() { throw new RedisServerException(value); } - - internal override double? AsNullableDouble() { throw new RedisServerException(value); } - - internal override int? AsNullableInt32() { throw new RedisServerException(value); } - - internal override long? AsNullableInt64() { throw new RedisServerException(value); } - - internal override RedisKey AsRedisKey() { throw new RedisServerException(value); } - - internal override RedisKey[] AsRedisKeyArray() { throw new RedisServerException(value); } - - internal override RedisResult[] AsRedisResultArray() { throw new RedisServerException(value); } - - internal override RedisValue AsRedisValue() { throw new RedisServerException(value); } - - internal override RedisValue[] AsRedisValueArray() { throw new RedisServerException(value); } - - internal override string AsString() { throw new RedisServerException(value); } - internal override string[] AsStringArray() { throw new RedisServerException(value); } - } - - private sealed class SingleRedisResult : RedisResult - { - private readonly RedisValue value; - public SingleRedisResult(RedisValue value) - { - this.value = value; - } - - public override bool IsNull => value.IsNull; - - public override string ToString() { return value.ToString(); } - internal override bool AsBoolean() { return (bool)value; } - - internal override bool[] AsBooleanArray() { return new[] { AsBoolean() }; } - - internal override byte[] AsByteArray() { return (byte[])value; } - internal override byte[][] AsByteArrayArray() { return new[] { AsByteArray() }; } - - internal override double AsDouble() { return (double)value; } - - internal override double[] AsDoubleArray() { return new[] { AsDouble() }; } - - internal override int AsInt32() { return (int)value; } - - internal override int[] AsInt32Array() { return new[] { AsInt32() }; } - - internal override long AsInt64() { return (long)value; } - - internal override long[] AsInt64Array() { return new[] { AsInt64() }; } - - internal override bool? AsNullableBoolean() { return (bool?)value; } - - internal override double? AsNullableDouble() { return (double?)value; } - - internal override int? AsNullableInt32() { return (int?)value; } - - internal override long? AsNullableInt64() { return (long?)value; } - - internal override RedisKey AsRedisKey() { return (byte[])value; } - - internal override RedisKey[] AsRedisKeyArray() { return new[] { AsRedisKey() }; } - - internal override RedisResult[] AsRedisResultArray() { throw new InvalidCastException(); } - - internal override RedisValue AsRedisValue() { return value; } - - internal override RedisValue[] AsRedisValueArray() { return new[] { AsRedisValue() }; } - - internal override string AsString() { return (string)value; } - internal override string[] AsStringArray() { return new[] { AsString() }; } - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisServer.cs b/StackExchange.Redis/StackExchange/Redis/RedisServer.cs deleted file mode 100644 index 5782dfdb6..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisServer.cs +++ /dev/null @@ -1,818 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - - internal sealed partial class RedisServer : RedisBase, IServer - { - private readonly ServerEndPoint server; - - internal RedisServer(ConnectionMultiplexer multiplexer, ServerEndPoint server, object asyncState) : base(multiplexer, asyncState) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - this.server = server; - } - - public ClusterConfiguration ClusterConfiguration => server.ClusterConfiguration; - - public EndPoint EndPoint => server.EndPoint; - - public RedisFeatures Features => server.GetFeatures(); - - public bool IsConnected => server.IsConnected; - - public bool IsSlave => server.IsSlave; - - public bool AllowSlaveWrites - { - get { return server.AllowSlaveWrites; } - set { server.AllowSlaveWrites = value; } - } - - public ServerType ServerType => server.ServerType; - - public Version Version => server.Version; - - public void ClientKill(EndPoint endpoint, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.KILL, (RedisValue)Format.ToString(endpoint)); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - - public Task ClientKillAsync(EndPoint endpoint, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.KILL, (RedisValue)Format.ToString(endpoint)); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public long ClientKill(long? id = null, ClientType? clientType = null, EndPoint endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None) - { - var msg = GetClientKillMessage(endpoint, id, clientType, skipMe, flags); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task ClientKillAsync(long? id = null, ClientType? clientType = null, EndPoint endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None) - { - var msg = GetClientKillMessage(endpoint, id, clientType, skipMe, flags); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - Message GetClientKillMessage(EndPoint endpoint, long? id, ClientType? clientType, bool skipMe, CommandFlags flags) - { - List parts = new List(9) - { - RedisLiterals.KILL - }; - if(id != null) - { - parts.Add(RedisLiterals.ID); - parts.Add(id.Value); - } - if (clientType != null) - { - parts.Add(RedisLiterals.TYPE); - switch(clientType.Value) - { - case ClientType.Normal: - parts.Add(RedisLiterals.normal); - break; - case ClientType.Slave: - parts.Add(RedisLiterals.slave); - break; - case ClientType.PubSub: - parts.Add(RedisLiterals.pubsub); - break; - default: - throw new ArgumentOutOfRangeException(nameof(clientType)); - } - parts.Add(id.Value); - } - if (endpoint != null) - { - parts.Add(RedisLiterals.ADDR); - parts.Add((RedisValue)Format.ToString(endpoint)); - } - if(!skipMe) - { - parts.Add(RedisLiterals.SKIPME); - parts.Add(RedisLiterals.no); - } - return Message.Create(-1, flags, RedisCommand.CLIENT, parts); - } - - public ClientInfo[] ClientList(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.LIST); - return ExecuteSync(msg, ClientInfo.Processor); - } - - public Task ClientListAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.LIST); - return ExecuteAsync(msg, ClientInfo.Processor); - } - - public ClusterConfiguration ClusterNodes(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); - return ExecuteSync(msg, ResultProcessor.ClusterNodes); - } - - public Task ClusterNodesAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); - return ExecuteAsync(msg, ResultProcessor.ClusterNodes); - } - - public string ClusterNodesRaw(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); - return ExecuteSync(msg, ResultProcessor.ClusterNodesRaw); - } - - public Task ClusterNodesRawAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); - return ExecuteAsync(msg, ResultProcessor.ClusterNodesRaw); - } - - public KeyValuePair[] ConfigGet(RedisValue pattern = default(RedisValue), CommandFlags flags = CommandFlags.None) - { - if (pattern.IsNullOrEmpty) pattern = RedisLiterals.Wildcard; - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, pattern); - return ExecuteSync(msg, ResultProcessor.StringPairInterleaved); - } - - public Task[]> ConfigGetAsync(RedisValue pattern = default(RedisValue), CommandFlags flags = CommandFlags.None) - { - if (pattern.IsNullOrEmpty) pattern = RedisLiterals.Wildcard; - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, pattern); - return ExecuteAsync(msg, ResultProcessor.StringPairInterleaved); - } - - public void ConfigResetStatistics(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.RESETSTAT); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - - public Task ConfigResetStatisticsAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.RESETSTAT); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public void ConfigRewrite(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.REWRITE); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - - public Task ConfigRewriteAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.REWRITE); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public void ConfigSet(RedisValue setting, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.SET, setting, value); - ExecuteSync(msg, ResultProcessor.DemandOK); - ExecuteSync(Message.Create(-1, flags | CommandFlags.FireAndForget, RedisCommand.CONFIG, RedisLiterals.GET, setting), ResultProcessor.AutoConfigure); - } - - public Task ConfigSetAsync(RedisValue setting, RedisValue value, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.SET, setting, value); - var task = ExecuteAsync(msg, ResultProcessor.DemandOK); - ExecuteSync(Message.Create(-1, flags | CommandFlags.FireAndForget, RedisCommand.CONFIG, RedisLiterals.GET, setting), ResultProcessor.AutoConfigure); - return task; - } - public long DatabaseSize(int database = 0, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(database, flags, RedisCommand.DBSIZE); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task DatabaseSizeAsync(int database = 0, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(database, flags, RedisCommand.DBSIZE); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public RedisValue Echo(RedisValue message, CommandFlags flags) - { - var msg = Message.Create(-1, flags, RedisCommand.ECHO, message); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - public Task EchoAsync(RedisValue message, CommandFlags flags) - { - var msg = Message.Create(-1, flags, RedisCommand.ECHO, message); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public void FlushAllDatabases(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.FLUSHALL); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - public Task FlushAllDatabasesAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.FLUSHALL); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public void FlushDatabase(int database = 0, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(database, flags, RedisCommand.FLUSHDB); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - - public Task FlushDatabaseAsync(int database = 0, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(database, flags, RedisCommand.FLUSHDB); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public ServerCounters GetCounters() - { - return server.GetCounters(); - } - - public IGrouping>[] Info(RedisValue section = default(RedisValue), CommandFlags flags = CommandFlags.None) - { - var msg = section.IsNullOrEmpty - ? Message.Create(-1, flags, RedisCommand.INFO) - : Message.Create(-1, flags, RedisCommand.INFO, section); - - return ExecuteSync(msg, ResultProcessor.Info); - } - - public Task>[]> InfoAsync(RedisValue section = default(RedisValue), CommandFlags flags = CommandFlags.None) - { - var msg = section.IsNullOrEmpty - ? Message.Create(-1, flags, RedisCommand.INFO) - : Message.Create(-1, flags, RedisCommand.INFO, section); - - return ExecuteAsync(msg, ResultProcessor.Info); - } - - public string InfoRaw(RedisValue section = default(RedisValue), CommandFlags flags = CommandFlags.None) - { - var msg = section.IsNullOrEmpty - ? Message.Create(-1, flags, RedisCommand.INFO) - : Message.Create(-1, flags, RedisCommand.INFO, section); - - return ExecuteSync(msg, ResultProcessor.String); - } - - public Task InfoRawAsync(RedisValue section = default(RedisValue), CommandFlags flags = CommandFlags.None) - { - var msg = section.IsNullOrEmpty - ? Message.Create(-1, flags, RedisCommand.INFO) - : Message.Create(-1, flags, RedisCommand.INFO, section); - - return ExecuteAsync(msg, ResultProcessor.String); - } - - IEnumerable IServer.Keys(int database, RedisValue pattern, int pageSize, CommandFlags flags) - { - return Keys(database, pattern, pageSize, CursorUtils.Origin, 0, flags); - } - - public IEnumerable Keys(int database = 0, RedisValue pattern = default(RedisValue), int pageSize = CursorUtils.DefaultPageSize, long cursor = CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) - { - if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - if (CursorUtils.IsNil(pattern)) pattern = RedisLiterals.Wildcard; - - if (multiplexer.CommandMap.IsAvailable(RedisCommand.SCAN)) - { - var features = server.GetFeatures(); - - if (features.Scan) return new KeysScanEnumerable(this, database, pattern, pageSize, cursor, pageOffset, flags); - } - - if (cursor != 0 || pageOffset != 0) throw ExceptionFactory.NoCursor(RedisCommand.KEYS); - Message msg = Message.Create(database, flags, RedisCommand.KEYS, pattern); - return ExecuteSync(msg, ResultProcessor.RedisKeyArray); - } - - public DateTime LastSave(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.LASTSAVE); - return ExecuteSync(msg, ResultProcessor.DateTime); - } - - public Task LastSaveAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.LASTSAVE); - return ExecuteAsync(msg, ResultProcessor.DateTime); - } - - public void MakeMaster(ReplicationChangeOptions options, TextWriter log = null) - { - multiplexer.MakeMaster(server, options, log); - } - public void Save(SaveType type, CommandFlags flags = CommandFlags.None) - { - var msg = GetSaveMessage(type, flags); - ExecuteSync(msg, GetSaveResultProcessor(type)); - } - - public Task SaveAsync(SaveType type, CommandFlags flags = CommandFlags.None) - { - var msg = GetSaveMessage(type, flags); - return ExecuteAsync(msg, GetSaveResultProcessor(type)); - } - - public bool ScriptExists(string script, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Hash(script)); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public bool ScriptExists(byte[] sha1, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Encode(sha1)); - return ExecuteSync(msg, ResultProcessor.Boolean); - } - - public Task ScriptExistsAsync(string script, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Hash(script)); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public Task ScriptExistsAsync(byte[] sha1, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Encode(sha1)); - return ExecuteAsync(msg, ResultProcessor.Boolean); - } - - public void ScriptFlush(CommandFlags flags = CommandFlags.None) - { - if (!multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(multiplexer.IncludeDetailInExceptions, RedisCommand.SCRIPT, null, server); - var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.FLUSH); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - - public Task ScriptFlushAsync(CommandFlags flags = CommandFlags.None) - { - if (!multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(multiplexer.IncludeDetailInExceptions, RedisCommand.SCRIPT, null, server); - var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.FLUSH); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public byte[] ScriptLoad(string script, CommandFlags flags = CommandFlags.None) - { - var msg = new RedisDatabase.ScriptLoadMessage(flags, script); - return ExecuteSync(msg, ResultProcessor.ScriptLoad); - } - - public Task ScriptLoadAsync(string script, CommandFlags flags = CommandFlags.None) - { - var msg = new RedisDatabase.ScriptLoadMessage(flags, script); - return ExecuteAsync(msg, ResultProcessor.ScriptLoad); - } - - public LoadedLuaScript ScriptLoad(LuaScript script, CommandFlags flags = CommandFlags.None) - { - return script.Load(this, flags); - } - - public Task ScriptLoadAsync(LuaScript script, CommandFlags flags = CommandFlags.None) - { - return script.LoadAsync(this, flags); - } - - public void Shutdown(ShutdownMode shutdownMode = ShutdownMode.Default, CommandFlags flags = CommandFlags.None) - { - Message msg; - switch (shutdownMode) - { - case ShutdownMode.Default: - msg = Message.Create(-1, flags, RedisCommand.SHUTDOWN); - break; - case ShutdownMode.Always: - msg = Message.Create(-1, flags, RedisCommand.SHUTDOWN, RedisLiterals.SAVE); - break; - case ShutdownMode.Never: - msg = Message.Create(-1, flags, RedisCommand.SHUTDOWN, RedisLiterals.NOSAVE); - break; - default: - throw new ArgumentOutOfRangeException(nameof(shutdownMode)); - } - try - { - ExecuteSync(msg, ResultProcessor.DemandOK); - } - catch (RedisConnectionException ex) - { - switch (ex.FailureType) - { - case ConnectionFailureType.SocketClosed: - case ConnectionFailureType.SocketFailure: - // that's fine - return; - } - throw; // otherwise, not something we were expecting - } - } - - public CommandTrace[] SlowlogGet(int count = 0, CommandFlags flags = CommandFlags.None) - { - var msg = count > 0 - ? Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET, count) - : Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET); - - return ExecuteSync(msg, CommandTrace.Processor); - } - - public Task SlowlogGetAsync(int count = 0, CommandFlags flags = CommandFlags.None) - { - var msg = count > 0 - ? Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET, count) - : Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET); - - return ExecuteAsync(msg, CommandTrace.Processor); - } - - public void SlowlogReset(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.RESET); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - - public Task SlowlogResetAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.RESET); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public RedisValue StringGet(int db, RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(db, flags, RedisCommand.GET, key); - return ExecuteSync(msg, ResultProcessor.RedisValue); - } - - public Task StringGetAsync(int db, RedisKey key, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(db, flags, RedisCommand.GET, key); - return ExecuteAsync(msg, ResultProcessor.RedisValue); - } - - public RedisChannel[] SubscriptionChannels(RedisChannel pattern = default(RedisChannel), CommandFlags flags = CommandFlags.None) - { - var msg = pattern.IsNullOrEmpty ? Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS) - : Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS, pattern); - return ExecuteSync(msg, ResultProcessor.RedisChannelArrayLiteral); - } - - public Task SubscriptionChannelsAsync(RedisChannel pattern = default(RedisChannel), CommandFlags flags = CommandFlags.None) - { - var msg = pattern.IsNullOrEmpty ? Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS) - : Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS, pattern); - return ExecuteAsync(msg, ResultProcessor.RedisChannelArrayLiteral); - } - - public long SubscriptionPatternCount(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMPAT); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task SubscriptionPatternCountAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMPAT); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public long SubscriptionSubscriberCount(RedisChannel channel, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); - return ExecuteSync(msg, ResultProcessor.PubSubNumSub); - } - - public Task SubscriptionSubscriberCountAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); - return ExecuteAsync(msg, ResultProcessor.PubSubNumSub); - } - - public DateTime Time(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.TIME); - return ExecuteSync(msg, ResultProcessor.DateTime); - } - - public Task TimeAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.TIME); - return ExecuteAsync(msg, ResultProcessor.DateTime); - } - - internal static Message CreateSlaveOfMessage(EndPoint endpoint, CommandFlags flags = CommandFlags.None) - { - RedisValue host, port; - if (endpoint == null) - { - host = "NO"; - port = "ONE"; - } - else - { - string hostRaw; - int portRaw; - if (Format.TryGetHostPort(endpoint, out hostRaw, out portRaw)) - { - host = hostRaw; - port = portRaw; - } - else - { - throw new NotSupportedException("Unknown endpoint type: " + endpoint.GetType().Name); - } - } - return Message.Create(-1, flags, RedisCommand.SLAVEOF, host, port); - } - - internal override Task ExecuteAsync(Message message, ResultProcessor processor, ServerEndPoint server = null) - { // inject our expected server automatically - if (server == null) server = this.server; - FixFlags(message, server); - if (!server.IsConnected) - { - if (message == null) return CompletedTask.Default(asyncState); - if (message.IsFireAndForget) return CompletedTask.Default(null); // F+F explicitly does not get async-state - - // no need to deny exec-sync here; will be complete before they see if - var tcs = TaskSource.Create(asyncState); - ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, multiplexer.IncludePerformanceCountersInExceptions, message.Command, message, server, multiplexer.GetServerSnapshot())); - return tcs.Task; - } - return base.ExecuteAsync(message, processor, server); - } - - internal override T ExecuteSync(Message message, ResultProcessor processor, ServerEndPoint server = null) - { // inject our expected server automatically - if (server == null) server = this.server; - FixFlags(message, server); - if (!server.IsConnected) - { - if (message == null || message.IsFireAndForget) return default(T); - throw ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, multiplexer.IncludePerformanceCountersInExceptions, message.Command, message, server, multiplexer.GetServerSnapshot()); - } - return base.ExecuteSync(message, processor, server); - } - - internal override RedisFeatures GetFeatures(int db, RedisKey key, CommandFlags flags, out ServerEndPoint server) - { - server = this.server; - return new RedisFeatures(server.Version); - } - - public void SlaveOf(EndPoint endpoint, CommandFlags flags = CommandFlags.None) - { - if (endpoint == server.EndPoint) - { - throw new ArgumentException("Cannot slave to self"); - } - // prepare the actual slaveof message (not sent yet) - var slaveofMsg = CreateSlaveOfMessage(endpoint, flags); - - var configuration = this.multiplexer.RawConfig; - - - // attempt to cease having an opinion on the master; will resume that when replication completes - // (note that this may fail; we aren't depending on it) - if (!string.IsNullOrWhiteSpace(configuration.TieBreaker) - && this.multiplexer.CommandMap.IsAvailable(RedisCommand.DEL)) - { - var del = Message.Create(0, CommandFlags.FireAndForget | CommandFlags.NoRedirect, RedisCommand.DEL, (RedisKey)configuration.TieBreaker); - del.SetInternalCall(); - server.QueueDirectFireAndForget(del, ResultProcessor.Boolean); - } - ExecuteSync(slaveofMsg, ResultProcessor.DemandOK); - - // attempt to broadcast a reconfigure message to anybody listening to this server - var channel = this.multiplexer.ConfigurationChangedChannel; - if (channel != null && this.multiplexer.CommandMap.IsAvailable(RedisCommand.PUBLISH)) - { - var pub = Message.Create(-1, CommandFlags.FireAndForget | CommandFlags.NoRedirect, RedisCommand.PUBLISH, (RedisValue)channel, RedisLiterals.Wildcard); - pub.SetInternalCall(); - server.QueueDirectFireAndForget(pub, ResultProcessor.Int64); - } - } - - public Task SlaveOfAsync(EndPoint endpoint, CommandFlags flags = CommandFlags.None) - { - var msg = CreateSlaveOfMessage(endpoint, flags); - if (endpoint == server.EndPoint) - { - throw new ArgumentException("Cannot slave to self"); - } - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - private void FixFlags(Message message, ServerEndPoint server) - { - // since the server is specified explicitly, we don't want defaults - // to make the "non-preferred-endpoint" counters look artificially - // inflated; note we only change *prefer* options - switch (Message.GetMasterSlaveFlags(message.Flags)) - { - case CommandFlags.PreferMaster: - if (server.IsSlave) message.SetPreferSlave(); - break; - case CommandFlags.PreferSlave: - if (!server.IsSlave) message.SetPreferMaster(); - break; - } - } - - Message GetSaveMessage(SaveType type, CommandFlags flags = CommandFlags.None) - { - switch(type) - { - case SaveType.BackgroundRewriteAppendOnlyFile: return Message.Create(-1, flags, RedisCommand.BGREWRITEAOF); - case SaveType.BackgroundSave: return Message.Create(-1, flags, RedisCommand.BGSAVE); -#pragma warning disable 0618 - case SaveType.ForegroundSave: return Message.Create(-1, flags, RedisCommand.SAVE); -#pragma warning restore 0618 - default: throw new ArgumentOutOfRangeException(nameof(type)); - } - } - - ResultProcessor GetSaveResultProcessor(SaveType type) - { - switch (type) - { - case SaveType.BackgroundRewriteAppendOnlyFile: return ResultProcessor.DemandOK; - case SaveType.BackgroundSave: return ResultProcessor.BackgroundSaveStarted; -#pragma warning disable 0618 - case SaveType.ForegroundSave: return ResultProcessor.DemandOK; -#pragma warning restore 0618 - default: throw new ArgumentOutOfRangeException(nameof(type)); - } - } - - static class ScriptHash - { - static readonly byte[] hex = { - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', - (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f' }; - public static RedisValue Encode(byte[] value) - { - if (value == null) return default(RedisValue); - byte[] result = new byte[value.Length * 2]; - int offset = 0; - for (int i = 0; i < value.Length; i++) - { - int val = value[i]; - result[offset++] = hex[val / 16]; - result[offset++] = hex[val % 16]; - } - return result; - } - public static RedisValue Hash(string value) - { - if (value == null) return default(RedisValue); - using (var sha1 = SHA1.Create()) - { - var bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(value)); - return Encode(bytes); - } - } - } - - sealed class KeysScanEnumerable : CursorEnumerable - { - private readonly RedisValue pattern; - - public KeysScanEnumerable(RedisServer server, int db, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) - : base(server, server.server, db, pageSize, cursor, pageOffset, flags) - { - this.pattern = pattern; - } - - protected override Message CreateMessage(long cursor) - { - if (CursorUtils.IsNil(pattern)) - { - if (pageSize == CursorUtils.DefaultPageSize) - { - return Message.Create(db, flags, RedisCommand.SCAN, cursor); - } - else - { - return Message.Create(db, flags, RedisCommand.SCAN, cursor, RedisLiterals.COUNT, pageSize); - } - } - else - { - if (pageSize == CursorUtils.DefaultPageSize) - { - return Message.Create(db, flags, RedisCommand.SCAN, cursor, RedisLiterals.MATCH, pattern); - } - else - { - return Message.Create(db, flags, RedisCommand.SCAN, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize); - } - } - } - protected override ResultProcessor Processor => processor; - - public static readonly ResultProcessor processor = new KeysResultProcessor(); - private class KeysResultProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.MultiBulk: - var arr = result.GetItems(); - long i64; - if (arr.Length == 2 && arr[1].Type == ResultType.MultiBulk && arr[0].TryGetInt64(out i64)) - { - var keysResult = new ScanResult(i64, arr[1].GetItemsAsKeys()); - SetResult(message, keysResult); - return true; - } - break; - } - return false; - } - } - } - - #region Sentinel - - public EndPoint SentinelGetMasterAddressByName(string serviceName, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.GETMASTERADDRBYNAME, (RedisValue)serviceName); - return ExecuteSync(msg, ResultProcessor.SentinelMasterEndpoint); - } - - public Task SentinelGetMasterAddressByNameAsync(string serviceName, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.GETMASTERADDRBYNAME, (RedisValue)serviceName); - return ExecuteAsync(msg, ResultProcessor.SentinelMasterEndpoint); - } - - public KeyValuePair[] SentinelMaster(string serviceName, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTER, (RedisValue)serviceName); - return ExecuteSync(msg, ResultProcessor.StringPairInterleaved); - } - - public Task[]> SentinelMasterAsync(string serviceName, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTER, (RedisValue)serviceName); - return ExecuteAsync(msg, ResultProcessor.StringPairInterleaved); - } - - public void SentinelFailover(string serviceName, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.FAILOVER, (RedisValue)serviceName); - ExecuteSync(msg, ResultProcessor.DemandOK); - } - - public Task SentinelFailoverAsync(string serviceName, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.FAILOVER, (RedisValue)serviceName); - return ExecuteAsync(msg, ResultProcessor.DemandOK); - } - - public KeyValuePair[][] SentinelMasters(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTERS); - return ExecuteSync(msg, ResultProcessor.SentinelArrayOfArrays); - } - - public Task[][]> SentinelMastersAsync(CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTERS); - return ExecuteAsync(msg, ResultProcessor.SentinelArrayOfArrays); - } - - public KeyValuePair[][] SentinelSlaves(string serviceName, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SLAVES, (RedisValue)serviceName); - return ExecuteSync(msg, ResultProcessor.SentinelArrayOfArrays); - } - - public Task[][]> SentinelSlavesAsync(string serviceName, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SLAVES, (RedisValue)serviceName); - return ExecuteAsync(msg, ResultProcessor.SentinelArrayOfArrays); - } - - #endregion - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisSubscriber.cs b/StackExchange.Redis/StackExchange/Redis/RedisSubscriber.cs deleted file mode 100644 index 25dbed286..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisSubscriber.cs +++ /dev/null @@ -1,347 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - partial class ConnectionMultiplexer - { - private readonly Dictionary subscriptions = new Dictionary(); - - internal static bool TryCompleteHandler(EventHandler handler, object sender, T args, bool isAsync) where T : EventArgs - { - if (handler == null) return true; - if (isAsync) - { - foreach (EventHandler sub in handler.GetInvocationList()) - { - try - { sub.Invoke(sender, args); } - catch - { } - } - return true; - } - return false; - } - - internal Task AddSubscription(RedisChannel channel, Action handler, CommandFlags flags, object asyncState) - { - if (handler != null) - { - lock (subscriptions) - { - Subscription sub; - if (subscriptions.TryGetValue(channel, out sub)) - { - sub.Add(handler); - } - else - { - sub = new Subscription(handler); - subscriptions.Add(channel, sub); - var task = sub.SubscribeToServer(this, channel, flags, asyncState, false); - if (task != null) return task; - } - - } - } - return CompletedTask.Default(asyncState); - } - - internal ServerEndPoint GetSubscribedServer(RedisChannel channel) - { - if (!channel.IsNullOrEmpty) - { - lock (subscriptions) - { - Subscription sub; - if (subscriptions.TryGetValue(channel, out sub)) - { - return sub.GetOwner(); - } - } - } - return null; - } - - internal void OnMessage(RedisChannel subscription, RedisChannel channel, RedisValue payload) - { - ICompletable completable = null; - lock (subscriptions) - { - Subscription sub; - if (subscriptions.TryGetValue(subscription, out sub)) - { - completable = sub.ForInvoke(channel, payload); - } - } - if (completable != null) unprocessableCompletionManager.CompleteSyncOrAsync(completable); - } - - internal Task RemoveAllSubscriptions(CommandFlags flags, object asyncState) - { - Task last = CompletedTask.Default(asyncState); - lock (subscriptions) - { - foreach (var pair in subscriptions) - { - pair.Value.Remove(null); // always wipes - var task = pair.Value.UnsubscribeFromServer(pair.Key, flags, asyncState, false); - if (task != null) last = task; - } - subscriptions.Clear(); - } - return last; - } - - internal Task RemoveSubscription(RedisChannel channel, Action handler, CommandFlags flags, object asyncState) - { - lock (subscriptions) - { - Subscription sub; - if (subscriptions.TryGetValue(channel, out sub)) - { - if (sub.Remove(handler)) - { - subscriptions.Remove(channel); - var task = sub.UnsubscribeFromServer(channel, flags, asyncState, false); - if (task != null) return task; - } - } - } - return CompletedTask.Default(asyncState); - } - - internal void ResendSubscriptions(ServerEndPoint server) - { - if (server == null) return; - lock (subscriptions) - { - foreach (var pair in subscriptions) - { - pair.Value.Resubscribe(pair.Key, server); - } - } - } - - internal bool SubscriberConnected(RedisChannel channel = default(RedisChannel)) - { - var server = GetSubscribedServer(channel); - if (server != null) return server.IsConnected; - - server = SelectServer(-1, RedisCommand.SUBSCRIBE, CommandFlags.DemandMaster, default(RedisKey)); - return server != null && server.IsConnected; - } - - - - internal long ValidateSubscriptions() - { - lock (subscriptions) - { - long count = 0; - foreach (var pair in subscriptions) - { - if (pair.Value.Validate(this, pair.Key)) count++; - } - return count; - } - } - - private sealed class Subscription - { - private Action handler; - private ServerEndPoint owner; - - public Subscription(Action value) - { - handler = value; - } - public void Add(Action value) - { - handler += value; - } - public ICompletable ForInvoke(RedisChannel channel, RedisValue message) - { - var tmp = handler; - return tmp == null ? null : new MessageCompletable(channel, message, tmp); - } - - public bool Remove(Action value) - { - if (value == null) - { // treat as blanket wipe - handler = null; - return true; - } - else - { - return (handler -= value) == null; - } - } - public Task SubscribeToServer(ConnectionMultiplexer multiplexer, RedisChannel channel, CommandFlags flags, object asyncState, bool internalCall) - { - var cmd = channel.IsPatternBased ? RedisCommand.PSUBSCRIBE : RedisCommand.SUBSCRIBE; - var selected = multiplexer.SelectServer(-1, cmd, flags, default(RedisKey)); - - if (selected == null || Interlocked.CompareExchange(ref owner, selected, null) != null) return null; - - var msg = Message.Create(-1, flags, cmd, channel); - - return selected.QueueDirectAsync(msg, ResultProcessor.TrackSubscriptions, asyncState); - } - - public Task UnsubscribeFromServer(RedisChannel channel, CommandFlags flags, object asyncState, bool internalCall) - { - var oldOwner = Interlocked.Exchange(ref owner, null); - if (oldOwner == null) return null; - - var cmd = channel.IsPatternBased ? RedisCommand.PUNSUBSCRIBE : RedisCommand.UNSUBSCRIBE; - var msg = Message.Create(-1, flags, cmd, channel); - if (internalCall) msg.SetInternalCall(); - return oldOwner.QueueDirectAsync(msg, ResultProcessor.TrackSubscriptions, asyncState); - } - - internal ServerEndPoint GetOwner() - { - return Interlocked.CompareExchange(ref owner, null, null); - } - internal void Resubscribe(RedisChannel channel, ServerEndPoint server) - { - if (server != null && Interlocked.CompareExchange(ref owner, server, server) == server) - { - var cmd = channel.IsPatternBased ? RedisCommand.PSUBSCRIBE : RedisCommand.SUBSCRIBE; - var msg = Message.Create(-1, CommandFlags.FireAndForget, cmd, channel); - msg.SetInternalCall(); - server.QueueDirectFireAndForget(msg, ResultProcessor.TrackSubscriptions); - } - } - - internal bool Validate(ConnectionMultiplexer multiplexer, RedisChannel channel) - { - bool changed = false; - var oldOwner = Interlocked.CompareExchange(ref owner, null, null); - if (oldOwner != null && !oldOwner.IsSelectable(RedisCommand.PSUBSCRIBE)) - { - if (UnsubscribeFromServer(channel, CommandFlags.FireAndForget, null, true) != null) - { - changed = true; - } - oldOwner = null; - } - if (oldOwner == null) - { - if (SubscribeToServer(multiplexer, channel, CommandFlags.FireAndForget, null, true) != null) - { - changed = true; - } - } - return changed; - } - - - } - } - - internal sealed class RedisSubscriber : RedisBase, ISubscriber - { - internal RedisSubscriber(ConnectionMultiplexer multiplexer, object asyncState) : base(multiplexer, asyncState) - { - } - - public EndPoint IdentifyEndpoint(RedisChannel channel, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); - msg.SetInternalCall(); - return ExecuteSync(msg, ResultProcessor.ConnectionIdentity); - } - - public Task IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None) - { - var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); - msg.SetInternalCall(); - return ExecuteAsync(msg, ResultProcessor.ConnectionIdentity); - } - - public bool IsConnected(RedisChannel channel = default(RedisChannel)) - { - return multiplexer.SubscriberConnected(channel); - } - - public override TimeSpan Ping(CommandFlags flags = CommandFlags.None) - { - // can't use regular PING, but we can unsubscribe from something random that we weren't even subscribed to... - RedisValue channel = Guid.NewGuid().ToByteArray(); - var msg = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.UNSUBSCRIBE, channel); - return ExecuteSync(msg, ResultProcessor.ResponseTimer); - } - - public override Task PingAsync(CommandFlags flags = CommandFlags.None) - { - // can't use regular PING, but we can unsubscribe from something random that we weren't even subscribed to... - RedisValue channel = Guid.NewGuid().ToByteArray(); - var msg = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.UNSUBSCRIBE, channel); - return ExecuteAsync(msg, ResultProcessor.ResponseTimer); - } - - public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) - { - if (channel.IsNullOrEmpty) throw new ArgumentNullException(nameof(channel)); - var msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, message); - return ExecuteSync(msg, ResultProcessor.Int64); - } - - public Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) - { - if (channel.IsNullOrEmpty) throw new ArgumentNullException(nameof(channel)); - var msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, message); - return ExecuteAsync(msg, ResultProcessor.Int64); - } - - public void Subscribe(RedisChannel channel, Action handler, CommandFlags flags = CommandFlags.None) - { - var task = SubscribeAsync(channel, handler, flags); - if ((flags & CommandFlags.FireAndForget) == 0) Wait(task); - } - - public Task SubscribeAsync(RedisChannel channel, Action handler, CommandFlags flags = CommandFlags.None) - { - - if (channel.IsNullOrEmpty) throw new ArgumentNullException(nameof(channel)); - return multiplexer.AddSubscription(channel, handler, flags, asyncState); - } - - - public EndPoint SubscribedEndpoint(RedisChannel channel) - { - var server = multiplexer.GetSubscribedServer(channel); - return server?.EndPoint; - } - - public void Unsubscribe(RedisChannel channel, Action handler = null, CommandFlags flags = CommandFlags.None) - { - var task = UnsubscribeAsync(channel, handler, flags); - if ((flags & CommandFlags.FireAndForget) == 0) Wait(task); - } - - public void UnsubscribeAll(CommandFlags flags = CommandFlags.None) - { - var task = UnsubscribeAllAsync(flags); - if ((flags & CommandFlags.FireAndForget) == 0) Wait(task); - } - - public Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None) - { - return multiplexer.RemoveAllSubscriptions(flags, asyncState); - } - - public Task UnsubscribeAsync(RedisChannel channel, Action handler = null, CommandFlags flags = CommandFlags.None) - { - if (channel.IsNullOrEmpty) throw new ArgumentNullException(nameof(channel)); - return multiplexer.RemoveSubscription(channel, handler, flags, asyncState); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisTransaction.cs b/StackExchange.Redis/StackExchange/Redis/RedisTransaction.cs deleted file mode 100644 index e15bf88d5..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisTransaction.cs +++ /dev/null @@ -1,487 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - internal class RedisTransaction : RedisDatabase, ITransaction - { - private List conditions; - - private List pending; - - public RedisTransaction(RedisDatabase wrapped, object asyncState) : base(wrapped.multiplexer, wrapped.Database, asyncState ?? wrapped.AsyncState) - { - // need to check we can reliably do this... - var commandMap = multiplexer.CommandMap; - commandMap.AssertAvailable(RedisCommand.MULTI); - commandMap.AssertAvailable(RedisCommand.EXEC); - commandMap.AssertAvailable(RedisCommand.DISCARD); - } - - public ConditionResult AddCondition(Condition condition) - { - if (condition == null) throw new ArgumentNullException(nameof(condition)); - - var commandMap = multiplexer.CommandMap; - if (conditions == null) - { - // we don't demand these unless the user is requesting conditions, but we need both... - commandMap.AssertAvailable(RedisCommand.WATCH); - commandMap.AssertAvailable(RedisCommand.UNWATCH); - conditions = new List(); - } - condition.CheckCommands(commandMap); - var result = new ConditionResult(condition); - conditions.Add(result); - return result; - } - - public void Execute() - { - Execute(CommandFlags.FireAndForget); - } - - public bool Execute(CommandFlags flags) - { - ResultProcessor proc; - var msg = CreateMessage(flags, out proc); - return base.ExecuteSync(msg, proc); // need base to avoid our local "not supported" override - } - - public Task ExecuteAsync(CommandFlags flags) - { - ResultProcessor proc; - var msg = CreateMessage(flags, out proc); - return base.ExecuteAsync(msg, proc); // need base to avoid our local wrapping override - } - - internal override Task ExecuteAsync(Message message, ResultProcessor processor, ServerEndPoint server = null) - { - if (message == null) return CompletedTask.Default(asyncState); - multiplexer.CheckMessage(message); - - multiplexer.Trace("Wrapping " + message.Command, "Transaction"); - // prepare the inner command as a task - Task task; - if (message.IsFireAndForget) - { - task = CompletedTask.Default(null); // F+F explicitly does not get async-state - } - else - { - var tcs = TaskSource.CreateDenyExecSync(asyncState); - var source = ResultBox.Get(tcs); - message.SetSource(source, processor); - task = tcs.Task; - } - - // prepare an outer-command that decorates that, but expects QUEUED - var queued = new QueuedMessage(message); - var wasQueued = ResultBox.Get(null); - queued.SetSource(wasQueued, QueuedProcessor.Default); - - // store it, and return the task of the *outer* command - // (there is no task for the inner command) - (pending ?? (pending = new List())).Add(queued); - - - switch(message.Command) - { - case RedisCommand.UNKNOWN: - case RedisCommand.EVAL: - case RedisCommand.EVALSHA: - // people can do very naughty things in an EVAL - // including change the DB; change it back to what we - // think it should be! - var sel = PhysicalConnection.GetSelectDatabaseCommand(message.Db); - queued = new QueuedMessage(sel); - wasQueued = ResultBox.Get(null); - queued.SetSource(wasQueued, QueuedProcessor.Default); - pending.Add(queued); - break; - } - return task; - } - - internal override T ExecuteSync(Message message, ResultProcessor processor, ServerEndPoint server = null) - { - throw new NotSupportedException("ExecuteSync cannot be used inside a transaction"); - } - private Message CreateMessage(CommandFlags flags, out ResultProcessor processor) - { - var work = pending; - pending = null; // any new operations go into a different queue - var cond = conditions; - conditions = null; // any new conditions go into a different queue - - if ((work == null || work.Count == 0) && (cond == null || cond.Count == 0)) - { - if ((flags & CommandFlags.FireAndForget) != 0) - { - processor = null; - return null; // they won't notice if we don't do anything... - } - processor = ResultProcessor.DemandPONG; - return Message.Create(-1, flags, RedisCommand.PING); - } - processor = TransactionProcessor.Default; - return new TransactionMessage(Database, flags, cond, work); - } - class QueuedMessage : Message - { - private readonly Message wrapped; - private volatile bool wasQueued; - - public QueuedMessage(Message message) : base(message.Db, message.Flags | CommandFlags.NoRedirect, message.Command) - { - message.SetNoRedirect(); - this.wrapped = message; - } - - public bool WasQueued - { - get { return wasQueued; } - set { wasQueued = value; } - } - - public Message Wrapped => wrapped; - - internal override void WriteImpl(PhysicalConnection physical) - { - wrapped.WriteImpl(physical); - wrapped.SetRequestSent(); - } - } - - class QueuedProcessor : ResultProcessor - { - public static readonly ResultProcessor Default = new QueuedProcessor(); - static readonly byte[] QUEUED = Encoding.UTF8.GetBytes("QUEUED"); - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - if(result.Type == ResultType.SimpleString && result.IsEqual(QUEUED)) - { - var q = message as QueuedMessage; - if (q != null) q.WasQueued = true; - return true; - } - return false; - } - } - class TransactionMessage : Message, IMultiMessage - { - - static readonly ConditionResult[] NixConditions = new ConditionResult[0]; - - static readonly QueuedMessage[] NixMessages = new QueuedMessage[0]; - - private ConditionResult[] conditions; - - private QueuedMessage[] operations; - - public TransactionMessage(int db, CommandFlags flags, List conditions, List operations) - : base(db, flags, RedisCommand.EXEC) - { - this.operations = (operations == null || operations.Count == 0) ? NixMessages : operations.ToArray(); - this.conditions = (conditions == null || conditions.Count == 0) ? NixConditions : conditions.ToArray(); - } - - public QueuedMessage[] InnerOperations => operations; - - public bool IsAborted => command != RedisCommand.EXEC; - - public override void AppendStormLog(StringBuilder sb) - { - base.AppendStormLog(sb); - if (conditions.Length != 0) sb.Append(", ").Append(conditions.Length).Append(" conditions"); - sb.Append(", ").Append(operations.Length).Append(" operations"); - } - public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) - { - int slot = ServerSelectionStrategy.NoSlot; - for(int i = 0; i < conditions.Length;i++) - { - int newSlot = conditions[i].Condition.GetHashSlot(serverSelectionStrategy); - slot = serverSelectionStrategy.CombineSlot(slot, newSlot); - if (slot == ServerSelectionStrategy.MultipleSlots) return slot; - } - for(int i = 0; i < operations.Length;i++) - { - int newSlot = operations[i].Wrapped.GetHashSlot(serverSelectionStrategy); - slot = serverSelectionStrategy.CombineSlot(slot, newSlot); - if (slot == ServerSelectionStrategy.MultipleSlots) return slot; - } - return slot; - } - - public IEnumerable GetMessages(PhysicalConnection connection) - { - ResultBox lastBox = null; - try - { - // Important: if the server supports EXECABORT, then we can check the pre-conditions (pause there), - // which will usually be pretty small and cheap to do - if that passes, we can just isue all the commands - // and rely on EXECABORT to kick us if we are being idiotic inside the MULTI. However, if the server does - // *not* support EXECABORT, then we need to explicitly check for QUEUED anyway; we might as well defer - // checking the preconditions to the same time to avoid having to pause twice. This will mean that on - // up-version servers, pre-condition failures exit with UNWATCH; and on down-version servers pre-condition - // failures exit with DISCARD - but that's ok : both work fine - - bool explicitCheckForQueued = !connection.Bridge.ServerEndPoint.GetFeatures().ExecAbort; - var multiplexer = connection.Multiplexer; - - // PART 1: issue the pre-conditions - if (!IsAborted && conditions.Length != 0) - { - for (int i = 0; i < conditions.Length; i++) - { - // need to have locked them before sending them - // to guarantee that we see the pulse - ResultBox latestBox = conditions[i].GetBox(); - Monitor.Enter(latestBox); - if (lastBox != null) Monitor.Exit(lastBox); - lastBox = latestBox; - foreach (var msg in conditions[i].CreateMessages(Db)) - { - msg.SetNoRedirect(); // need to keep them in the current context only - yield return msg; - } - } - - if (!explicitCheckForQueued && lastBox != null) - { - // need to get those sent ASAP; if they are stuck in the buffers, we die - multiplexer.Trace("Flushing and waiting for precondition responses"); - connection.Flush(); - if (Monitor.Wait(lastBox, multiplexer.TimeoutMilliseconds)) - { - if (!AreAllConditionsSatisfied(multiplexer)) - command = RedisCommand.UNWATCH; // somebody isn't happy - } - else - { // timeout running pre-conditions - multiplexer.Trace("Timeout checking preconditions"); - command = RedisCommand.UNWATCH; - } - Monitor.Exit(lastBox); - lastBox = null; - } - } - - // PART 2: begin the transaction - if (!IsAborted) - { - multiplexer.Trace("Begining transaction"); - yield return Message.Create(-1, CommandFlags.None, RedisCommand.MULTI); - } - - // PART 3: issue the commands - if (!IsAborted && operations.Length != 0) - { - multiplexer.Trace("Issuing transaction operations"); - - foreach (var op in operations) - { - if (explicitCheckForQueued) - { // need to have locked them before sending them - // to guarantee that we see the pulse - ResultBox thisBox = op.ResultBox; - if (thisBox != null) - { - Monitor.Enter(thisBox); - if (lastBox != null) Monitor.Exit(lastBox); - lastBox = thisBox; - } - } - yield return op; - } - - if (explicitCheckForQueued && lastBox != null) - { - multiplexer.Trace("Flushing and waiting for precondition+queued responses"); - connection.Flush(); // make sure they get sent, so we can check for QUEUED (and the pre-conditions if necessary) - if (Monitor.Wait(lastBox, multiplexer.TimeoutMilliseconds)) - { - if (!AreAllConditionsSatisfied(multiplexer)) - { - command = RedisCommand.DISCARD; - } - else - { - foreach (var op in operations) - { - if (!op.WasQueued) - { - multiplexer.Trace("Aborting: operation was not queued: " + op.Command); - command = RedisCommand.DISCARD; - break; - } - } - } - multiplexer.Trace("Confirmed: QUEUED x " + operations.Length); - } - else - { - multiplexer.Trace("Aborting: timeout checking queued messages"); - command = RedisCommand.DISCARD; - } - Monitor.Exit(lastBox); - lastBox = null; - } - } - } - finally - { - if (lastBox != null) Monitor.Exit(lastBox); - } - if (IsAborted) - { - connection.Multiplexer.Trace("Aborting: canceling wrapped messages"); - var bridge = connection.Bridge; - foreach (var op in operations) - { - op.Wrapped.Cancel(); - bridge.CompleteSyncOrAsync(op.Wrapped); - } - } - connection.Multiplexer.Trace("End ot transaction: " + Command); - yield return this; // acts as either an EXEC or an UNWATCH, depending on "aborted" - } - - internal override void WriteImpl(PhysicalConnection physical) - { - physical.WriteHeader(Command, 0); - } - - private bool AreAllConditionsSatisfied(ConnectionMultiplexer multiplexer) - { - bool result = true; - for (int i = 0; i < conditions.Length; i++) - { - var condition = conditions[i]; - if (condition.UnwrapBox()) - { - multiplexer.Trace("Precondition passed: " + condition.Condition); - } - else - { - multiplexer.Trace("Precondition failed: " + condition.Condition); - result = false; - } - } - return result; - } - } - - class TransactionProcessor : ResultProcessor - { - public static readonly TransactionProcessor Default = new TransactionProcessor(); - - public override bool SetResult(PhysicalConnection connection, Message message, RawResult result) - { - if (result.IsError) - { - var tran = message as TransactionMessage; - if (tran != null) - { - string error = result.GetString(); - var bridge = connection.Bridge; - foreach(var op in tran.InnerOperations) - { - ServerFail(op.Wrapped, error); - bridge.CompleteSyncOrAsync(op.Wrapped); - } - } - } - return base.SetResult(connection, message, result); - } - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - var tran = message as TransactionMessage; - if (tran != null) - { - var bridge = connection.Bridge; - var wrapped = tran.InnerOperations; - switch (result.Type) - { - case ResultType.SimpleString: - if (tran.IsAborted && result.IsEqual(RedisLiterals.BytesOK)) - { - connection.Multiplexer.Trace("Acknowledging UNWATCH (aborted electively)"); - SetResult(message, false); - return true; - } - //EXEC returned with a NULL - if (!tran.IsAborted && result.IsNull) - { - connection.Multiplexer.Trace("Server aborted due to failed EXEC"); - //cancel the commands in the transaction and mark them as complete with the completion manager - foreach (var op in wrapped) - { - op.Wrapped.Cancel(); - bridge.CompleteSyncOrAsync(op.Wrapped); - } - SetResult(message, false); - return true; - } - break; - case ResultType.MultiBulk: - if (!tran.IsAborted) - { - var arr = result.GetItems(); - if (arr == null) - { - connection.Multiplexer.Trace("Server aborted due to failed WATCH"); - foreach (var op in wrapped) - { - op.Wrapped.Cancel(); - bridge.CompleteSyncOrAsync(op.Wrapped); - } - SetResult(message, false); - return true; - } - else if (wrapped.Length == arr.Length) - { - connection.Multiplexer.Trace("Server committed; processing nested replies"); - for (int i = 0; i < arr.Length; i++) - { - if (wrapped[i].Wrapped.ComputeResult(connection, arr[i])) - { - bridge.CompleteSyncOrAsync(wrapped[i].Wrapped); - } - } - SetResult(message, true); - return true; - } - } - break; - } - // even if we didn't fully understand the result, we still need to do something with - // the pending tasks - foreach (var op in wrapped) - { - op.Wrapped.Fail(ConnectionFailureType.ProtocolFailure, null); - bridge.CompleteSyncOrAsync(op.Wrapped); - } - } - return false; - } - } - } - //internal class RedisDatabaseTransaction : RedisCoreTransaction, ITransaction - //{ - // public IRedisDatabaseAsync Pending { get { return this; } } - - // bool ITransaction.Execute(CommandFlags flags) - // { - // return ExecuteTransaction(flags); - // } - // Task ITransaction.ExecuteAsync(CommandFlags flags) - // { - // return ExecuteTransactionAsync(flags); - // } - //} -} \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/RedisType.cs b/StackExchange.Redis/StackExchange/Redis/RedisType.cs deleted file mode 100644 index 4a51f9369..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisType.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace StackExchange.Redis -{ - /// - /// The intrinsinc data-types supported by redis - /// - /// http://redis.io/topics/data-types - public enum RedisType - { - /// - /// The specified key does not exist - /// - None, - /// - /// Strings are the most basic kind of Redis value. Redis Strings are binary safe, this means that a Redis string can contain any kind of data, for instance a JPEG image or a serialized Ruby object. - /// A String value can be at max 512 Megabytes in length. - /// - /// http://redis.io/commands#string - String, - /// - /// Redis Lists are simply lists of strings, sorted by insertion order. It is possible to add elements to a Redis List pushing new elements on the head (on the left) or on the tail (on the right) of the list. - /// - /// http://redis.io/commands#list - List, - /// - /// Redis Sets are an unordered collection of Strings. It is possible to add, remove, and test for existence of members in O(1) (constant time regardless of the number of elements contained inside the Set). - /// Redis Sets have the desirable property of not allowing repeated members. Adding the same element multiple times will result in a set having a single copy of this element. Practically speaking this means that adding a member does not require a check if exists then add operation. - /// - /// http://redis.io/commands#set - Set, - /// - /// Redis Sorted Sets are, similarly to Redis Sets, non repeating collections of Strings. The difference is that every member of a Sorted Set is associated with score, that is used in order to take the sorted set ordered, from the smallest to the greatest score. While members are unique, scores may be repeated. - /// - /// http://redis.io/commands#sorted_set - SortedSet, - /// - /// Redis Hashes are maps between string fields and string values, so they are the perfect data type to represent objects (eg: A User with a number of fields like name, surname, age, and so forth) - /// - /// http://redis.io/commands#hash - Hash, - /// - /// The data-type was not recognised by the client library - /// - Unknown, - } - -} diff --git a/StackExchange.Redis/StackExchange/Redis/RedisValue.cs b/StackExchange.Redis/StackExchange/Redis/RedisValue.cs deleted file mode 100644 index 6dfe239a0..000000000 --- a/StackExchange.Redis/StackExchange/Redis/RedisValue.cs +++ /dev/null @@ -1,726 +0,0 @@ -using System; -#if CORE_CLR -using System.Collections.Generic; -using System.Reflection; -#endif -using System.Text; - -namespace StackExchange.Redis -{ - /// - /// Represents values that can be stored in redis - /// - public struct RedisValue : IEquatable, IComparable, IComparable, IConvertible - { - internal static readonly RedisValue[] EmptyArray = new RedisValue[0]; - - static readonly byte[] EmptyByteArr = new byte[0]; - - private static readonly byte[] IntegerSentinel = new byte[0]; - - private readonly byte[] valueBlob; - - private readonly long valueInt64; - - // internal bool IsNullOrDefaultValue { get { return (valueBlob == null && valueInt64 == 0L) || ((object)valueBlob == (object)NullSentinel); } } - private RedisValue(long valueInt64, byte[] valueBlob) - { - this.valueInt64 = valueInt64; - this.valueBlob = valueBlob; - } - - /// - /// Represents the string "" - /// - public static RedisValue EmptyString { get; } = new RedisValue(0, EmptyByteArr); - - /// - /// A null value - /// - public static RedisValue Null { get; } = new RedisValue(0, null); - - /// - /// Indicates whether the value is a primitive integer - /// - public bool IsInteger => valueBlob == IntegerSentinel; - - /// - /// Indicates whether the value should be considered a null value - /// - public bool IsNull => valueBlob == null; - - /// - /// Indicates whether the value is either null or a zero-length value - /// - public bool IsNullOrEmpty => valueBlob == null || (valueBlob.Length == 0 && !(valueBlob == IntegerSentinel)); - - /// - /// Indicates whether the value is greater than zero-length - /// - public bool HasValue => valueBlob != null && valueBlob.Length > 0; - - /// - /// Indicates whether two RedisValue values are equivalent - /// - public static bool operator !=(RedisValue x, RedisValue y) - { - return !(x == y); - } - - /// - /// Indicates whether two RedisValue values are equivalent - /// - public static bool operator ==(RedisValue x, RedisValue y) - { - if (x.valueBlob == null) return y.valueBlob == null; - - if (x.valueBlob == IntegerSentinel) - { - if (y.valueBlob == IntegerSentinel) - { - return x.valueInt64 == y.valueInt64; - } - else - { - return Equals((byte[])x, (byte[])y); - } - } - else if (y.valueBlob == IntegerSentinel) - { - return Equals((byte[])x, (byte[])y); - } - - return Equals(x.valueBlob, y.valueBlob); - } - - /// - /// See Object.Equals() - /// - public override bool Equals(object obj) - { - if (obj == null) return valueBlob == null; - - if (obj is RedisValue) - { - return Equals((RedisValue)obj); - } - if (obj is string) - { - return (string)obj == (string)this; - } - if (obj is byte[]) - { - return Equals((byte[])obj, (byte[])this); - } - if (obj is long) - { - return (long)obj == (long)this; - } - if (obj is int) - { - return (int)obj == (int)this; - } - return false; - } - - /// - /// Indicates whether two RedisValue values are equivalent - /// - public bool Equals(RedisValue other) - { - return this == other; - } - - /// - /// See Object.GetHashCode() - /// - public override int GetHashCode() - { - if (valueBlob == IntegerSentinel) return valueInt64.GetHashCode(); - if (valueBlob == null) return -1; - return GetHashCode(valueBlob); - } - - /// - /// Returns a string representation of the value - /// - public override string ToString() - { - return (string)this; - } - - internal static unsafe bool Equals(byte[] x, byte[] y) - { - if ((object)x == (object)y) return true; // ref equals - if (x == null || y == null) return false; - int len = x.Length; - if (len != y.Length) return false; - - int octets = len / 8, spare = len % 8; - fixed (byte* x8 = x, y8 = y) - { - long* x64 = (long*)x8, y64 = (long*)y8; - for (int i = 0; i < octets; i++) - { - if (x64[i] != y64[i]) return false; - } - int offset = len - spare; - while (spare-- != 0) - { - if (x8[offset] != y8[offset++]) return false; - } - } - return true; - } - - internal static unsafe int GetHashCode(byte[] value) - { - unchecked - { - if (value == null) return -1; - int len = value.Length; - if (len == 0) return 0; - int octets = len / 8, spare = len % 8; - int acc = 728271210; - fixed (byte* ptr8 = value) - { - long* ptr64 = (long*)ptr8; - for (int i = 0; i < octets; i++) - { - long val = ptr64[i]; - int valHash = (((int)val) ^ ((int)(val >> 32))); - acc = (((acc << 5) + acc) ^ valHash); - } - int offset = len - spare; - while (spare-- != 0) - { - acc = (((acc << 5) + acc) ^ ptr8[offset++]); - } - } - return acc; - } - } - - internal static bool TryParseInt64(byte[] value, int offset, int count, out long result) - { - result = 0; - if (value == null || count <= 0) return false; - checked - { - int max = offset + count; - if (value[offset] == '-') - { - for (int i = offset + 1; i < max; i++) - { - var b = value[i]; - if (b < '0' || b > '9') return false; - result = (result * 10) - (b - '0'); - } - return true; - } - else - { - for (int i = offset; i < max; i++) - { - var b = value[i]; - if (b < '0' || b > '9') return false; - result = (result * 10) + (b - '0'); - } - return true; - } - } - } - - internal void AssertNotNull() - { - if (IsNull) throw new ArgumentException("A null value is not valid in this context"); - } - - enum CompareType { - Null, Int64, Double, Raw - } - CompareType ResolveType(out long i64, out double r8) - { - byte[] blob = valueBlob; - if (blob == IntegerSentinel) - { - i64 = valueInt64; - r8 = default(double); - return CompareType.Int64; - } - if(blob == null) - { - i64 = default(long); - r8 = default(double); - return CompareType.Null; - } - if(TryParseInt64(blob, 0, blob.Length, out i64)) - { - r8 = default(double); - return CompareType.Int64; - } - if(TryParseDouble(blob, out r8)) - { - i64 = default(long); - return CompareType.Double; - } - i64 = default(long); - r8 = default(double); - return CompareType.Raw; - } - - /// - /// Compare against a RedisValue for relative order - /// - public int CompareTo(RedisValue other) - { - try - { - long thisInt64, otherInt64; - double thisDouble, otherDouble; - CompareType thisType = this.ResolveType(out thisInt64, out thisDouble), - otherType = other.ResolveType(out otherInt64, out otherDouble); - - if(thisType == CompareType.Null) - { - return otherType == CompareType.Null ? 0 : -1; - } - if(otherType == CompareType.Null) - { - return 1; - } - - if(thisType == CompareType.Int64) - { - if (otherType == CompareType.Int64) return thisInt64.CompareTo(otherInt64); - if (otherType == CompareType.Double) return ((double)thisInt64).CompareTo(otherDouble); - } - else if(thisType == CompareType.Double) - { - if (otherType == CompareType.Int64) return thisDouble.CompareTo((double)otherInt64); - if (otherType == CompareType.Double) return thisDouble.CompareTo(otherDouble); - } - // otherwise, compare as strings -#if !CORE_CLR - return StringComparer.InvariantCulture.Compare((string)this, (string)other); -#else - var compareInfo = System.Globalization.CultureInfo.InvariantCulture.CompareInfo; - return compareInfo.Compare((string)this, (string)other, System.Globalization.CompareOptions.Ordinal); -#endif - } - catch(Exception ex) - { - ConnectionMultiplexer.TraceWithoutContext(ex.Message); - } - // if all else fails, consider equivalent - return 0; - } - - int IComparable.CompareTo(object obj) - { - if (obj is RedisValue) return CompareTo((RedisValue)obj); - if (obj is long) return CompareTo((RedisValue)(long)obj); - if (obj is double) return CompareTo((RedisValue)(double)obj); - if (obj is string) return CompareTo((RedisValue)(string)obj); - if (obj is byte[]) return CompareTo((RedisValue)(byte[])obj); - if (obj is bool) return CompareTo((RedisValue)(bool)obj); - return -1; - } - - - /// - /// Creates a new RedisValue from an Int32 - /// - public static implicit operator RedisValue(int value) - { - return new RedisValue(value, IntegerSentinel); - } - /// - /// Creates a new RedisValue from a nullable Int32 - /// - public static implicit operator RedisValue(int? value) - { - return value == null ? Null : (RedisValue)value.GetValueOrDefault(); - } - /// - /// Creates a new RedisValue from an Int64 - /// - public static implicit operator RedisValue(long value) - { - return new RedisValue(value, IntegerSentinel); - } - /// - /// Creates a new RedisValue from a nullable Int64 - /// - public static implicit operator RedisValue(long? value) - { - return value == null ? Null : (RedisValue)value.GetValueOrDefault(); - } - /// - /// Creates a new RedisValue from a Double - /// - public static implicit operator RedisValue(double value) - { - return Format.ToString(value); - } - - /// - /// Creates a new RedisValue from a nullable Double - /// - public static implicit operator RedisValue(double? value) - { - return value == null ? Null : (RedisValue)value.GetValueOrDefault(); - } - /// - /// Creates a new RedisValue from a String - /// - public static implicit operator RedisValue(string value) - { - byte[] blob; - if (value == null) blob = null; - else if (value.Length == 0) blob = EmptyByteArr; - else blob = Encoding.UTF8.GetBytes(value); - return new RedisValue(0, blob); - } - /// - /// Creates a new RedisValue from a Byte[] - /// - public static implicit operator RedisValue(byte[] value) - { - byte[] blob; - if (value == null) blob = null; - else if (value.Length == 0) blob = EmptyByteArr; - else blob = value; - return new RedisValue(0, blob); - } - - internal static RedisValue Parse(object obj) - { - if (obj == null) return RedisValue.Null; - if (obj is RedisValue) return (RedisValue)obj; - if (obj is string) return (RedisValue)(string)obj; - if (obj is int) return (RedisValue)(int)obj; - if (obj is double) return (RedisValue)(double)obj; - if (obj is byte[]) return (RedisValue)(byte[])obj; - if (obj is bool) return (RedisValue)(bool)obj; - if (obj is long) return (RedisValue)(long)obj; - if (obj is float) return (RedisValue)(float)obj; - - throw new InvalidOperationException("Unable to format type for redis: " + obj.GetType().FullName); - } - /// - /// Creates a new RedisValue from a Boolean - /// - public static implicit operator RedisValue(bool value) - { - return new RedisValue(value ? 1 : 0, IntegerSentinel); - } - /// - /// Creates a new RedisValue from a nullable Boolean - /// - public static implicit operator RedisValue(bool? value) - { - return value == null ? Null : (RedisValue)value.GetValueOrDefault(); - } - /// - /// Converts the value to a Boolean - /// - public static explicit operator bool (RedisValue value) - { - switch((long)value) - { - case 0: return false; - case 1: return true; - default: throw new InvalidCastException(); - } - } - - /// - /// Converts the value to an Int32 - /// - public static explicit operator int(RedisValue value) - { - checked - { - return (int)(long)value; - } - } - /// - /// Converts the value to an Int64 - /// - public static explicit operator long(RedisValue value) - { - var blob = value.valueBlob; - if (blob == IntegerSentinel) return value.valueInt64; - if (blob == null) return 0; // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr") - long i64; - if (TryParseInt64(blob, 0, blob.Length, out i64)) return i64; - throw new InvalidCastException(); - } - - /// - /// Converts the value to a Double - /// - public static explicit operator double (RedisValue value) - { - var blob = value.valueBlob; - if (blob == IntegerSentinel) return value.valueInt64; - if (blob == null) return 0; // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr") - - double r8; - if (TryParseDouble(blob, out r8)) return r8; - throw new InvalidCastException(); - } - - static bool TryParseDouble(byte[] blob, out double value) - { - // simple integer? - if (blob.Length == 1 && blob[0] >= '0' && blob[0] <= '9') - { - value = blob[0] - '0'; - return true; - } - - return Format.TryParseDouble(Encoding.UTF8.GetString(blob), out value); - } - - /// - /// Converts the value to a nullable Double - /// - public static explicit operator double? (RedisValue value) - { - if (value.valueBlob == null) return null; - return (double)value; - } - /// - /// Converts the value to a nullable Int64 - /// - public static explicit operator long? (RedisValue value) - { - if (value.valueBlob == null) return null; - return (long)value; - } - /// - /// Converts the value to a nullable Int32 - /// - public static explicit operator int? (RedisValue value) - { - if (value.valueBlob == null) return null; - return (int)value; - } - /// - /// Converts the value to a nullable Boolean - /// - public static explicit operator bool? (RedisValue value) - { - if (value.valueBlob == null) return null; - return (bool)value; - } - - /// - /// Converts the value to a String - /// - public static implicit operator string(RedisValue value) - { - var valueBlob = value.valueBlob; - if (valueBlob == IntegerSentinel) - return Format.ToString(value.valueInt64); - if (valueBlob == null) return null; - - if (valueBlob.Length == 0) return ""; - if (valueBlob.Length == 2 && valueBlob[0] == (byte)'O' && valueBlob[1] == (byte)'K') - { - return "OK"; // special case for +OK status results from modules - } - try - { - return Encoding.UTF8.GetString(valueBlob); - } - catch - { - return BitConverter.ToString(valueBlob); - } - } - /// - /// Converts the value to a byte[] - /// - public static implicit operator byte[](RedisValue value) - { - var valueBlob = value.valueBlob; - if (valueBlob == IntegerSentinel) - { - return Encoding.UTF8.GetBytes(Format.ToString(value.valueInt64)); - } - return valueBlob; - } - - TypeCode IConvertible.GetTypeCode() => TypeCode.Object; - - bool IConvertible.ToBoolean(IFormatProvider provider) => (bool)this; - - byte IConvertible.ToByte(IFormatProvider provider) => (byte)this; - - char IConvertible.ToChar(IFormatProvider provider) => (char)this; - - DateTime IConvertible.ToDateTime(IFormatProvider provider) => DateTime.Parse((string)this, provider); - - decimal IConvertible.ToDecimal(IFormatProvider provider) => (decimal)this; - - double IConvertible.ToDouble(IFormatProvider provider) => (double)this; - - short IConvertible.ToInt16(IFormatProvider provider) => (short)this; - - int IConvertible.ToInt32(IFormatProvider provider) => (int)this; - - long IConvertible.ToInt64(IFormatProvider provider) => (long)this; - - sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)this; - - float IConvertible.ToSingle(IFormatProvider provider) => (float)this; - - string IConvertible.ToString(IFormatProvider provider) => (string)this; - - object IConvertible.ToType(Type conversionType, IFormatProvider provider) - { - if (conversionType== null) throw new ArgumentNullException(nameof(conversionType)); - if (conversionType== typeof(byte[])) return (byte[])this; - if (conversionType == typeof(RedisValue)) return this; - switch(conversionType.GetTypeCode()) - { - case TypeCode.Boolean: return (bool)this; - case TypeCode.Byte: return (byte)this; - case TypeCode.Char: return (char)this; - case TypeCode.DateTime: return DateTime.Parse((string)this, provider); - case TypeCode.Decimal: return (decimal)this; - case TypeCode.Double: return (double)this; - case TypeCode.Int16: return (short)this; - case TypeCode.Int32: return (int)this; - case TypeCode.Int64: return (long)this; - case TypeCode.SByte: return (sbyte)this; - case TypeCode.Single: return (float)this; - case TypeCode.String: return (string)this; - case TypeCode.UInt16: return (ushort)this; - case TypeCode.UInt32: return (uint)this; - case TypeCode.UInt64: return (long)this; - case TypeCode.Object: return this; - default: - throw new NotSupportedException(); - } - } - - ushort IConvertible.ToUInt16(IFormatProvider provider) => (ushort)this; - - uint IConvertible.ToUInt32(IFormatProvider provider) => (uint)this; - - ulong IConvertible.ToUInt64(IFormatProvider provider) => (ulong)this; - - /// - /// Convert to a long if possible, returning true. - /// - /// Returns false otherwise. - /// - public bool TryParse(out long val) - { - var blob = valueBlob; - if (blob == IntegerSentinel) - { - val = valueInt64; - return true; - } - - if (blob == null) - { - // in redis-land 0 approx. equal null; so roll with it - val = 0; - return true; - } - - return TryParseInt64(blob, 0, blob.Length, out val); - } - - /// - /// Convert to a int if possible, returning true. - /// - /// Returns false otherwise. - /// - public bool TryParse(out int val) - { - long l; - if (!TryParse(out l) || l > int.MaxValue || l < int.MinValue) - { - val = 0; - return false; - } - - val = (int)l; - return true; - } - - /// - /// Convert to a double if possible, returning true. - /// - /// Returns false otherwise. - /// - public bool TryParse(out double val) - { - var blob = valueBlob; - if (blob == IntegerSentinel) - { - val = valueInt64; - return true; - } - if (blob == null) - { - // in redis-land 0 approx. equal null; so roll with it - val = 0; - return true; - } - - return TryParseDouble(blob, out val); - } - } - - internal static class ReflectionExtensions - { -#if CORE_CLR - internal static TypeCode GetTypeCode(this Type type) - { - if (type == null) return TypeCode.Empty; - TypeCode result; - if (typeCodeLookup.TryGetValue(type, out result)) return result; - - if (type.GetTypeInfo().IsEnum) - { - type = Enum.GetUnderlyingType(type); - if (typeCodeLookup.TryGetValue(type, out result)) return result; - } - return TypeCode.Object; - } - - static readonly Dictionary typeCodeLookup = new Dictionary - { - {typeof(bool), TypeCode.Boolean }, - {typeof(byte), TypeCode.Byte }, - {typeof(char), TypeCode.Char}, - {typeof(DateTime), TypeCode.DateTime}, - {typeof(decimal), TypeCode.Decimal}, - {typeof(double), TypeCode.Double }, - {typeof(short), TypeCode.Int16 }, - {typeof(int), TypeCode.Int32 }, - {typeof(long), TypeCode.Int64 }, - {typeof(object), TypeCode.Object}, - {typeof(sbyte), TypeCode.SByte }, - {typeof(float), TypeCode.Single }, - {typeof(string), TypeCode.String }, - {typeof(ushort), TypeCode.UInt16 }, - {typeof(uint), TypeCode.UInt32 }, - {typeof(ulong), TypeCode.UInt64 }, - }; -#else - internal static TypeCode GetTypeCode(this Type type) - { - return Type.GetTypeCode(type); - } -#endif - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ReplicationChangeOptions.cs b/StackExchange.Redis/StackExchange/Redis/ReplicationChangeOptions.cs deleted file mode 100644 index 5ce2f12d7..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ReplicationChangeOptions.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace StackExchange.Redis -{ - /// - /// Additional operations to perform when making a server a master - /// - public enum ReplicationChangeOptions - { - /// - /// No additional operations - /// - None = 0, - /// - /// Set the tie-breaker key on all available masters, to specify this server - /// - SetTiebreaker = 1, - /// - /// Broadcast to the pub-sub channel to listening clients to reconfigure themselves - /// - Broadcast = 2, - /// - /// Issue a SLAVEOF to all other known nodes, making this this master of all - /// - EnslaveSubordinates = 4, - /// - /// All additional operations - /// - All = SetTiebreaker | Broadcast | EnslaveSubordinates - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ResultBox.cs b/StackExchange.Redis/StackExchange/Redis/ResultBox.cs deleted file mode 100644 index 57f0ba617..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ResultBox.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - abstract partial class ResultBox - { - protected Exception exception; - - public void SetException(Exception exception) - { - this.exception = exception; - //try - //{ - // throw exception; - //} - //catch (Exception caught) - //{ // stacktrace etc - // this.exception = caught; - //} - } - - public abstract bool TryComplete(bool isAsync); - - [Conditional("DEBUG")] - protected static void IncrementAllocationCount() - { - OnAllocated(); - } - - static partial void OnAllocated(); - } - sealed class ResultBox : ResultBox - { - private static readonly ResultBox[] store = new ResultBox[64]; - - private object stateOrCompletionSource; - - private T value; - - public ResultBox(object stateOrCompletionSource) - { - this.stateOrCompletionSource = stateOrCompletionSource; - } - - public object AsyncState => - stateOrCompletionSource is TaskCompletionSource - ? ((TaskCompletionSource) stateOrCompletionSource).Task.AsyncState - : stateOrCompletionSource; - - public static ResultBox Get(object stateOrCompletionSource) - { - ResultBox found; - for (int i = 0; i < store.Length; i++) - { - if ((found = Interlocked.Exchange(ref store[i], null)) != null) - { - found.Reset(stateOrCompletionSource); - return found; - } - } - IncrementAllocationCount(); - - return new ResultBox(stateOrCompletionSource); - } - - public static void UnwrapAndRecycle(ResultBox box, bool recycle, out T value, out Exception exception) - { - if (box == null) - { - value = default(T); - exception = null; - } - else - { - value = box.value; - exception = box.exception; - box.value = default(T); - box.exception = null; - if (recycle) - { - for (int i = 0; i < store.Length; i++) - { - if (Interlocked.CompareExchange(ref store[i], box, null) == null) return; - } - } - } - } - - public void SetResult(T value) - { - this.value = value; - } - - public override bool TryComplete(bool isAsync) - { - if (stateOrCompletionSource is TaskCompletionSource) - { - var tcs = (TaskCompletionSource)stateOrCompletionSource; -#if !PLAT_SAFE_CONTINUATIONS // we don't need to check in this scenario - if (isAsync || TaskSource.IsSyncSafe(tcs.Task)) -#endif - { - T val; - Exception ex; - UnwrapAndRecycle(this, true, out val, out ex); - - if (ex == null) tcs.TrySetResult(val); - else - { - if (ex is TaskCanceledException) tcs.TrySetCanceled(); - else tcs.TrySetException(ex); - // mark it as observed - GC.KeepAlive(tcs.Task.Exception); - GC.SuppressFinalize(tcs.Task); - } - return true; - } -#if !PLAT_SAFE_CONTINUATIONS - else - { // looks like continuations; push to async to preserve the reader thread - return false; - } -#endif - } - else - { - lock (this) - { // tell the waiting thread that we're done - Monitor.PulseAll(this); - } - ConnectionMultiplexer.TraceWithoutContext("Pulsed", "Result"); - return true; - } - } - - private void Reset(object stateOrCompletionSource) - { - value = default(T); - exception = null; - - this.stateOrCompletionSource = stateOrCompletionSource; - } - } - -} diff --git a/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs b/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs deleted file mode 100644 index c7bee8abd..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ResultProcessor.cs +++ /dev/null @@ -1,1456 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Text.RegularExpressions; - -namespace StackExchange.Redis -{ - abstract class ResultProcessor - { - public static readonly ResultProcessor - Boolean = new BooleanProcessor(), - DemandOK = new ExpectBasicStringProcessor(RedisLiterals.BytesOK), - DemandPONG = new ExpectBasicStringProcessor(RedisLiterals.BytesPONG), - DemandZeroOrOne = new DemandZeroOrOneProcessor(), - AutoConfigure = new AutoConfigureProcessor(), - TrackSubscriptions = new TrackSubscriptionsProcessor(), - Tracer = new TracerProcessor(false), - EstablishConnection = new TracerProcessor(true), - BackgroundSaveStarted = new ExpectBasicStringProcessor(RedisLiterals.BytesBackgroundSavingStarted); - - public static readonly ResultProcessor - ByteArray = new ByteArrayProcessor(), - ScriptLoad = new ScriptLoadProcessor(); - - public static readonly ResultProcessor - ClusterNodes = new ClusterNodesProcessor(); - - public static readonly ResultProcessor - ConnectionIdentity = new ConnectionIdentityProcessor(); - - public static readonly ResultProcessor - DateTime = new DateTimeProcessor(); - - public static readonly ResultProcessor - Double = new DoubleProcessor(); - public static readonly ResultProcessor>[]> - Info = new InfoProcessor(); - - public static readonly ResultProcessor - Int64 = new Int64Processor(), - PubSubNumSub = new PubSubNumSubProcessor(); - - public static readonly ResultProcessor - NullableDouble = new NullableDoubleProcessor(); - public static readonly ResultProcessor - NullableInt64 = new NullableInt64Processor(); - - public static readonly ResultProcessor - RedisChannelArrayLiteral = new RedisChannelArrayProcessor(RedisChannel.PatternMode.Literal); - - public static readonly ResultProcessor - RedisKey = new RedisKeyProcessor(); - - public static readonly ResultProcessor - RedisKeyArray = new RedisKeyArrayProcessor(); - - public static readonly ResultProcessor - RedisType = new RedisTypeProcessor(); - - public static readonly ResultProcessor - RedisValue = new RedisValueProcessor(); - - public static readonly ResultProcessor - RedisValueArray = new RedisValueArrayProcessor(); - - public static readonly ResultProcessor - StringArray = new StringArrayProcessor(); - - public static readonly ResultProcessor - RedisGeoPositionArray = new RedisValueGeoPositionArrayProcessor(); - public static readonly ResultProcessor - RedisGeoPosition = new RedisValueGeoPositionProcessor(); - - public static readonly ResultProcessor - ResponseTimer = new TimingProcessor(); - - public static readonly ResultProcessor - ScriptResult = new ScriptResultProcessor(); - - public static readonly SortedSetEntryArrayProcessor - SortedSetWithScores = new SortedSetEntryArrayProcessor(); - - public static ResultProcessor GeoRadiusArray(GeoRadiusOptions options) => GeoRadiusResultArrayProcessor.Get(options); - - public static readonly ResultProcessor - String = new StringProcessor(), - ClusterNodesRaw = new ClusterNodesRawProcessor(); - - #region Sentinel - - public static readonly ResultProcessor - SentinelMasterEndpoint = new SentinelGetMasterAddressByNameProcessor(); - - public static readonly ResultProcessor[][]> - SentinelArrayOfArrays = new SentinelArrayOfArraysProcessor(); - - #endregion - - public static readonly ResultProcessor[]> - StringPairInterleaved = new StringPairInterleavedProcessor(); - public static readonly TimeSpanProcessor - TimeSpanFromMilliseconds = new TimeSpanProcessor(true), - TimeSpanFromSeconds = new TimeSpanProcessor(false); - public static readonly HashEntryArrayProcessor - HashEntryArray = new HashEntryArrayProcessor(); - static readonly byte[] MOVED = Encoding.UTF8.GetBytes("MOVED "), ASK = Encoding.UTF8.GetBytes("ASK "); - - public void ConnectionFail(Message message, ConnectionFailureType fail, Exception innerException) - { - PhysicalConnection.IdentifyFailureType(innerException, ref fail); - - string exMessage = fail.ToString() + (message == null ? "" : (" on " + message.Command)); - var ex = innerException == null ? new RedisConnectionException(fail, exMessage) - : new RedisConnectionException(fail, exMessage, innerException); - SetException(message, ex); - } - - public void ConnectionFail(Message message, ConnectionFailureType fail, string errorMessage) - { - SetException(message, new RedisConnectionException(fail, errorMessage)); - } - - public void ServerFail(Message message, string errorMessage) - { - SetException(message, new RedisServerException(errorMessage)); - } - - public void SetException(Message message, Exception ex) - { - var box = message?.ResultBox; - box?.SetException(ex); - } - // true if ready to be completed (i.e. false if re-issued to another server) - public virtual bool SetResult(PhysicalConnection connection, Message message, RawResult result) - { - var logging = message as LoggingMessage; - if (logging != null) - { - try - { - connection.Multiplexer.LogLocked(logging.Log, "Response from {0} / {1}: {2}", connection.Bridge, message.CommandAndKey, result); - } - catch { } - } - if (result.IsError) - { - var bridge = connection.Bridge; - var server = bridge.ServerEndPoint; - bool log = !message.IsInternalCall; - bool isMoved = result.AssertStarts(MOVED); - string err = string.Empty; - if (isMoved || result.AssertStarts(ASK)) - { - message.SetResponseReceived(); - - log = false; - string[] parts = result.GetString().Split(StringSplits.Space, 3); - int hashSlot; - EndPoint endpoint; - if (Format.TryParseInt32(parts[1], out hashSlot) && - (endpoint = Format.TryParseEndPoint(parts[2])) != null) - { - - // no point sending back to same server, and no point sending to a dead server - if (!Equals(server.EndPoint, endpoint)) - { - if (bridge.Multiplexer.TryResend(hashSlot, message, endpoint, isMoved)) - { - connection.Multiplexer.Trace(message.Command + " re-issued to " + endpoint, isMoved ? "MOVED" : "ASK"); - return false; - } - else - { - err = string.Format("Endpoint {0} serving hashslot {1} is not reachable at this point of time. Please check connectTimeout value. If it is low, try increasing it to give the ConnectionMultiplexer a chance to recover from the network disconnect. ", endpoint, hashSlot); -#if !CORE_CLR - err += ConnectionMultiplexer.GetThreadPoolAndCPUSummary(bridge.Multiplexer.IncludePerformanceCountersInExceptions); -#endif - } - } - } - } - - if (string.IsNullOrWhiteSpace(err)) - { - err = result.GetString(); - } - - if (log) - { - bridge.Multiplexer.OnErrorMessage(server.EndPoint, err); - } - connection.Multiplexer.Trace("Completed with error: " + err + " (" + GetType().Name + ")", ToString()); - ServerFail(message, err); - } - else - { - bool coreResult = SetResultCore(connection, message, result); - if (coreResult) - { - connection.Multiplexer.Trace("Completed with success: " + result.ToString() + " (" + GetType().Name + ")", ToString()); - } - else - { - UnexpectedResponse(message, result); - } - } - return true; - } - protected abstract bool SetResultCore(PhysicalConnection connection, Message message, RawResult result); - - private void UnexpectedResponse(Message message, RawResult result) - { - ConnectionMultiplexer.TraceWithoutContext("From " + GetType().Name, "Unexpected Response"); - ConnectionFail(message, ConnectionFailureType.ProtocolFailure, "Unexpected response to " + (message?.Command.ToString() ?? "n/a") + ": " + result.ToString()); - } - - public sealed class TimeSpanProcessor : ResultProcessor - { - private readonly bool isMilliseconds; - public TimeSpanProcessor(bool isMilliseconds) - { - this.isMilliseconds = isMilliseconds; - } - public bool TryParse(RawResult result, out TimeSpan? expiry) - { - switch (result.Type) - { - case ResultType.Integer: - long time; - if (result.TryGetInt64(out time)) - { - if (time < 0) - { - expiry = null; - } - else if (isMilliseconds) - { - expiry = TimeSpan.FromMilliseconds(time); - } - else - { - expiry = TimeSpan.FromSeconds(time); - } - return true; - } - break; - } - expiry = null; - return false; - } - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - TimeSpan? expiry; - if (TryParse(result, out expiry)) - { - SetResult(message, expiry); - return true; - } - return false; - } - } - - public sealed class TimingProcessor : ResultProcessor - { - public static TimerMessage CreateMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value = default(RedisValue)) - { - return new TimerMessage(db, flags, command, value); - } - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - if (result.Type == ResultType.Error) - { - return false; - } - else - { // don't check the actual reply; there are multiple ways of constructing - // a timing message, and we don't actually care about what approach was used - var timingMessage = message as TimerMessage; - TimeSpan duration; - if (timingMessage != null) - { - var watch = timingMessage.Watch; - watch.Stop(); - duration = watch.Elapsed; - } - else - { - duration = TimeSpan.MaxValue; - } - SetResult(message, duration); - return true; - } - } - - internal sealed class TimerMessage : Message - { - public readonly Stopwatch Watch; - private readonly RedisValue value; - public TimerMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value) - : base(db, flags, command) - { - this.Watch = Stopwatch.StartNew(); - this.value = value; - } - internal override void WriteImpl(PhysicalConnection physical) - { - if (value.IsNull) - { - physical.WriteHeader(command, 0); - } - else - { - physical.WriteHeader(command, 1); - physical.Write(value); - } - } - } - } - - public sealed class TrackSubscriptionsProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - if (result.Type == ResultType.MultiBulk) - { - var items = result.GetItems(); - long count; - if (items.Length >= 3 && items[2].TryGetInt64(out count)) - { - connection.SubscriptionCount = count; - return true; - } - } - return false; - } - } - internal sealed class DemandZeroOrOneProcessor : ResultProcessor - { - static readonly byte[] zero = { (byte)'0' }, one = { (byte)'1' }; - - public static bool TryGet(RawResult result, out bool value) - { - switch (result.Type) - { - case ResultType.Integer: - case ResultType.SimpleString: - case ResultType.BulkString: - if (result.IsEqual(one)) { value = true; return true; } - else if (result.IsEqual(zero)) { value = false; return true; } - break; - } - value = false; - return false; - } - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - bool value; - if (TryGet(result, out value)) - { - SetResult(message, value); - return true; - } - return false; - } - } - - internal sealed class ScriptLoadProcessor : ResultProcessor - { - static readonly Regex sha1 = new Regex("^[0-9a-f]{40}$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - internal static bool IsSHA1(string script) - { - return script != null && sha1.IsMatch(script); - } - internal static byte[] ParseSHA1(byte[] value) - { - if (value != null && value.Length == 40) - { - var tmp = new byte[20]; - int charIndex = 0; - for (int i = 0; i < tmp.Length; i++) - { - int x = FromHex((char)value[charIndex++]), y = FromHex((char)value[charIndex++]); - if (x < 0 || y < 0) return null; - tmp[i] = (byte)((x << 4) | y); - } - return tmp; - } - return null; - } - internal static byte[] ParseSHA1(string value) - { - if (value != null && value.Length == 40 && sha1.IsMatch(value)) - { - var tmp = new byte[20]; - int charIndex = 0; - for (int i = 0; i < tmp.Length; i++) - { - int x = FromHex(value[charIndex++]), y = FromHex(value[charIndex++]); - if (x < 0 || y < 0) return null; - tmp[i] = (byte)((x << 4) | y); - } - return tmp; - } - return null; - } - private static int FromHex(char c) - { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - return -1; - } - // note that top-level error messages still get handled by SetResult, but nested errors - // (is that a thing?) will be wrapped in the RedisResult - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.BulkString: - var asciiHash = result.GetBlob(); - if (asciiHash == null || asciiHash.Length != 40) return false; - - byte[] hash = null; - if (!message.IsInternalCall) - { - hash = ParseSHA1(asciiHash); // external caller wants the hex bytes, not the ascii bytes - } - var sl = message as RedisDatabase.ScriptLoadMessage; - if (sl != null) - { - connection.Bridge.ServerEndPoint.AddScript(sl.Script, asciiHash); - } - SetResult(message, hash); - return true; - } - return false; - } - } - - internal sealed class SortedSetEntryArrayProcessor : ValuePairInterleavedProcessorBase - { - protected override SortedSetEntry Parse(RawResult first, RawResult second) - { - double val; - return new SortedSetEntry(first.AsRedisValue(), second.TryGetDouble(out val) ? val : double.NaN); - } - } - - internal sealed class HashEntryArrayProcessor : ValuePairInterleavedProcessorBase - { - protected override HashEntry Parse(RawResult first, RawResult second) - { - return new HashEntry(first.AsRedisValue(), second.AsRedisValue()); - } - } - - internal abstract class ValuePairInterleavedProcessorBase : ResultProcessor - { - static readonly T[] nix = new T[0]; - - public bool TryParse(RawResult result, out T[] pairs) - { - switch (result.Type) - { - case ResultType.MultiBulk: - var arr = result.GetItems(); - if (arr == null) - { - pairs = null; - } - else - { - int count = arr.Length / 2; - if (count == 0) - { - pairs = nix; - } - else - { - pairs = new T[count]; - int offset = 0; - for (int i = 0; i < pairs.Length; i++) - { - pairs[i] = Parse(arr[offset++], arr[offset++]); - } - } - } - return true; - default: - pairs = null; - return false; - } - } - protected abstract T Parse(RawResult first, RawResult second); - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - T[] arr; - if (TryParse(result, out arr)) - { - SetResult(message, arr); - return true; - } - return false; - } - } - - sealed class AutoConfigureProcessor : ResultProcessor - { - static readonly byte[] READONLY = Encoding.UTF8.GetBytes("READONLY "); - public override bool SetResult(PhysicalConnection connection, Message message, RawResult result) - { - if (result.IsError && result.AssertStarts(READONLY)) - { - var server = connection.Bridge.ServerEndPoint; - server.Multiplexer.Trace("Auto-configured role: slave"); - server.IsSlave = true; - } - return base.SetResult(connection, message, result); - } - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - var server = connection.Bridge.ServerEndPoint; - switch (result.Type) - { - case ResultType.BulkString: - if (message != null && message.Command == RedisCommand.INFO) - { - string info = result.GetString(), line; - if (string.IsNullOrWhiteSpace(info)) - { - SetResult(message, true); - return true; - } - string masterHost = null, masterPort = null; - bool roleSeen = false; - using (var reader = new StringReader(info)) - { - while ((line = reader.ReadLine()) != null) - { - if (string.IsNullOrWhiteSpace(line) || line.StartsWith("# ")) continue; - - string val; - if ((val = Extract(line, "role:")) != null) - { - roleSeen = true; - switch (val) - { - case "master": - server.IsSlave = false; - server.Multiplexer.Trace("Auto-configured role: master"); - break; - case "slave": - server.IsSlave = true; - server.Multiplexer.Trace("Auto-configured role: slave"); - break; - } - } - else if ((val = Extract(line, "master_host:")) != null) - { - masterHost = val; - } - else if ((val = Extract(line, "master_port:")) != null) - { - masterPort = val; - } - else if ((val = Extract(line, "redis_version:")) != null) - { - Version version; - if (Version.TryParse(val, out version)) - { - server.Version = version; - server.Multiplexer.Trace("Auto-configured version: " + version); - } - } - else if ((val = Extract(line, "redis_mode:")) != null) - { - switch (val) - { - case "standalone": - server.ServerType = ServerType.Standalone; - server.Multiplexer.Trace("Auto-configured server-type: standalone"); - break; - case "cluster": - server.ServerType = ServerType.Cluster; - server.Multiplexer.Trace("Auto-configured server-type: cluster"); - break; - case "sentinel": - server.ServerType = ServerType.Sentinel; - server.Multiplexer.Trace("Auto-configured server-type: sentinel"); - break; - } - } - else if ((val = Extract(line, "run_id:")) != null) - { - server.RunId = val; - } - } - if (roleSeen) - { // these are in the same section, if presnt - server.MasterEndPoint = Format.TryParseEndPoint(masterHost, masterPort); - } - } - } - SetResult(message, true); - return true; - case ResultType.MultiBulk: - if (message != null && message.Command == RedisCommand.CONFIG) - { - var arr = result.GetItems(); - int count = arr.Length / 2; - - byte[] timeout = (byte[])RedisLiterals.timeout, - databases = (byte[])RedisLiterals.databases, - slave_read_only = (byte[])RedisLiterals.slave_read_only, - yes = (byte[])RedisLiterals.yes, - no = (byte[])RedisLiterals.no; - - long i64; - for (int i = 0; i < count; i++) - { - var key = arr[i * 2]; - if (key.IsEqual(timeout) && arr[(i * 2) + 1].TryGetInt64(out i64)) - { - // note the configuration is in seconds - int timeoutSeconds = checked((int)i64), targetSeconds; - if (timeoutSeconds > 0) - { - if (timeoutSeconds >= 60) - { - targetSeconds = timeoutSeconds - 20; // time to spare... - } - else - { - targetSeconds = (timeoutSeconds * 3) / 4; - } - server.Multiplexer.Trace("Auto-configured timeout: " + targetSeconds + "s"); - server.WriteEverySeconds = targetSeconds; - } - } - else if (key.IsEqual(databases) && arr[(i * 2) + 1].TryGetInt64(out i64)) - { - int dbCount = checked((int)i64); - server.Multiplexer.Trace("Auto-configured databases: " + dbCount); - server.Databases = dbCount; - } - else if (key.IsEqual(slave_read_only)) - { - var val = arr[(i * 2) + 1]; - if (val.IsEqual(yes)) - { - server.SlaveReadOnly = true; - server.Multiplexer.Trace("Auto-configured slave-read-only: true"); - } - else if (val.IsEqual(no)) - { - server.SlaveReadOnly = false; - server.Multiplexer.Trace("Auto-configured slave-read-only: false"); - } - - } - } - } - SetResult(message, true); - return true; - } - return false; - } - - static string Extract(string line, string prefix) - { - if (line.StartsWith(prefix)) return line.Substring(prefix.Length).Trim(); - return null; - } - } - sealed class BooleanProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - if (result.IsNull) - { - SetResult(message, false); // lots of ops return (nil) when they mean "no" - return true; - } - switch (result.Type) - { - case ResultType.SimpleString: - if (result.IsEqual(RedisLiterals.BytesOK)) - { - SetResult(message, true); - } - else - { - SetResult(message, result.GetBoolean()); - } - return true; - case ResultType.Integer: - case ResultType.BulkString: - SetResult(message, result.GetBoolean()); - return true; - case ResultType.MultiBulk: - var items = result.GetItems(); - if (items.Length == 1) - { // treat an array of 1 like a single reply (for example, SCRIPT EXISTS) - SetResult(message, items[0].GetBoolean()); - return true; - } - break; - } - return false; - } - } - - sealed class ByteArrayProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.BulkString: - SetResult(message, result.GetBlob()); - return true; - } - return false; - } - } - - sealed class ClusterNodesProcessor : ResultProcessor - { - internal static ClusterConfiguration Parse(PhysicalConnection connection, string nodes) - { - var server = connection.Bridge.ServerEndPoint; - var config = new ClusterConfiguration(connection.Multiplexer.ServerSelectionStrategy, nodes, server.EndPoint); - server.SetClusterConfiguration(config); - return config; - } - - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.BulkString: - string nodes = result.GetString(); - connection.Bridge.ServerEndPoint.ServerType = ServerType.Cluster; - var config = Parse(connection, nodes); - SetResult(message, config); - return true; - } - return false; - } - } - - sealed class ClusterNodesRawProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.Integer: - case ResultType.SimpleString: - case ResultType.BulkString: - string nodes = result.GetString(); - try - { ClusterNodesProcessor.Parse(connection, nodes); } - catch - { /* tralalalala */} - SetResult(message, nodes); - return true; - } - return false; - } - } - - private sealed class ConnectionIdentityProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - SetResult(message, connection.Bridge.ServerEndPoint.EndPoint); - return true; - } - } - - sealed class DateTimeProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - long unixTime, micros; - switch (result.Type) - { - case ResultType.Integer: - if (result.TryGetInt64(out unixTime)) - { - var time = RedisBase.UnixEpoch.AddSeconds(unixTime); - SetResult(message, time); - return true; - } - break; - case ResultType.MultiBulk: - var arr = result.GetItems(); - switch (arr.Length) - { - case 1: - if (arr[0].TryGetInt64(out unixTime)) - { - var time = RedisBase.UnixEpoch.AddSeconds(unixTime); - SetResult(message, time); - return true; - } - break; - case 2: - if (arr[0].TryGetInt64(out unixTime) && arr[1].TryGetInt64(out micros)) - { - var time = RedisBase.UnixEpoch.AddSeconds(unixTime).AddTicks(micros * 10); // datetime ticks are 100ns - SetResult(message, time); - return true; - } - break; - } - break; - } - return false; - } - } - - sealed class DoubleProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.Integer: - long i64; - if (result.TryGetInt64(out i64)) - { - SetResult(message, i64); - return true; - } - break; - case ResultType.SimpleString: - case ResultType.BulkString: - double val; - if (result.TryGetDouble(out val)) - { - SetResult(message, val); - return true; - } - break; - } - return false; - } - } - sealed class ExpectBasicStringProcessor : ResultProcessor - { - private readonly byte[] expected; - public ExpectBasicStringProcessor(string value) - { - expected = Encoding.UTF8.GetBytes(value); - } - public ExpectBasicStringProcessor(byte[] value) - { - expected = value; - } - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - if (result.IsEqual(expected)) - { - SetResult(message, true); - return true; - } - return false; - } - } - - sealed class InfoProcessor : ResultProcessor>[]> - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - if (result.Type == ResultType.BulkString) - { - string category = Normalize(null), line; - var list = new List>>(); - using (var reader = new StringReader(result.GetString())) - { - while ((line = reader.ReadLine()) != null) - { - if (string.IsNullOrWhiteSpace(line)) continue; - if (line.StartsWith("# ")) - { - category = Normalize(line.Substring(2)); - continue; - } - int idx = line.IndexOf(':'); - if (idx < 0) continue; - var pair = new KeyValuePair( - line.Substring(0, idx).Trim(), - line.Substring(idx + 1).Trim()); - list.Add(Tuple.Create(category, pair)); - } - } - var final = list.GroupBy(x => x.Item1, x => x.Item2).ToArray(); - SetResult(message, final); - return true; - } - return false; - } - - static string Normalize(string category) - { - return string.IsNullOrWhiteSpace(category) ? "miscellaneous" : category.Trim(); - } - } - - class Int64Processor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.Integer: - case ResultType.SimpleString: - case ResultType.BulkString: - long i64; - if (result.TryGetInt64(out i64)) - { - SetResult(message, i64); - return true; - } - break; - } - return false; - } - } - class PubSubNumSubProcessor : Int64Processor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - if (result.Type == ResultType.MultiBulk) - { - var arr = result.GetItems(); - long val; - if (arr != null && arr.Length == 2 && arr[1].TryGetInt64(out val)) - { - SetResult(message, val); - return true; - } - } - return base.SetResultCore(connection, message, result); - } - } - - sealed class NullableDoubleProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.Integer: - case ResultType.SimpleString: - case ResultType.BulkString: - if (result.IsNull) - { - SetResult(message, null); - return true; - } - double val; - if (result.TryGetDouble(out val)) - { - SetResult(message, val); - return true; - } - break; - } - return false; - } - } - sealed class NullableInt64Processor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.Integer: - case ResultType.SimpleString: - case ResultType.BulkString: - if (result.IsNull) - { - SetResult(message, null); - return true; - } - long i64; - if (result.TryGetInt64(out i64)) - { - SetResult(message, i64); - return true; - } - break; - } - return false; - } - } - - sealed class RedisChannelArrayProcessor : ResultProcessor - { - private readonly RedisChannel.PatternMode mode; - public RedisChannelArrayProcessor(RedisChannel.PatternMode mode) - { - this.mode = mode; - } - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.MultiBulk: - var arr = result.GetItems(); - RedisChannel[] final; - if (arr.Length == 0) - { - final = RedisChannel.EmptyArray; - } - else - { - final = new RedisChannel[arr.Length]; - byte[] channelPrefix = connection.ChannelPrefix; - for (int i = 0; i < final.Length; i++) - { - final[i] = arr[i].AsRedisChannel(channelPrefix, mode); - } - } - SetResult(message, final); - return true; - } - return false; - } - } - - sealed class RedisKeyArrayProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.MultiBulk: - var arr = result.GetItemsAsKeys(); - SetResult(message, arr); - return true; - } - return false; - } - } - - sealed class RedisKeyProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.Integer: - case ResultType.SimpleString: - case ResultType.BulkString: - SetResult(message, result.AsRedisKey()); - return true; - } - return false; - } - } - - sealed class RedisTypeProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.SimpleString: - case ResultType.BulkString: - string s = result.GetString(); - RedisType value; - if (string.Equals(s, "zset", StringComparison.OrdinalIgnoreCase)) value = Redis.RedisType.SortedSet; - else if (!Enum.TryParse(s, true, out value)) value = global::StackExchange.Redis.RedisType.Unknown; - SetResult(message, value); - return true; - } - return false; - } - } - - sealed class RedisValueArrayProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.MultiBulk: - var arr = result.GetItemsAsValues(); - - SetResult(message, arr); - return true; - } - return false; - } - } - sealed class StringArrayProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.MultiBulk: - var arr = result.GetItemsAsStrings(); - - SetResult(message, arr); - return true; - } - return false; - } - } - - sealed class RedisValueGeoPositionProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.MultiBulk: - var pos = result.GetItemsAsGeoPosition(); - - SetResult(message, pos); - return true; - } - return false; - } - } - sealed class RedisValueGeoPositionArrayProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.MultiBulk: - var arr = result.GetItemsAsGeoPositionArray(); - - SetResult(message, arr); - return true; - } - return false; - } - } - - sealed class GeoRadiusResultArrayProcessor : ResultProcessor - { - private static readonly GeoRadiusResultArrayProcessor[] instances; - private readonly GeoRadiusOptions options; - - static GeoRadiusResultArrayProcessor() - { - instances = new GeoRadiusResultArrayProcessor[8]; - for (int i = 0; i < 8; i++) instances[i] = new GeoRadiusResultArrayProcessor((GeoRadiusOptions)i); - } - public static GeoRadiusResultArrayProcessor Get(GeoRadiusOptions options) - { - int i = (int)options; - if (i < 0 || i >= instances.Length) throw new ArgumentOutOfRangeException(nameof(options)); - return instances[i]; - } - - private GeoRadiusResultArrayProcessor(GeoRadiusOptions options) - { - this.options = options; - } - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.MultiBulk: - var arr = result.GetItemsAsRawResults(); - - GeoRadiusResult[] typed; - if (arr == null) - { - typed = null; - } - else - { - var options = this.options; - typed = new GeoRadiusResult[arr.Length]; - for (int i = 0; i < arr.Length; i++) - { - typed[i] = Parse(options, arr[i]); - } - } - SetResult(message, typed); - return true; - } - return false; - } - - private static GeoRadiusResult Parse(GeoRadiusOptions options, RawResult item) - { - if (options == GeoRadiusOptions.None) - { - // Without any WITH option specified, the command just returns a linear array like ["New York","Milan","Paris"]. - return new GeoRadiusResult(item.AsRedisValue(), null, null, null); - } - // If WITHCOORD, WITHDIST or WITHHASH options are specified, the command returns an array of arrays, where each sub-array represents a single item. - var arr = item.GetArrayOfRawResults(); - - int index = 0; - // the first item in the sub-array is always the name of the returned item. - var member = arr[index++].AsRedisValue(); - - /* The other information is returned in the following order as successive elements of the sub-array. -The distance from the center as a floating point number, in the same unit specified in the radius. -The geohash integer. -The coordinates as a two items x,y array (longitude,latitude). - */ - double? distance = null; - GeoPosition? position = null; - long? hash = null; - if ((options & GeoRadiusOptions.WithDistance) != 0) { distance = (double?)arr[index++].AsRedisValue(); } - if ((options & GeoRadiusOptions.WithGeoHash) != 0) { hash = (long?)arr[index++].AsRedisValue(); } - if ((options & GeoRadiusOptions.WithCoordinates) != 0) - { - var coords = arr[index++].GetArrayOfRawResults(); - double longitude = (double)coords[0].AsRedisValue(), latitude = (double)coords[1].AsRedisValue(); - position = new GeoPosition(longitude, latitude); - } - return new GeoRadiusResult(member, distance, hash, position); - } - } - - sealed class RedisValueProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.Integer: - case ResultType.SimpleString: - case ResultType.BulkString: - SetResult(message, result.AsRedisValue()); - return true; - } - return false; - } - } - private class ScriptResultProcessor : ResultProcessor - { - static readonly byte[] NOSCRIPT = Encoding.UTF8.GetBytes("NOSCRIPT "); - public override bool SetResult(PhysicalConnection connection, Message message, RawResult result) - { - if (result.Type == ResultType.Error && result.AssertStarts(NOSCRIPT)) - { // scripts are not flushed individually, so assume the entire script cache is toast ("SCRIPT FLUSH") - connection.Bridge.ServerEndPoint.FlushScriptCache(); - message.SetScriptUnavailable(); - } - // and apply usual processing for the rest - return base.SetResult(connection, message, result); - } - - // note that top-level error messages still get handled by SetResult, but nested errors - // (is that a thing?) will be wrapped in the RedisResult - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - var value = Redis.RedisResult.TryCreate(connection, result); - if (value != null) - { - SetResult(message, value); - return true; - } - return false; - } - } - - sealed class StringPairInterleavedProcessor : ValuePairInterleavedProcessorBase> - { - protected override KeyValuePair Parse(RawResult first, RawResult second) - { - return new KeyValuePair(first.GetString(), second.GetString()); - } - } - - sealed class StringProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.Integer: - case ResultType.SimpleString: - case ResultType.BulkString: - SetResult(message, result.GetString()); - return true; - case ResultType.MultiBulk: - var arr = result.GetItems(); - if(arr.Length == 1) - { - SetResult(message, arr[0].GetString()); - return true; - } - break; - } - return false; - } - } - private class TracerProcessor : ResultProcessor - { - static readonly byte[] - authRequired = Encoding.UTF8.GetBytes("NOAUTH Authentication required."), - authFail = Encoding.UTF8.GetBytes("ERR operation not permitted"), - loading = Encoding.UTF8.GetBytes("LOADING "); - - private readonly bool establishConnection; - - public TracerProcessor(bool establishConnection) - { - this.establishConnection = establishConnection; - } - public override bool SetResult(PhysicalConnection connection, Message message, RawResult result) - { - var final = base.SetResult(connection, message, result); - if (result.IsError) - { - if (result.IsEqual(authFail) || result.IsEqual(authRequired)) - { - connection.RecordConnectionFailed(ConnectionFailureType.AuthenticationFailure, new Exception(result.ToString() + " Verify if the Redis password provided is correct.")); - } - else if (result.AssertStarts(loading)) - { - connection.RecordConnectionFailed(ConnectionFailureType.Loading); - } - else - { - connection.RecordConnectionFailed(ConnectionFailureType.ProtocolFailure); - } - } - return final; - } - - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - bool happy; - switch (message.Command) - { - case RedisCommand.ECHO: - happy = result.Type == ResultType.BulkString && (!establishConnection || result.IsEqual(connection.Multiplexer.UniqueId)); - break; - case RedisCommand.PING: - happy = result.Type == ResultType.SimpleString && result.IsEqual(RedisLiterals.BytesPONG); - break; - case RedisCommand.TIME: - happy = result.Type == ResultType.MultiBulk && result.GetItems().Length == 2; - break; - case RedisCommand.EXISTS: - happy = result.Type == ResultType.Integer; - break; - default: - happy = true; - break; - } - if (happy) - { - if (establishConnection) connection.Bridge.OnFullyEstablished(connection); - SetResult(message, happy); - return true; - } - else - { - connection.RecordConnectionFailed(ConnectionFailureType.ProtocolFailure); - return false; - } - } - } - - #region Sentinel - - sealed class SentinelGetMasterAddressByNameProcessor : ResultProcessor - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - switch (result.Type) - { - case ResultType.MultiBulk: - var arr = result.GetItemsAsValues(); - - int port; - if (arr.Length == 2 && int.TryParse(arr[1], out port)) - { - SetResult(message, Format.ParseEndPoint(arr[0], port)); - return true; - } - else if (arr.Length == 0) - { - SetResult(message, null); - return true; - } - break; - case ResultType.SimpleString: - //We don't want to blow up if the master is not found - if (result.IsNull) - return true; - break; - } - return false; - } - } - - sealed class SentinelArrayOfArraysProcessor : ResultProcessor[][]> - { - protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) - { - var innerProcessor = StringPairInterleaved as StringPairInterleavedProcessor; - if (innerProcessor == null) - { - return false; - } - - switch (result.Type) - { - case ResultType.MultiBulk: - var arrayOfArrays = result.GetArrayOfRawResults(); - - var returnArray = new KeyValuePair[arrayOfArrays.Length][]; - - for (int i = 0; i < arrayOfArrays.Length; i++) - { - var rawInnerArray = arrayOfArrays[i]; - KeyValuePair[] kvpArray; - innerProcessor.TryParse(rawInnerArray, out kvpArray); - returnArray[i] = kvpArray; - } - - SetResult(message, returnArray); - return true; - } - return false; - } - } - - #endregion - } - internal abstract class ResultProcessor : ResultProcessor - { - - protected void SetResult(Message message, T value) - { - if (message == null) return; - var box = message.ResultBox as ResultBox; - message.SetResponseReceived(); - - box?.SetResult(value); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ResultType.cs b/StackExchange.Redis/StackExchange/Redis/ResultType.cs deleted file mode 100644 index 057e0b9d1..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ResultType.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace StackExchange.Redis -{ - internal enum ResultType : byte - { - None = 0, - SimpleString = 1, - Error = 2, - Integer = 3, - BulkString = 4, - MultiBulk = 5 - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/SaveType.cs b/StackExchange.Redis/StackExchange/Redis/SaveType.cs deleted file mode 100644 index 61c086c46..000000000 --- a/StackExchange.Redis/StackExchange/Redis/SaveType.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace StackExchange.Redis -{ - /// - /// The type of save operation to perform - /// - public enum SaveType - { - /// - /// Instruct Redis to start an Append Only File rewrite process. The rewrite will create a small optimized version of the current Append Only File. - /// - /// http://redis.io/commands/bgrewriteaof - BackgroundRewriteAppendOnlyFile, - /// - /// Save the DB in background. The OK code is immediately returned. Redis forks, the parent continues to serve the clients, the child saves the DB on disk then exits. A client my be able to check if the operation succeeded using the LASTSAVE command. - /// - /// http://redis.io/commands/bgsave - BackgroundSave, - /// - /// Save the DB in foreground. This is almost never a good thing to do, and could cause significant blocking. Only do this if you know you need to save - /// - /// http://redis.io/commands/save - [Obsolete("Saving on the foreground can cause significant blocking; use with extreme caution")] - ForegroundSave - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ScriptParameterMapper.cs b/StackExchange.Redis/StackExchange/Redis/ScriptParameterMapper.cs deleted file mode 100644 index 0843fa2e0..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ScriptParameterMapper.cs +++ /dev/null @@ -1,411 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Text; -using System.Text.RegularExpressions; - -namespace StackExchange.Redis -{ - class ScriptParameterMapper - { - public struct ScriptParameters - { - public RedisKey[] Keys; - public RedisValue[] Arguments; - - public static readonly ConstructorInfo Cons = typeof(ScriptParameters).GetConstructor(new[] { typeof(RedisKey[]), typeof(RedisValue[]) }); - public ScriptParameters(RedisKey[] keys, RedisValue[] args) - { - Keys = keys; - Arguments = args; - } - } - - static readonly Regex ParameterExtractor = new Regex(@"@(? ([a-z]|_) ([a-z]|_|\d)*)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - static string[] ExtractParameters(string script) - { - var ps = ParameterExtractor.Matches(script); - if (ps.Count == 0) return null; - - var ret = new HashSet(); - - for (var i = 0; i < ps.Count; i++) - { - var c = ps[i]; - var ix = c.Index - 1; - if (ix >= 0) - { - var prevChar = script[ix]; - - // don't consider this a parameter if it's in the middle of word (ie. if it's preceeded by a letter) - if (char.IsLetterOrDigit(prevChar) || prevChar == '_') continue; - - // this is an escape, ignore it - if (prevChar == '@') continue; - } - - var n = c.Groups["paramName"].Value; - if (!ret.Contains(n)) ret.Add(n); - } - - return ret.ToArray(); - } - - static string MakeOrdinalScriptWithoutKeys(string rawScript, string[] args) - { - var ps = ParameterExtractor.Matches(rawScript); - if (ps.Count == 0) return rawScript; - - var ret = new StringBuilder(); - var upTo = 0; - - for (var i = 0; i < ps.Count; i++) - { - var capture = ps[i]; - var name = capture.Groups["paramName"].Value; - - var ix = capture.Index; - ret.Append(rawScript.Substring(upTo, ix - upTo)); - - var argIx = Array.IndexOf(args, name); - - if (argIx != -1) - { - ret.Append("ARGV["); - ret.Append(argIx + 1); - ret.Append("]"); - } - else - { - var isEscape = false; - var prevIx = capture.Index - 1; - if (prevIx >= 0) - { - var prevChar = rawScript[prevIx]; - isEscape = prevChar == '@'; - } - - if (isEscape) - { - // strip the @ off, so just the one triggering the escape exists - ret.Append(capture.Groups["paramName"].Value); - } - else - { - ret.Append(capture.Value); - } - } - - upTo = capture.Index + capture.Length; - } - - ret.Append(rawScript.Substring(upTo, rawScript.Length - upTo)); - - return ret.ToString(); - } - - static void LoadMember(ILGenerator il, MemberInfo member) - { - // stack starts: - // T(*?) - - var asField = member as FieldInfo; - if (asField != null) - { - il.Emit(OpCodes.Ldfld, asField); // typeof(member) - return; - } - - var asProp = member as PropertyInfo; - if (asProp != null) - { - var getter = asProp.GetGetMethod(); - if (getter.IsVirtual) - { - il.Emit(OpCodes.Callvirt, getter); // typeof(member) - } - else - { - il.Emit(OpCodes.Call, getter); // typeof(member) - } - - return; - } - - throw new Exception("Should't be possible"); - } - - static readonly MethodInfo RedisValue_FromInt = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(int) }); - static readonly MethodInfo RedisValue_FromNullableInt = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(int?) }); - static readonly MethodInfo RedisValue_FromLong = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(long) }); - static readonly MethodInfo RedisValue_FromNullableLong = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(long?) }); - static readonly MethodInfo RedisValue_FromDouble= typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(double) }); - static readonly MethodInfo RedisValue_FromNullableDouble = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(double?) }); - static readonly MethodInfo RedisValue_FromString = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(string) }); - static readonly MethodInfo RedisValue_FromByteArray = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(byte[]) }); - static readonly MethodInfo RedisValue_FromBool = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(bool) }); - static readonly MethodInfo RedisValue_FromNullableBool = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(bool?) }); - static readonly MethodInfo RedisKey_AsRedisValue = typeof(RedisKey).GetMethod("AsRedisValue", BindingFlags.NonPublic | BindingFlags.Instance); - static void ConvertToRedisValue(MemberInfo member, ILGenerator il, LocalBuilder needsPrefixBool, ref LocalBuilder redisKeyLoc) - { - // stack starts: - // typeof(member) - - var t = member is FieldInfo ? ((FieldInfo)member).FieldType : ((PropertyInfo)member).PropertyType; - - if (t == typeof(RedisValue)) - { - // They've already converted for us, don't do anything - return; - } - - if (t == typeof(RedisKey)) - { - redisKeyLoc = redisKeyLoc ?? il.DeclareLocal(typeof(RedisKey)); - PrefixIfNeeded(il, needsPrefixBool, ref redisKeyLoc); // RedisKey - il.Emit(OpCodes.Stloc, redisKeyLoc); // --empty-- - il.Emit(OpCodes.Ldloca, redisKeyLoc); // RedisKey* - il.Emit(OpCodes.Call, RedisKey_AsRedisValue); // RedisValue - return; - } - - MethodInfo convertOp = null; - if (t == typeof(int)) convertOp = RedisValue_FromInt; - if (t == typeof(int?)) convertOp = RedisValue_FromNullableInt; - if (t == typeof(long)) convertOp = RedisValue_FromLong; - if (t == typeof(long?)) convertOp = RedisValue_FromNullableLong; - if (t == typeof(double)) convertOp = RedisValue_FromDouble; - if (t == typeof(double?)) convertOp = RedisValue_FromNullableDouble; - if (t == typeof(string)) convertOp = RedisValue_FromString; - if (t == typeof(byte[])) convertOp = RedisValue_FromByteArray; - if (t == typeof(bool)) convertOp = RedisValue_FromBool; - if (t == typeof(bool?)) convertOp = RedisValue_FromNullableBool; - - il.Emit(OpCodes.Call, convertOp); - - // stack ends: - // RedisValue - } - - /// - /// Turns a script with @namedParameters into a LuaScript that can be executed - /// against a given IDatabase(Async) object - /// - public static LuaScript PrepareScript(string script) - { - var ps = ExtractParameters(script); - var ordinalScript = MakeOrdinalScriptWithoutKeys(script, ps); - - return new LuaScript(script, ordinalScript, ps); - } - - static readonly HashSet ConvertableTypes = - new HashSet { - typeof(int), - typeof(int?), - typeof(long), - typeof(long?), - typeof(double), - typeof(double?), - typeof(string), - typeof(byte[]), - typeof(bool), - typeof(bool?), - - typeof(RedisKey), - typeof(RedisValue) - }; - - /// - /// Determines whether or not the given type can be used to provide parameters for the given LuaScript. - /// - public static bool IsValidParameterHash(Type t, LuaScript script, out string missingMember, out string badTypeMember) - { - for (var i = 0; i < script.Arguments.Length; i++) - { - var argName = script.Arguments[i]; - var member = t.GetMember(argName).SingleOrDefault(m => m is PropertyInfo || m is FieldInfo); - if (member == null) - { - missingMember = argName; - badTypeMember = null; - return false; - } - - var memberType = member is FieldInfo ? ((FieldInfo)member).FieldType : ((PropertyInfo)member).PropertyType; - if(!ConvertableTypes.Contains(memberType)){ - missingMember = null; - badTypeMember = argName; - return false; - } - } - - missingMember = badTypeMember = null; - return true; - } - - static void PrefixIfNeeded(ILGenerator il, LocalBuilder needsPrefixBool, ref LocalBuilder redisKeyLoc) - { - // top of stack is - // RedisKey - - var getVal = typeof(RedisKey?).GetProperty("Value").GetGetMethod(); - var prepend = typeof(RedisKey).GetMethod("Prepend"); - - var doNothing = il.DefineLabel(); - redisKeyLoc = redisKeyLoc ?? il.DeclareLocal(typeof(RedisKey)); - - il.Emit(OpCodes.Ldloc, needsPrefixBool); // RedisKey bool - il.Emit(OpCodes.Brfalse, doNothing); // RedisKey - il.Emit(OpCodes.Stloc, redisKeyLoc); // --empty-- - il.Emit(OpCodes.Ldloca, redisKeyLoc); // RedisKey* - il.Emit(OpCodes.Ldarga_S, 1); // RedisKey* RedisKey?* - il.Emit(OpCodes.Call, getVal); // RedisKey* RedisKey - il.Emit(OpCodes.Call, prepend); // RedisKey - - - il.MarkLabel(doNothing); // RedisKey - } - - /// - /// Creates a Func that extracts parameters from the given type for use by a LuaScript. - /// - /// Members that are RedisKey's get extracted to be passed in as keys to redis; all members that - /// appear in the script get extracted as RedisValue arguments to be sent up as args. - /// - /// We send all values as arguments so we don't have to prepare the same script for different parameter - /// types. - /// - /// The created Func takes a RedisKey, which will be prefixed to all keys (and arguments of type RedisKey) for - /// keyspace isolation. - /// - public static Func GetParameterExtractor(Type t, LuaScript script) - { - string ignored; - if (!IsValidParameterHash(t, script, out ignored, out ignored)) throw new Exception("Shouldn't be possible"); - - var keys = new List(); - var args = new List(); - - for (var i = 0; i < script.Arguments.Length; i++) - { - var argName = script.Arguments[i]; - var member = t.GetMember(argName).SingleOrDefault(m => m is PropertyInfo || m is FieldInfo); - - var memberType = member is FieldInfo ? ((FieldInfo)member).FieldType : ((PropertyInfo)member).PropertyType; - - if (memberType == typeof(RedisKey)) - { - keys.Add(member); - } - - args.Add(member); - } - - var nullableRedisKeyHasValue = typeof(RedisKey?).GetProperty("HasValue").GetGetMethod(); - - var dyn = new DynamicMethod("ParameterExtractor_" + t.FullName + "_" + script.OriginalScript.GetHashCode(), typeof(ScriptParameters), new[] { typeof(object), typeof(RedisKey?) }, restrictedSkipVisibility: true); - var il = dyn.GetILGenerator(); - - // only init'd if we use it - LocalBuilder redisKeyLoc = null; - var loc = il.DeclareLocal(t); - il.Emit(OpCodes.Ldarg_0); // object -#if !CORE_CLR - if (t.IsValueType) -#else - if (t.GetTypeInfo().IsValueType) -#endif - { - il.Emit(OpCodes.Unbox_Any, t); // T - } - else - { - il.Emit(OpCodes.Castclass, t); // T - } - il.Emit(OpCodes.Stloc, loc); // --empty-- - - var needsKeyPrefixLoc = il.DeclareLocal(typeof(bool)); - - il.Emit(OpCodes.Ldarga_S, 1); // RedisKey?* - il.Emit(OpCodes.Call, nullableRedisKeyHasValue); // bool - il.Emit(OpCodes.Stloc, needsKeyPrefixLoc); // --empty-- - - if (keys.Count == 0) - { - // if there are no keys, don't allocate - il.Emit(OpCodes.Ldnull); // null - } - else - { - il.Emit(OpCodes.Ldc_I4, keys.Count); // int - il.Emit(OpCodes.Newarr, typeof(RedisKey)); // RedisKey[] - } - - for (var i = 0; i < keys.Count; i++) - { - il.Emit(OpCodes.Dup); // RedisKey[] RedisKey[] - il.Emit(OpCodes.Ldc_I4, i); // RedisKey[] RedisKey[] int -#if !CORE_CLR - if (t.IsValueType) -#else - if (t.GetTypeInfo().IsValueType) -#endif - { - il.Emit(OpCodes.Ldloca, loc); // RedisKey[] RedisKey[] int T* - } - else - { - il.Emit(OpCodes.Ldloc, loc); // RedisKey[] RedisKey[] int T - } - LoadMember(il, keys[i]); // RedisKey[] RedisKey[] int RedisKey - PrefixIfNeeded(il, needsKeyPrefixLoc, ref redisKeyLoc); // RedisKey[] RedisKey[] int RedisKey - il.Emit(OpCodes.Stelem, typeof(RedisKey)); // RedisKey[] - } - - if (args.Count == 0) - { - // if there are no args, don't allocate - il.Emit(OpCodes.Ldnull); // RedisKey[] null - } - else - { - il.Emit(OpCodes.Ldc_I4, args.Count); // RedisKey[] int - il.Emit(OpCodes.Newarr, typeof(RedisValue)); // RedisKey[] RedisValue[] - } - - for (var i = 0; i < args.Count; i++) - { - il.Emit(OpCodes.Dup); // RedisKey[] RedisValue[] RedisValue[] - il.Emit(OpCodes.Ldc_I4, i); // RedisKey[] RedisValue[] RedisValue[] int -#if !CORE_CLR - if (t.IsValueType) -#else - if (t.GetTypeInfo().IsValueType) -#endif - { - il.Emit(OpCodes.Ldloca, loc); // RedisKey[] RedisValue[] RedisValue[] int T* - } - else - { - il.Emit(OpCodes.Ldloc, loc); // RedisKey[] RedisValue[] RedisValue[] int T - } - - var member = args[i]; - LoadMember(il, member); // RedisKey[] RedisValue[] RedisValue[] int memberType - ConvertToRedisValue(member, il, needsKeyPrefixLoc, ref redisKeyLoc); // RedisKey[] RedisValue[] RedisValue[] int RedisValue - - il.Emit(OpCodes.Stelem, typeof(RedisValue)); // RedisKey[] RedisValue[] - } - - il.Emit(OpCodes.Newobj, ScriptParameters.Cons); // ScriptParameters - il.Emit(OpCodes.Ret); // --empty-- - - var ret = (Func)dyn.CreateDelegate(typeof(Func)); - - return ret; - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ServerEndPoint.cs b/StackExchange.Redis/StackExchange/Redis/ServerEndPoint.cs deleted file mode 100644 index 4bd9a479d..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ServerEndPoint.cs +++ /dev/null @@ -1,727 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Runtime.CompilerServices; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -namespace StackExchange.Redis -{ - [Flags] - internal enum UnselectableFlags - { - None = 0, - RedundantMaster = 1, - DidNotRespond = 2, - ServerType = 4 - - } - - internal sealed partial class ServerEndPoint : IDisposable - { - internal volatile ServerEndPoint Master; - internal volatile ServerEndPoint[] Slaves = NoSlaves; - private static readonly Regex nameSanitizer = new Regex("[^!-~]", RegexOptions.Compiled); - private static readonly ServerEndPoint[] NoSlaves = new ServerEndPoint[0]; - private readonly EndPoint endpoint; - - - private readonly Hashtable knownScripts = new Hashtable(StringComparer.Ordinal); - private readonly ConnectionMultiplexer multiplexer; - - private int databases, writeEverySeconds; - - private PhysicalBridge interactive, subscription; - - bool isDisposed; - - ServerType serverType; - - private bool slaveReadOnly, isSlave; - - private volatile UnselectableFlags unselectableReasons; - - private Version version; - - - internal void ResetNonConnected() - { - interactive?.ResetNonConnected(); - subscription?.ResetNonConnected(); - } - public ServerEndPoint(ConnectionMultiplexer multiplexer, EndPoint endpoint, TextWriter log) - { - this.multiplexer = multiplexer; - this.endpoint = endpoint; - var config = multiplexer.RawConfig; - version = config.DefaultVersion; - slaveReadOnly = true; - isSlave = false; - databases = 0; - writeEverySeconds = config.KeepAlive > 0 ? config.KeepAlive : 60; - interactive = CreateBridge(ConnectionType.Interactive, log); - serverType = ServerType.Standalone; - - // overrides for twemproxy - if (multiplexer.RawConfig.Proxy == Proxy.Twemproxy) - { - databases = 1; - serverType = ServerType.Twemproxy; - } - } - - public ClusterConfiguration ClusterConfiguration { get; private set; } - - public int Databases { get { return databases; } set { SetConfig(ref databases, value); } } - - public EndPoint EndPoint => endpoint; - - public bool HasDatabases => serverType == ServerType.Standalone; - - public bool IsConnected - { - get - { - var tmp = interactive; - return tmp != null && tmp.IsConnected; - } - } - - internal Exception LastException - { - get - { - var tmp1 = interactive; - var tmp2 = subscription; - - //check if subscription endpoint has a better lastexception - if (tmp2 != null && tmp2.LastException != null) - { - if (tmp2.LastException.Data.Contains("Redis-FailureType") && !tmp2.LastException.Data["Redis-FailureType"].ToString().Equals(ConnectionFailureType.UnableToConnect.ToString())) - { - return tmp2.LastException; - } - } - return tmp1?.LastException; - } - } - - internal PhysicalBridge.State ConnectionState - { - get - { - var tmp = interactive; - return tmp.ConnectionState; - } - } - - public bool IsSlave { get { return isSlave; } set { SetConfig(ref isSlave, value); } } - - public long OperationCount - { - get - { - long total = 0; - var tmp = interactive; - if (tmp != null) total += tmp.OperationCount; - tmp = subscription; - if (tmp != null) total += tmp.OperationCount; - return total; - } - } - - public bool RequiresReadMode => serverType == ServerType.Cluster && IsSlave; - - public ServerType ServerType { get { return serverType; } set { SetConfig(ref serverType, value); } } - - public bool SlaveReadOnly { get { return slaveReadOnly; } set { SetConfig(ref slaveReadOnly, value); } } - - public bool AllowSlaveWrites { get; set; } - - public Version Version { get { return version; } set { SetConfig(ref version, value); } } - - public int WriteEverySeconds { get { return writeEverySeconds; } set { SetConfig(ref writeEverySeconds, value); } } - internal ConnectionMultiplexer Multiplexer => multiplexer; - - public void ClearUnselectable(UnselectableFlags flags) - { - var oldFlags = unselectableReasons; - if (oldFlags != 0) - { - unselectableReasons &= ~flags; - if (unselectableReasons != oldFlags) - { - multiplexer.Trace(unselectableReasons == 0 ? "Now usable" : ("Now unusable: " + flags), ToString()); - } - } - } - - public void Dispose() - { - isDisposed = true; - var tmp = interactive; - interactive = null; - tmp?.Dispose(); - - tmp = subscription; - subscription = null; - tmp?.Dispose(); - } - - public PhysicalBridge GetBridge(ConnectionType type, bool create = true, TextWriter log = null) - { - if (isDisposed) return null; - switch (type) - { - case ConnectionType.Interactive: - return interactive ?? (create ? interactive = CreateBridge(ConnectionType.Interactive, log) : null); - case ConnectionType.Subscription: - return subscription ?? (create ? subscription = CreateBridge(ConnectionType.Subscription, log) : null); - } - return null; - } - public PhysicalBridge GetBridge(RedisCommand command, bool create = true) - { - if (isDisposed) return null; - switch (command) - { - case RedisCommand.SUBSCRIBE: - case RedisCommand.UNSUBSCRIBE: - case RedisCommand.PSUBSCRIBE: - case RedisCommand.PUNSUBSCRIBE: - return subscription ?? (create ? subscription = CreateBridge(ConnectionType.Subscription, null) : null); - default: - return interactive; - } - } - - public RedisFeatures GetFeatures() - { - return new RedisFeatures(version); - } - - public void SetClusterConfiguration(ClusterConfiguration configuration) - { - - ClusterConfiguration = configuration; - - if (configuration != null) - { - multiplexer.Trace("Updating cluster ranges..."); - multiplexer.UpdateClusterRange(configuration); - multiplexer.Trace("Resolving genealogy..."); - var thisNode = configuration.Nodes.FirstOrDefault(x => x.EndPoint.Equals(this.EndPoint)); - if (thisNode != null) - { - List slaves = null; - ServerEndPoint master = null; - foreach (var node in configuration.Nodes) - { - if (node.NodeId == thisNode.ParentNodeId) - { - master = multiplexer.GetServerEndPoint(node.EndPoint); - } - else if (node.ParentNodeId == thisNode.NodeId) - { - if (slaves == null) slaves = new List(); - slaves.Add(multiplexer.GetServerEndPoint(node.EndPoint)); - } - } - Master = master; - Slaves = slaves?.ToArray() ?? NoSlaves; - } - multiplexer.Trace("Cluster configured"); - } - } - - public void SetUnselectable(UnselectableFlags flags) - { - if (flags != 0) - { - var oldFlags = unselectableReasons; - unselectableReasons |= flags; - if (unselectableReasons != oldFlags) - { - multiplexer.Trace(unselectableReasons == 0 ? "Now usable" : ("Now unusable: " + flags), ToString()); - } - } - } - public override string ToString() - { - return Format.ToString(EndPoint); - } - - public bool TryEnqueue(Message message) - { - var bridge = GetBridge(message.Command); - return bridge != null && bridge.TryEnqueue(message, isSlave); - } - - internal void Activate(ConnectionType type, TextWriter log) - { - GetBridge(type, true, log); - } - - internal void AddScript(string script, byte[] hash) - { - lock (knownScripts) - { - knownScripts[script] = hash; - } - } - - internal void AutoConfigure(PhysicalConnection connection) - { - if (serverType == ServerType.Twemproxy) - { - // don't try to detect configuration; all the config commands are disabled, and - // the fallback master/slave detection won't help - return; - } - - var commandMap = multiplexer.CommandMap; - const CommandFlags flags = CommandFlags.FireAndForget | CommandFlags.HighPriority | CommandFlags.NoRedirect; - - var features = GetFeatures(); - Message msg; - - if (commandMap.IsAvailable(RedisCommand.CONFIG)) - { - if (multiplexer.RawConfig.KeepAlive <= 0) - { - msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, RedisLiterals.timeout); - msg.SetInternalCall(); - WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); - } - msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, RedisLiterals.slave_read_only); - msg.SetInternalCall(); - WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); - msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, RedisLiterals.databases); - msg.SetInternalCall(); - WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); - } - if (commandMap.IsAvailable(RedisCommand.INFO)) - { - lastInfoReplicationCheckTicks = Environment.TickCount; - if (features.InfoSections) - { - msg = Message.Create(-1, flags, RedisCommand.INFO, RedisLiterals.replication); - msg.SetInternalCall(); - WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); - - msg = Message.Create(-1, flags, RedisCommand.INFO, RedisLiterals.server); - msg.SetInternalCall(); - WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); - } - else - { - msg = Message.Create(-1, flags, RedisCommand.INFO); - msg.SetInternalCall(); - WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); - } - } - else if (commandMap.IsAvailable(RedisCommand.SET)) - { - // this is a nasty way to find if we are a slave, and it will only work on up-level servers, but... - RedisKey key = Guid.NewGuid().ToByteArray(); - msg = Message.Create(0, flags, RedisCommand.SET, key, RedisLiterals.slave_read_only, RedisLiterals.PX, 1, RedisLiterals.NX); - msg.SetInternalCall(); - WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); - } - if (commandMap.IsAvailable(RedisCommand.CLUSTER)) - { - msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); - msg.SetInternalCall(); - WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.ClusterNodes); - } - } - - int _nextReplicaOffset; - internal uint NextReplicaOffset() // used to round-robin between multiple replicas - => (uint) System.Threading.Interlocked.Increment(ref _nextReplicaOffset); - - internal Task Close() - { - var tmp = interactive; - Task result; - if (tmp == null || !tmp.IsConnected || !multiplexer.CommandMap.IsAvailable(RedisCommand.QUIT)) - { - result = CompletedTask.Default(null); - } - else - { - result = QueueDirectAsync(Message.Create(-1, CommandFlags.None, RedisCommand.QUIT), ResultProcessor.DemandOK, bridge: interactive); - } - return result; - } - - internal void FlushScriptCache() - { - lock (knownScripts) - { - knownScripts.Clear(); - } - } - - private string runId; - internal string RunId - { - get { return runId; } - set - { - if (value != runId) // we only care about changes - { - // if we had an old run-id, and it has changed, then the - // server has been restarted; which means the script cache - // is toast - if (runId != null) FlushScriptCache(); - runId = value; - } - } - } - - internal ServerCounters GetCounters() - { - var counters = new ServerCounters(endpoint); - interactive?.GetCounters(counters.Interactive); - subscription?.GetCounters(counters.Subscription); - return counters; - } - - internal int GetOutstandingCount(RedisCommand command, out int inst, out int qu, out int qs, out int qc, out int wr, out int wq, out int @in, out int ar) - { - var bridge = GetBridge(command, false); - if (bridge == null) - { - return inst = qu = qs = qc = wr = wq = @in = ar = 0; - } - - return bridge.GetOutstandingCount(out inst, out qu, out qs, out qc, out wr, out wq, out @in, out ar); - } - - internal string GetProfile() - { - var sb = new StringBuilder(); - sb.Append("Circular op-count snapshot; int:"); - interactive?.AppendProfile(sb); - sb.Append("; sub:"); - subscription?.AppendProfile(sb); - return sb.ToString(); - } - - internal byte[] GetScriptHash(string script, RedisCommand command) - { - var found = (byte[])knownScripts[script]; - if(found == null && command == RedisCommand.EVALSHA) - { - // the script provided is a hex sha; store and re-use the ascii for that - found = Encoding.ASCII.GetBytes(script); - lock(knownScripts) - { - knownScripts[script] = found; - } - } - return found; - } - - internal string GetStormLog(RedisCommand command) - { - var bridge = GetBridge(command); - return bridge?.GetStormLog(); - } - - internal Message GetTracerMessage(bool assertIdentity) - { - // different configurations block certain commands, as can ad-hoc local configurations, so - // we'll do the best with what we have available. - // note that the muxer-ctor asserts that one of ECHO, PING, TIME of GET is available - // see also: TracerProcessor - var map = multiplexer.CommandMap; - Message msg; - const CommandFlags flags = CommandFlags.NoRedirect | CommandFlags.FireAndForget; - if (assertIdentity && map.IsAvailable(RedisCommand.ECHO)) - { - msg = Message.Create(-1, flags, RedisCommand.ECHO, (RedisValue)multiplexer.UniqueId); - } - else if (map.IsAvailable(RedisCommand.PING)) - { - msg = Message.Create(-1, flags, RedisCommand.PING); - } - else if (map.IsAvailable(RedisCommand.TIME)) - { - msg = Message.Create(-1, flags, RedisCommand.TIME); - } - else if (!assertIdentity && map.IsAvailable(RedisCommand.ECHO)) - { - // we'll use echo as a PING substitute if it is all we have (in preference to EXISTS) - msg = Message.Create(-1, flags, RedisCommand.ECHO, (RedisValue)multiplexer.UniqueId); - } - else - { - map.AssertAvailable(RedisCommand.EXISTS); - msg = Message.Create(0, flags, RedisCommand.EXISTS, (RedisValue)multiplexer.UniqueId); - } - msg.SetInternalCall(); - return msg; - } - - internal bool IsSelectable(RedisCommand command) - { - var bridge = unselectableReasons == 0 ? GetBridge(command, false) : null; - return bridge != null && bridge.IsConnected; - } - - internal void OnEstablishing(PhysicalConnection connection, TextWriter log) - { - try - { - if (connection == null) return; - Handshake(connection, log); - } - catch (Exception ex) - { - connection.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); - } - } - - internal void OnFullyEstablished(PhysicalConnection connection) - { - try - { - if (connection == null) return; - var bridge = connection.Bridge; - if (bridge == subscription) - { - multiplexer.ResendSubscriptions(this); - } - multiplexer.OnConnectionRestored(endpoint, bridge.ConnectionType); - } - catch (Exception ex) - { - connection.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); - } - } - - internal int LastInfoReplicationCheckSecondsAgo - { - get { return unchecked(Environment.TickCount - VolatileWrapper.Read(ref lastInfoReplicationCheckTicks)) / 1000; } - } - - private EndPoint masterEndPoint; - public EndPoint MasterEndPoint - { - get { return masterEndPoint; } - set { SetConfig(ref masterEndPoint, value); } - } - - - internal bool CheckInfoReplication() - { - lastInfoReplicationCheckTicks = Environment.TickCount; - PhysicalBridge bridge; - if (version >= RedisFeatures.v2_8_0 && multiplexer.CommandMap.IsAvailable(RedisCommand.INFO) - && (bridge = GetBridge(ConnectionType.Interactive, false)) != null) - { - var msg = Message.Create(-1, CommandFlags.FireAndForget | CommandFlags.HighPriority | CommandFlags.NoRedirect, RedisCommand.INFO, RedisLiterals.replication); - msg.SetInternalCall(); - QueueDirectFireAndForget(msg, ResultProcessor.AutoConfigure, bridge); - return true; - } - return false; - } - private int lastInfoReplicationCheckTicks; - - private int _heartBeatActive; - internal void OnHeartbeat() - { - // don't overlap operations on an endpoint - if (Interlocked.CompareExchange(ref _heartBeatActive, 1, 0) == 0) - { - try - { - - - interactive?.OnHeartbeat(false); - subscription?.OnHeartbeat(false); - } - catch (Exception ex) - { - multiplexer.OnInternalError(ex, EndPoint); - } - finally - { - Interlocked.Exchange(ref _heartBeatActive, 0); - } - } - } - - internal Task QueueDirectAsync(Message message, ResultProcessor processor, object asyncState = null, PhysicalBridge bridge = null) - { - var tcs = TaskSource.CreateDenyExecSync(asyncState); - var source = ResultBox.Get(tcs); - message.SetSource(processor, source); - if (bridge == null) bridge = GetBridge(message.Command); - if (!bridge.TryEnqueue(message, isSlave)) - { - ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, multiplexer.IncludePerformanceCountersInExceptions, message.Command, message, this, multiplexer.GetServerSnapshot())); - } - return tcs.Task; - } - - internal void QueueDirectFireAndForget(Message message, ResultProcessor processor, PhysicalBridge bridge = null) - { - if (message != null) - { - message.SetSource(processor, null); - multiplexer.Trace("Enqueue: " + message); - (bridge ?? GetBridge(message.Command)).TryEnqueue(message, isSlave); - } - } - - internal void ReportNextFailure() - { - interactive?.ReportNextFailure(); - subscription?.ReportNextFailure(); - } - - internal Task SendTracer(TextWriter log = null) - { - var msg = GetTracerMessage(false); - msg = LoggingMessage.Create(log, msg); - return QueueDirectAsync(msg, ResultProcessor.Tracer); - } - - internal string Summary() - { - var sb = new StringBuilder(Format.ToString(endpoint)) - .Append(": ").Append(serverType).Append(" v").Append(version).Append(", ").Append(isSlave ? "slave" : "master"); - - - if (databases > 0) sb.Append("; ").Append(databases).Append(" databases"); - if (writeEverySeconds > 0) - sb.Append("; keep-alive: ").Append(TimeSpan.FromSeconds(writeEverySeconds)); - var tmp = interactive; - sb.Append("; int: ").Append(tmp?.ConnectionState.ToString() ?? "n/a"); - tmp = subscription; - if(tmp == null) - { - sb.Append("; sub: n/a"); - } else - { - var state = tmp.ConnectionState; - sb.Append("; sub: ").Append(state); - if(state == PhysicalBridge.State.ConnectedEstablished) - { - sb.Append(", ").Append(tmp.SubscriptionCount).Append(" active"); - } - } - - var flags = unselectableReasons; - if (flags != 0) - { - sb.Append("; not in use: ").Append(flags); - } - return sb.ToString(); - } - internal void WriteDirectOrQueueFireAndForget(PhysicalConnection connection, Message message, ResultProcessor processor) - { - if (message != null) - { - message.SetSource(processor, null); - if (connection == null) - { - multiplexer.Trace("Enqueue: " + message); - GetBridge(message.Command).TryEnqueue(message, isSlave); - } - else - { - multiplexer.Trace("Writing direct: " + message); - connection.Bridge.WriteMessageDirect(connection, message); - } - } - } - - private PhysicalBridge CreateBridge(ConnectionType type, TextWriter log) - { - multiplexer.Trace(type.ToString()); - var bridge = new PhysicalBridge(this, type); - bridge.TryConnect(log); - return bridge; - } - void Handshake(PhysicalConnection connection, TextWriter log) - { - multiplexer.LogLocked(log, "Server handshake"); - if (connection == null) - { - multiplexer.Trace("No connection!?"); - return; - } - Message msg; - string password = multiplexer.RawConfig.Password; - if (!string.IsNullOrWhiteSpace(password)) - { - multiplexer.LogLocked(log, "Authenticating (password)"); - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.AUTH, (RedisValue)password); - msg.SetInternalCall(); - WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.DemandOK); - } - if (multiplexer.CommandMap.IsAvailable(RedisCommand.CLIENT)) - { - string name = multiplexer.ClientName; - if (!string.IsNullOrWhiteSpace(name)) - { - name = nameSanitizer.Replace(name, ""); - if (!string.IsNullOrWhiteSpace(name)) - { - multiplexer.LogLocked(log, "Setting client name: {0}", name); - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETNAME, (RedisValue)name); - msg.SetInternalCall(); - WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.DemandOK); - } - } - } - - var connType = connection.Bridge.ConnectionType; - - if (connType == ConnectionType.Interactive) - { - multiplexer.LogLocked(log, "Auto-configure..."); - AutoConfigure(connection); - } - multiplexer.LogLocked(log, "Sending critical tracer: {0}", connection.Bridge); - var tracer = GetTracerMessage(true); - tracer = LoggingMessage.Create(log, tracer); - WriteDirectOrQueueFireAndForget(connection, tracer, ResultProcessor.EstablishConnection); - - - // note: this **must** be the last thing on the subscription handshake, because after this - // we will be in subscriber mode: regular commands cannot be sent - if (connType == ConnectionType.Subscription) - { - var configChannel = multiplexer.ConfigurationChangedChannel; - if(configChannel != null) - { - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.SUBSCRIBE, (RedisChannel)configChannel); - WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.TrackSubscriptions); - } - } - multiplexer.LogLocked(log, "Flushing outbound buffer"); - connection.Flush(); - } - - private void SetConfig(ref T field, T value, [CallerMemberName] string caller = null) - { - if(!EqualityComparer.Default.Equals(field, value)) - { - multiplexer.Trace(caller + " changed from " + field + " to " + value, "Configuration"); - field = value; - multiplexer.ReconfigureIfNeeded(endpoint, false, caller); - } - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ServerSelectionStrategy.cs b/StackExchange.Redis/StackExchange/Redis/ServerSelectionStrategy.cs deleted file mode 100644 index 365b897ad..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ServerSelectionStrategy.cs +++ /dev/null @@ -1,299 +0,0 @@ -using System; -using System.Net; -using System.Threading; - -namespace StackExchange.Redis -{ - internal sealed class ServerSelectionStrategy - { - public const int NoSlot = -1, MultipleSlots = -2; - private const int RedisClusterSlotCount = 16384; - static readonly ushort[] crc16tab = - { - 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, - 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, - 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, - 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, - 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, - 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, - 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, - 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, - 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, - 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, - 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, - 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, - 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, - 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, - 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, - 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, - 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, - 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, - 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, - 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, - 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, - 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, - 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, - 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, - 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, - 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, - 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, - 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, - 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, - 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, - 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, - 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 - }; - - private readonly ConnectionMultiplexer multiplexer; - private int anyStartOffset; - - private ServerEndPoint[] map; - - private ServerType serverType = ServerType.Standalone; - - public ServerSelectionStrategy(ConnectionMultiplexer multiplexer) - { - this.multiplexer = multiplexer; - } - - public ServerType ServerType { get { return serverType; } set { serverType = value; } } - internal int TotalSlots => RedisClusterSlotCount; - - /// - /// Computes the hash-slot that would be used by the given key - /// - public unsafe int HashSlot(RedisKey key) - { - //HASH_SLOT = CRC16(key) mod 16384 - if (key.IsNull) return NoSlot; - unchecked - { - var blob = (byte[])key; - fixed (byte* ptr = blob) - { - int offset = 0, count = blob.Length, start, end; - if ((start = IndexOf(ptr, (byte)'{', 0, count - 1)) >= 0 - && (end = IndexOf(ptr, (byte)'}', start + 1, count)) >= 0 - && --end != start) - { - offset = start + 1; - count = end - start; // note we already subtracted one via --end - } - - uint crc = 0; - for (int i = 0; i < count; i++) - crc = ((crc << 8) ^ crc16tab[((crc >> 8) ^ ptr[offset++]) & 0x00FF]) & 0x0000FFFF; - return (int)(crc % RedisClusterSlotCount); - } - } - } - - public ServerEndPoint Select(Message message) - { - if (message == null) throw new ArgumentNullException(nameof(message)); - int slot = NoSlot; - switch (serverType) - { - case ServerType.Cluster: - case ServerType.Twemproxy: // strictly speaking twemproxy uses a different hashing algo, but the hash-tag behavior is - // the same, so this does a pretty good job of spotting illegal commands before sending them - - slot = message.GetHashSlot(this); - if (slot == MultipleSlots) throw ExceptionFactory.MultiSlot(multiplexer.IncludeDetailInExceptions, message); - break; - - } - return Select(slot, message.Command, message.Flags); - } - - public ServerEndPoint Select(int db, RedisCommand command, RedisKey key, CommandFlags flags) - { - int slot = serverType == ServerType.Cluster ? HashSlot(key) : NoSlot; - return Select(slot, command, flags); - } - - public bool TryResend(int hashSlot, Message message, EndPoint endpoint, bool isMoved) - { - try - { - if (serverType == ServerType.Standalone || hashSlot < 0 || hashSlot >= RedisClusterSlotCount) return false; - - ServerEndPoint server = multiplexer.GetServerEndPoint(endpoint); - if (server != null) - { - bool retry = false; - if ((message.Flags & CommandFlags.NoRedirect) == 0) - { - message.SetAsking(!isMoved); - message.SetNoRedirect(); // once is enough - - // note that everything so far is talking about MASTER nodes; we might be - // wanting a SLAVE, so we'll check - ServerEndPoint resendVia = null; - var command = message.Command; - switch (Message.GetMasterSlaveFlags(message.Flags)) - { - case CommandFlags.DemandMaster: - resendVia = server.IsSelectable(command) ? server : null; - break; - case CommandFlags.PreferMaster: - resendVia = server.IsSelectable(command) ? server : FindSlave(server, command); - break; - case CommandFlags.PreferSlave: - resendVia = FindSlave(server, command) ?? (server.IsSelectable(command) ? server : null); - break; - case CommandFlags.DemandSlave: - resendVia = FindSlave(server, command); - break; - } - if (resendVia == null) - { - multiplexer.Trace("Unable to resend to " + endpoint); - } - else - { - message.PrepareToResend(resendVia, isMoved); - retry = resendVia.TryEnqueue(message); - } - } - - if (isMoved) // update map; note we can still update the map even if we aren't actually goint to resend - { - var arr = MapForMutation(); - var oldServer = arr[hashSlot]; - arr[hashSlot] = server; - if (oldServer != server) - { - multiplexer.OnHashSlotMoved(hashSlot, oldServer?.EndPoint, endpoint); - } - } - - return retry; - } - return false; - } - catch - { - return false; - } - } - - internal int CombineSlot(int oldSlot, int newSlot) - { - if (oldSlot == MultipleSlots || newSlot == NoSlot) return oldSlot; - if (oldSlot == NoSlot) return newSlot; - return oldSlot == newSlot ? oldSlot : MultipleSlots; - } - internal int CombineSlot(int oldSlot, RedisKey key) - { - if (oldSlot == MultipleSlots || key.IsNull) return oldSlot; - - int newSlot = HashSlot(key); - if (oldSlot == NoSlot) return newSlot; - return oldSlot == newSlot ? oldSlot : MultipleSlots; - } - internal int CountCoveredSlots() - { - var arr = map; - if (arr == null) return 0; - int count = 0; - for (int i = 0; i < arr.Length; i++) - if (arr[i] != null) count++; - return count; - } - - internal void UpdateClusterRange(int fromInclusive, int toInclusive, ServerEndPoint server) - { - var arr = MapForMutation(); - for (int i = fromInclusive; i <= toInclusive; i++) - { - arr[i] = server; - } - } - - static unsafe int IndexOf(byte* ptr, byte value, int start, int end) - { - for (int offset = start; offset < end; offset++) - if (ptr[offset] == value) return offset; - return -1; - } - - private ServerEndPoint Any(RedisCommand command, CommandFlags flags) - { - return multiplexer.AnyConnected(serverType, (uint)Interlocked.Increment(ref anyStartOffset), command, flags); - } - - private ServerEndPoint FindMaster(ServerEndPoint endpoint, RedisCommand command) - { - int max = 5; - do - { - if (!endpoint.IsSlave && endpoint.IsSelectable(command)) return endpoint; - - endpoint = endpoint.Master; - } while (endpoint != null && --max != 0); - return null; - } - - private ServerEndPoint FindSlave(ServerEndPoint endpoint, RedisCommand command) - { - if (endpoint.IsSlave && endpoint.IsSelectable(command)) return endpoint; - - var slaves = endpoint.Slaves; - var len = slaves.Length; - uint startOffset = len <= 1 ? 0 : endpoint.NextReplicaOffset(); - for (int i = 0; i < len; i++) - { - endpoint = slaves[(int)(((uint)i + startOffset) % len)]; - if (endpoint.IsSlave && endpoint.IsSelectable(command)) return endpoint; - } - return null; - } - - private ServerEndPoint[] MapForMutation() - { - var arr = map; - if (arr == null) - { - lock (this) - { - if (map == null) map = new ServerEndPoint[RedisClusterSlotCount]; - arr = map; - } - } - return arr; - } - - private ServerEndPoint Select(int slot, RedisCommand command, CommandFlags flags) - { - flags = Message.GetMasterSlaveFlags(flags); // only intersted in master/slave preferences - - ServerEndPoint[] arr; - if (slot == NoSlot || (arr = map) == null) return Any(command, flags); - - ServerEndPoint endpoint = arr[slot], testing; - // but: ^^^ is the MASTER slots; if we want a slave, we need to do some thinking - - if (endpoint != null) - { - switch (flags) - { - case CommandFlags.DemandSlave: - return FindSlave(endpoint, command) ?? Any(command, flags); - case CommandFlags.PreferSlave: - testing = FindSlave(endpoint, command); - if (testing != null) return testing; - break; - case CommandFlags.DemandMaster: - return FindMaster(endpoint, command) ?? Any(command, flags); - case CommandFlags.PreferMaster: - testing = FindMaster(endpoint, command); - if (testing != null) return testing; - break; - } - if (endpoint.IsSelectable(command)) return endpoint; - } - return Any(command, flags); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/ServerType.cs b/StackExchange.Redis/StackExchange/Redis/ServerType.cs deleted file mode 100644 index 80072f34a..000000000 --- a/StackExchange.Redis/StackExchange/Redis/ServerType.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace StackExchange.Redis -{ - /// - /// Indicates the flavor of a particular redis server - /// - public enum ServerType - { - /// - /// Classic redis-server server - /// - Standalone, - /// - /// Monitoring/configuration redis-sentinel server - /// - Sentinel, - /// - /// Distributed redis-cluster server - /// - Cluster, - /// - /// Distributed redis installation via twemproxy - /// - Twemproxy - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/SetOperation.cs b/StackExchange.Redis/StackExchange/Redis/SetOperation.cs deleted file mode 100644 index fdb1acda4..000000000 --- a/StackExchange.Redis/StackExchange/Redis/SetOperation.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace StackExchange.Redis -{ - /// - /// Describes an algebraic set operation that can be performed to combine multiple sets - /// - public enum SetOperation - { - /// - /// Returns the members of the set resulting from the union of all the given sets. - /// - Union, - /// - /// Returns the members of the set resulting from the intersection of all the given sets. - /// - Intersect, - /// - /// Returns the members of the set resulting from the difference between the first set and all the successive sets. - /// - Difference - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/SocketManager.NoPoll.cs b/StackExchange.Redis/StackExchange/Redis/SocketManager.NoPoll.cs deleted file mode 100644 index c26046646..000000000 --- a/StackExchange.Redis/StackExchange/Redis/SocketManager.NoPoll.cs +++ /dev/null @@ -1,16 +0,0 @@ -#if !FEATURE_SOCKET_MODE_POLL - -namespace StackExchange.Redis -{ - partial class SocketManager - { - internal const SocketMode DefaultSocketMode = SocketMode.Async; - - private void OnAddRead(System.Net.Sockets.Socket socket, ISocketCallback callback) - { - throw new System.NotSupportedException(); - } - } -} - -#endif \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/SocketManager.Poll.cs b/StackExchange.Redis/StackExchange/Redis/SocketManager.Poll.cs deleted file mode 100644 index 9b573fd80..000000000 --- a/StackExchange.Redis/StackExchange/Redis/SocketManager.Poll.cs +++ /dev/null @@ -1,428 +0,0 @@ -#if FEATURE_SOCKET_MODE_POLL -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Threading; - -namespace StackExchange.Redis -{ - partial class SocketManager - { - internal const SocketMode DefaultSocketMode = SocketMode.Poll; - static readonly IntPtr[] EmptyPointers = new IntPtr[0]; - static readonly WaitCallback HelpProcessItems = state => - { - var qdsl = state as QueueDrainSyncLock; - if (qdsl != null && qdsl.Consume()) - { - var mgr = qdsl.Manager; - mgr.ProcessItems(false); - qdsl.Pulse(); - } - }; - - private static ParameterizedThreadStart read = state => ((SocketManager)state).Read(); - - readonly Queue readQueue = new Queue(), errorQueue = new Queue(); - - private readonly Dictionary socketLookup = new Dictionary(); - - private int readerCount; - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("ws2_32.dll", SetLastError = true)] - internal static extern int select([In] int ignoredParameter, [In, Out] IntPtr[] readfds, [In, Out] IntPtr[] writefds, [In, Out] IntPtr[] exceptfds, [In] ref TimeValue timeout); - - private static void ProcessItems(Queue queue, CallbackOperation operation) - - { - if (queue == null) return; - while (true) - { - // get the next item (note we could be competing with a worker here, hence lock) - ISocketCallback callback; - lock (queue) - { - if (queue.Count == 0) break; - callback = queue.Dequeue(); - } - if (callback != null) - { - try - { - switch (operation) - { - case CallbackOperation.Read: callback.Read(); break; - case CallbackOperation.Error: callback.Error(); break; - } - } - catch (Exception ex) - { - Trace.WriteLine(ex); - } - } - } - } - - private void OnAddRead(Socket socket, ISocketCallback callback) - { - if (socket == null) throw new ArgumentNullException(nameof(socket)); - if (callback == null) throw new ArgumentNullException(nameof(callback)); - - lock (socketLookup) - { - if (isDisposed) throw new ObjectDisposedException(name); - - var handle = socket.Handle; - if (handle == IntPtr.Zero) throw new ObjectDisposedException("socket"); - socketLookup.Add(handle, new SocketPair(socket, callback)); - if (socketLookup.Count == 1) - { - Monitor.PulseAll(socketLookup); - if (Interlocked.CompareExchange(ref readerCount, 0, 0) == 0) - StartReader(); - } - } - } - - partial void OnDispose() - { - lock (socketLookup) - { - isDisposed = true; - socketLookup.Clear(); - Monitor.PulseAll(socketLookup); - } - } - - partial void OnShutdown(Socket socket) - { - lock (socketLookup) - { - socketLookup.Remove(socket.Handle); - } - } - - private void ProcessItems(bool setState) - { - if(setState) managerState = ManagerState.ProcessReadQueue; - ProcessItems(readQueue, CallbackOperation.Read); - if (setState) managerState = ManagerState.ProcessErrorQueue; - ProcessItems(errorQueue, CallbackOperation.Error); - } - private void Read() - { - bool weAreReader = false; - try - { - weAreReader = Interlocked.CompareExchange(ref readerCount, 1, 0) == 0; - if (weAreReader) - { - managerState = ManagerState.Preparing; - ReadImpl(); - managerState = ManagerState.Inactive; - } - } - catch (Exception ex) - { - if (weAreReader) - { - managerState = ManagerState.Faulted; - } - Debug.WriteLine(ex); - Trace.WriteLine(ex); - } - finally - { - if (weAreReader) Interlocked.Exchange(ref readerCount, 0); - } - } - - internal ManagerState State => managerState; - private volatile ManagerState managerState; - private volatile int lastErrorTicks; - internal string LastErrorTimeRelative() - { - var tmp = lastErrorTicks; - if (tmp == 0) return "never"; - return unchecked(Environment.TickCount - tmp) + "ms ago"; - } - private ISocketCallback GetCallback(IntPtr key) - { - lock(socketLookup) - { - SocketPair pair; - return socketLookup.TryGetValue(key, out pair) ? pair.Callback : null; - } - } - private void ReadImpl() - { - List dead = null, active = new List(); - List activeCallbacks = new List(); - IntPtr[] readSockets = EmptyPointers, errorSockets = EmptyPointers; - long lastHeartbeat = Environment.TickCount; - SocketPair[] allSocketPairs = null; - while (true) - { - managerState = ManagerState.CheckForHeartbeat; - active.Clear(); - activeCallbacks.Clear(); - dead?.Clear(); - - // this check is actually a pace-maker; sometimes the Timer callback stalls for - // extended periods of time, which can cause socket disconnect - long now = Environment.TickCount; - if (unchecked(now - lastHeartbeat) >= 15000) - { - managerState = ManagerState.ExecuteHeartbeat; - lastHeartbeat = now; - lock (socketLookup) - { - if (allSocketPairs == null || allSocketPairs.Length != socketLookup.Count) - allSocketPairs = new SocketPair[socketLookup.Count]; - socketLookup.Values.CopyTo(allSocketPairs, 0); - } - foreach (var pair in allSocketPairs) - { - var callback = pair.Callback; - if (callback != null) try { callback.OnHeartbeat(); } catch { } - } - } - - managerState = ManagerState.LocateActiveSockets; - lock (socketLookup) - { - if (isDisposed) return; - - if (socketLookup.Count == 0) - { - // if empty, give it a few seconds chance before exiting - managerState = ManagerState.NoSocketsPause; - Monitor.Wait(socketLookup, TimeSpan.FromSeconds(20)); - if (socketLookup.Count == 0) return; // nothing new came in, so exit - } - managerState = ManagerState.PrepareActiveSockets; - foreach (var pair in socketLookup) - { - var socket = pair.Value.Socket; - if (socket.Handle == pair.Key && socket.Connected) - if (pair.Value.Socket.Connected) - { - active.Add(pair.Key); - activeCallbacks.Add(pair.Value.Callback); - } - else - { - (dead ?? (dead = new List())).Add(pair.Key); - } - } - if (dead != null && dead.Count != 0) - { - managerState = ManagerState.CullDeadSockets; - foreach (var socket in dead) socketLookup.Remove(socket); - } - } - int pollingSockets = active.Count; - if (pollingSockets == 0) - { - // nobody had actual sockets; just sleep - managerState = ManagerState.NoActiveSocketsPause; - Thread.Sleep(10); - continue; - } - - if (readSockets.Length < active.Count + 1) - { - managerState = ManagerState.GrowingSocketArray; - ConnectionMultiplexer.TraceWithoutContext("Resizing socket array for " + active.Count + " sockets"); - readSockets = new IntPtr[active.Count + 6]; // leave so space for growth - errorSockets = new IntPtr[active.Count + 6]; - } - managerState = ManagerState.CopyingPointersForSelect; - readSockets[0] = errorSockets[0] = (IntPtr)active.Count; - active.CopyTo(readSockets, 1); - active.CopyTo(errorSockets, 1); - int ready; - try - { - var timeout = new TimeValue(1000); - managerState = ManagerState.ExecuteSelect; - ready = select(0, readSockets, null, errorSockets, ref timeout); - managerState = ManagerState.ExecuteSelectComplete; - if (ready <= 0) // -ve typically means a socket was disposed just before; just retry - { - bool hasWorkToDo = false; - if (ready == 0) - { - managerState = ManagerState.CheckForStaleConnections; - foreach (var s in activeCallbacks) - { - if (s.IsDataAvailable) - { - hasWorkToDo = true; - } - else - { -#pragma warning disable 0420 - s.CheckForStaleConnection(ref managerState); -#pragma warning restore 0420 - } - } - managerState = ManagerState.CheckForStaleConnectionsDone; - } - else - { - lastErrorTicks = Environment.TickCount; - } - if (!hasWorkToDo) - { - continue; - } - } - ConnectionMultiplexer.TraceWithoutContext((int)readSockets[0] != 0, "Read sockets: " + (int)readSockets[0]); - ConnectionMultiplexer.TraceWithoutContext((int)errorSockets[0] != 0, "Error sockets: " + (int)errorSockets[0]); - } - catch (Exception ex) - { // this typically means a socket was disposed just before; just retry - Trace.WriteLine(ex.Message); - continue; - } - - bool haveWork = false; - int queueCount = (int)readSockets[0]; - if (queueCount != 0) - { - managerState = ManagerState.EnqueueRead; - lock (readQueue) - { - for (int i = 1; i <= queueCount; i++) - { - var callback = GetCallback(readSockets[i]); - if (callback != null) - { - readQueue.Enqueue(callback); - haveWork = true; - } - } - } - } - queueCount = (int)errorSockets[0]; - if (queueCount != 0) - { - managerState = ManagerState.EnqueueError; - lock (errorQueue) - { - for (int i = 1; i <= queueCount; i++) - { - var callback = GetCallback(errorSockets[i]); - if (callback != null) - { - errorQueue.Enqueue(callback); - haveWork = true; - } - } - } - } - if(!haveWork) - { - // edge case: select is returning 0, but data could still be available - managerState = ManagerState.EnqueueReadFallback; - lock (readQueue) - { - foreach (var callback in activeCallbacks) - { - if(callback.IsDataAvailable) - { - readQueue.Enqueue(callback); - } - } - } - } - - - if (ready >= 5) // number of sockets we should attempt to process by ourself before asking for help - { - // seek help, work in parallel, then synchronize - var obj = new QueueDrainSyncLock(this); - lock (obj) - { - managerState = ManagerState.RequestAssistance; - ThreadPool.QueueUserWorkItem(HelpProcessItems, obj); - managerState = ManagerState.ProcessQueues; - ProcessItems(true); - if (!obj.Consume()) - { // then our worker arrived and picked up work; we need - // to let it finish; note that if it *didn't* get that far - // yet, the Consume() call will mean that it never tries - Monitor.Wait(obj); - } - } - } - else - { - // just do it ourself - managerState = ManagerState.ProcessQueues; - ProcessItems(true); - } - } - } - - private void StartReader() - { - var thread = new Thread(read, 32*1024) // don't need a huge stack - { - Priority = useHighPrioritySocketThreads ? ThreadPriority.AboveNormal : ThreadPriority.Normal, - Name = name + ":Read", - IsBackground = true - }; - thread.Start(this); - } - [StructLayout(LayoutKind.Sequential)] - internal struct TimeValue - { - public int Seconds; - public int Microseconds; - public TimeValue(int microSeconds) - { - Seconds = (int)(microSeconds / 1000000L); - Microseconds = (int)(microSeconds % 1000000L); - } - } - - struct SocketPair - { - public readonly ISocketCallback Callback; - public readonly Socket Socket; - public SocketPair(Socket socket, ISocketCallback callback) - { - Socket = socket; - Callback = callback; - } - } - sealed class QueueDrainSyncLock - { - private int workers; - public QueueDrainSyncLock(SocketManager manager) - { - Manager = manager; - } - public SocketManager Manager { get; } - - internal bool Consume() - { - return Interlocked.CompareExchange(ref workers, 1, 0) == 0; - } - - internal void Pulse() - { - lock (this) - { - Monitor.PulseAll(this); - } - } - } - } -} -#endif \ No newline at end of file diff --git a/StackExchange.Redis/StackExchange/Redis/SocketManager.cs b/StackExchange.Redis/StackExchange/Redis/SocketManager.cs deleted file mode 100644 index 6504ce9ed..000000000 --- a/StackExchange.Redis/StackExchange/Redis/SocketManager.cs +++ /dev/null @@ -1,481 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Threading; -#if CORE_CLR -using System.Runtime.InteropServices; -using System.Threading.Tasks; -#endif - -namespace StackExchange.Redis -{ - internal enum SocketMode - { - Abort, - Poll, - Async - } - /// - /// Allows callbacks from SocketManager as work is discovered - /// - internal partial interface ISocketCallback - { - /// - /// Indicates that a socket has connected - /// - SocketMode Connected(Stream stream, TextWriter log); - /// - /// Indicates that the socket has signalled an error condition - /// - void Error(); - - void OnHeartbeat(); - - /// - /// Indicates that data is available on the socket, and that the consumer should read synchronously from the socket while there is data - /// - void Read(); - /// - /// Indicates that we cannot know whether data is available, and that the consume should commence reading asynchronously - /// - void StartReading(); - - // check for write-read timeout - void CheckForStaleConnection(ref SocketManager.ManagerState state); - - bool IsDataAvailable { get; } - } - - internal struct SocketToken - { - internal readonly Socket Socket; - public SocketToken(Socket socket) - { - Socket = socket; - } - public int Available => Socket?.Available ?? 0; - - public bool HasValue => Socket != null; - } - - /// - /// A SocketManager monitors multiple sockets for availability of data; this is done using - /// the Socket.Select API and a dedicated reader-thread, which allows for fast responses - /// even when the system is under ambient load. - /// - public sealed partial class SocketManager : IDisposable - { - internal enum ManagerState - { - Inactive, - Preparing, - Faulted, - CheckForHeartbeat, - ExecuteHeartbeat, - LocateActiveSockets, - NoSocketsPause, - PrepareActiveSockets, - CullDeadSockets, - NoActiveSocketsPause, - GrowingSocketArray, - CopyingPointersForSelect, - ExecuteSelect, - ExecuteSelectComplete, - CheckForStaleConnections, - - RecordConnectionFailed_OnInternalError, - RecordConnectionFailed_OnDisconnected, - RecordConnectionFailed_ReportFailure, - RecordConnectionFailed_OnConnectionFailed, - RecordConnectionFailed_FailOutstanding, - RecordConnectionFailed_ShutdownSocket, - - CheckForStaleConnectionsDone, - EnqueueRead, - EnqueueError, - EnqueueReadFallback, - RequestAssistance, - ProcessQueues, - ProcessReadQueue, - ProcessErrorQueue, - - } - private static readonly ParameterizedThreadStart writeAllQueues = context => - { - try { ((SocketManager)context).WriteAllQueues(); } catch { } - }; - - private static readonly WaitCallback writeOneQueue = context => - { - - try { ((SocketManager)context).WriteOneQueue(); } catch { } - }; - - private readonly string name; - - private readonly Queue writeQueue = new Queue(); - - bool isDisposed; - private bool useHighPrioritySocketThreads = true; - - /// - /// Creates a new (optionally named) SocketManager instance - /// - public SocketManager(string name = null) : this(name, true) { } - - /// - /// Creates a new SocketManager instance - /// - public SocketManager(string name, bool useHighPrioritySocketThreads) - { - if (string.IsNullOrWhiteSpace(name)) name = GetType().Name; - this.name = name; - this.useHighPrioritySocketThreads = useHighPrioritySocketThreads; - - // we need a dedicated writer, because when under heavy ambient load - // (a busy asp.net site, for example), workers are not reliable enough -#if !CORE_CLR - Thread dedicatedWriter = new Thread(writeAllQueues, 32 * 1024); // don't need a huge stack; - dedicatedWriter.Priority = useHighPrioritySocketThreads ? ThreadPriority.AboveNormal : ThreadPriority.Normal; -#else - Thread dedicatedWriter = new Thread(writeAllQueues); -#endif - dedicatedWriter.Name = name + ":Write"; - dedicatedWriter.IsBackground = true; // should not keep process alive - dedicatedWriter.Start(this); // will self-exit when disposed - } - - private enum CallbackOperation - { - Read, - Error - } - - /// - /// Gets the name of this SocketManager instance - /// - public string Name => name; - - /// - /// Releases all resources associated with this instance - /// - public void Dispose() - { - lock (writeQueue) - { - // make sure writer threads know to exit - isDisposed = true; - Monitor.PulseAll(writeQueue); - } - OnDispose(); - } - - internal SocketToken BeginConnect(EndPoint endpoint, ISocketCallback callback, ConnectionMultiplexer multiplexer, TextWriter log) - { - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - SetFastLoopbackOption(socket); - socket.NoDelay = true; - try - { - CompletionType connectCompletionType = CompletionType.Any; - this.ShouldForceConnectCompletionType(ref connectCompletionType); - - var formattedEndpoint = Format.ToString(endpoint); - var tuple = Tuple.Create(socket, callback); - if (endpoint is DnsEndPoint) - { - // A work-around for a Mono bug in BeginConnect(EndPoint endpoint, AsyncCallback callback, object state) - DnsEndPoint dnsEndpoint = (DnsEndPoint)endpoint; - -#if CORE_CLR - multiplexer.LogLocked(log, "BeginConnect: {0}", formattedEndpoint); - socket.ConnectAsync(dnsEndpoint.Host, dnsEndpoint.Port).ContinueWith(t => - { - multiplexer.LogLocked(log, "EndConnect: {0}", formattedEndpoint); - EndConnectImpl(t, multiplexer, log, tuple); - multiplexer.LogLocked(log, "Connect complete: {0}", formattedEndpoint); - }); -#else - CompletionTypeHelper.RunWithCompletionType( - cb => { - multiplexer.LogLocked(log, "BeginConnect: {0}", formattedEndpoint); - return socket.BeginConnect(dnsEndpoint.Host, dnsEndpoint.Port, cb, tuple); - }, - ar => { - multiplexer.LogLocked(log, "EndConnect: {0}", formattedEndpoint); - EndConnectImpl(ar, multiplexer, log, tuple); - multiplexer.LogLocked(log, "Connect complete: {0}", formattedEndpoint); - }, - connectCompletionType); -#endif - } - else - { -#if CORE_CLR - multiplexer.LogLocked(log, "BeginConnect: {0}", formattedEndpoint); - socket.ConnectAsync(endpoint).ContinueWith(t => - { - multiplexer.LogLocked(log, "EndConnect: {0}", formattedEndpoint); - EndConnectImpl(t, multiplexer, log, tuple); - }); -#else - CompletionTypeHelper.RunWithCompletionType( - cb => { - multiplexer.LogLocked(log, "BeginConnect: {0}", formattedEndpoint); - return socket.BeginConnect(endpoint, cb, tuple); - }, - ar => { - multiplexer.LogLocked(log, "EndConnect: {0}", formattedEndpoint); - EndConnectImpl(ar, multiplexer, log, tuple); - multiplexer.LogLocked(log, "Connect complete: {0}", formattedEndpoint); - }, - connectCompletionType); -#endif - } - } - catch (NotImplementedException ex) - { - if (!(endpoint is IPEndPoint)) - { - throw new InvalidOperationException("BeginConnect failed with NotImplementedException; consider using IP endpoints, or enable ResolveDns in the configuration", ex); - } - throw; - } - var token = new SocketToken(socket); - return token; - } - internal void SetFastLoopbackOption(Socket socket) - { - // SIO_LOOPBACK_FAST_PATH (http://msdn.microsoft.com/en-us/library/windows/desktop/jj841212%28v=vs.85%29.aspx) - // Speeds up localhost operations significantly. OK to apply to a socket that will not be hooked up to localhost, - // or will be subject to WFP filtering. - const int SIO_LOOPBACK_FAST_PATH = -1744830448; - -#if !CORE_CLR - // windows only - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - // Win8/Server2012+ only - var osVersion = Environment.OSVersion.Version; - if (osVersion.Major > 6 || osVersion.Major == 6 && osVersion.Minor >= 2) - { - byte[] optionInValue = BitConverter.GetBytes(1); - socket.IOControl(SIO_LOOPBACK_FAST_PATH, optionInValue, null); - } - } -#else - try - { - // Ioctl is not supported on other platforms at the moment - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - byte[] optionInValue = BitConverter.GetBytes(1); - socket.IOControl(SIO_LOOPBACK_FAST_PATH, optionInValue, null); - } - } - catch (SocketException) - { - } - catch (PlatformNotSupportedException) - { - // Fix for https://github.com/StackExchange/StackExchange.Redis/issues/582 - // Checking the platform can fail on some platforms. However, we don't - // care if the platform check fails because this is for a Windows - // optimization, and checking the platform will not fail on Windows. - } -#endif - } - - internal void RequestWrite(PhysicalBridge bridge, bool forced) - { - if (Interlocked.CompareExchange(ref bridge.inWriteQueue, 1, 0) == 0 || forced) - { - lock (writeQueue) - { - writeQueue.Enqueue(bridge); - if (writeQueue.Count == 1) - { - Monitor.PulseAll(writeQueue); - } - else if (writeQueue.Count >= 2) - { // struggling are we? let's have some help dealing with the backlog - ThreadPool.QueueUserWorkItem(writeOneQueue, this); - } - } - } - } - - internal void Shutdown(SocketToken token) - { - Shutdown(token.Socket); - } - - private void EndConnectImpl(IAsyncResult ar, ConnectionMultiplexer multiplexer, TextWriter log, Tuple tuple) - { - try - { - bool ignoreConnect = false; - ShouldIgnoreConnect(tuple.Item2, ref ignoreConnect); - if (ignoreConnect) return; - var socket = tuple.Item1; - var callback = tuple.Item2; -#if CORE_CLR - multiplexer.Wait((Task)ar); // make it explode if invalid (note: already complete at this point) -#else - socket.EndConnect(ar); -#endif - var netStream = new NetworkStream(socket, false); - var socketMode = callback?.Connected(netStream, log) ?? SocketMode.Abort; - switch (socketMode) - { - case SocketMode.Poll: - multiplexer.LogLocked(log, "Starting poll"); - OnAddRead(socket, callback); - break; - case SocketMode.Async: - multiplexer.LogLocked(log, "Starting read"); - try - { callback.StartReading(); } - catch (Exception ex) - { - ConnectionMultiplexer.TraceWithoutContext(ex.Message); - Shutdown(socket); - } - break; - default: - ConnectionMultiplexer.TraceWithoutContext("Aborting socket"); - Shutdown(socket); - break; - } - } - catch (ObjectDisposedException) - { - multiplexer.LogLocked(log, "(socket shutdown)"); - if (tuple != null) - { - try - { tuple.Item2.Error(); } - catch (Exception inner) - { - ConnectionMultiplexer.TraceWithoutContext(inner.Message); - } - } - } - catch(Exception outer) - { - ConnectionMultiplexer.TraceWithoutContext(outer.Message); - if (tuple != null) - { - try - { tuple.Item2.Error(); } - catch (Exception inner) - { - ConnectionMultiplexer.TraceWithoutContext(inner.Message); - } - } - } - } - - partial void OnDispose(); - partial void OnShutdown(Socket socket); - - partial void ShouldIgnoreConnect(ISocketCallback callback, ref bool ignore); - - partial void ShouldForceConnectCompletionType(ref CompletionType completionType); - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] - private void Shutdown(Socket socket) - { - if (socket != null) - { - OnShutdown(socket); - try { socket.Shutdown(SocketShutdown.Both); } catch { } -#if !CORE_CLR - try { socket.Close(); } catch { } -#endif - try { socket.Dispose(); } catch { } - } - } - - private void WriteAllQueues() - { - while (true) - { - PhysicalBridge bridge; - lock (writeQueue) - { - if (writeQueue.Count == 0) - { - if (isDisposed) break; // <========= exit point - Monitor.Wait(writeQueue); - if (isDisposed) break; // (woken by Dispose) - if (writeQueue.Count == 0) continue; // still nothing... - } - bridge = writeQueue.Dequeue(); - } - - switch (bridge.WriteQueue(200)) - { - case WriteResult.MoreWork: - case WriteResult.QueueEmptyAfterWrite: - // back of the line! - lock (writeQueue) - { - writeQueue.Enqueue(bridge); - } - break; - case WriteResult.CompetingWriter: - break; - case WriteResult.NoConnection: - Interlocked.Exchange(ref bridge.inWriteQueue, 0); - break; - case WriteResult.NothingToDo: - if (!bridge.ConfirmRemoveFromWriteQueue()) - { // more snuck in; back of the line! - lock (writeQueue) - { - writeQueue.Enqueue(bridge); - } - } - break; - } - } - } - - private void WriteOneQueue() - { - PhysicalBridge bridge; - lock (writeQueue) - { - bridge = writeQueue.Count == 0 ? null : writeQueue.Dequeue(); - } - if (bridge == null) return; - bool keepGoing; - do - { - switch (bridge.WriteQueue(-1)) - { - case WriteResult.MoreWork: - case WriteResult.QueueEmptyAfterWrite: - keepGoing = true; - break; - case WriteResult.NothingToDo: - keepGoing = !bridge.ConfirmRemoveFromWriteQueue(); - break; - case WriteResult.CompetingWriter: - keepGoing = false; - break; - case WriteResult.NoConnection: - Interlocked.Exchange(ref bridge.inWriteQueue, 0); - keepGoing = false; - break; - default: - keepGoing = false; - break; - } - } while (keepGoing); - } - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/SortType.cs b/StackExchange.Redis/StackExchange/Redis/SortType.cs deleted file mode 100644 index a1a034fc6..000000000 --- a/StackExchange.Redis/StackExchange/Redis/SortType.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace StackExchange.Redis -{ - /// - /// Specifies how to compare elements for sorting - /// - public enum SortType - { - /// - /// Elements are interpreted as a double-precision floating point number and sorted numerically - /// - Numeric, - /// - /// Elements are sorted using their alphabetic form (Redis is UTF-8 aware as long as the !LC_COLLATE environment variable is set at the server) - /// - Alphabetic - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/SortedSetEntry.cs b/StackExchange.Redis/StackExchange/Redis/SortedSetEntry.cs deleted file mode 100644 index d1e9b123f..000000000 --- a/StackExchange.Redis/StackExchange/Redis/SortedSetEntry.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; - -namespace StackExchange.Redis -{ - - - - - /// - /// Describes a sorted-set element with the corresponding value - /// - public struct SortedSetEntry : IEquatable, IComparable, IComparable - { - internal readonly RedisValue element; - internal readonly double score; - - /// - /// Initializes a SortedSetEntry value - /// - public SortedSetEntry(RedisValue element, double score) - { - this.element = element; - this.score = score; - } - /// - /// The unique element stored in the sorted set - /// - public RedisValue Element => element; - - /// - /// The score against the element - /// - public double Score => score; - - /// - /// The score against the element - /// -#if !CORE_CLR - [Browsable(false)] -#endif - [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Please use Score", false)] - public double Value { get { return score; } } - - /// - /// The unique element stored in the sorted set - /// -#if !CORE_CLR - [Browsable(false)] -#endif - [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Please use Element", false)] - public RedisValue Key { get { return element; } } - - /// - /// Converts to a key/value pair - /// - public static implicit operator KeyValuePair(SortedSetEntry value) - { - return new KeyValuePair(value.element, value.score); - } - /// - /// Converts from a key/value pair - /// - public static implicit operator SortedSetEntry(KeyValuePair value) - { - return new SortedSetEntry(value.Key, value.Value); - } - - /// - /// See Object.ToString() - /// - public override string ToString() - { - return element + ": " + score; - } - /// - /// See Object.GetHashCode() - /// - public override int GetHashCode() - { - return element.GetHashCode() ^ score.GetHashCode(); - } - /// - /// Compares two values for equality - /// - public override bool Equals(object obj) - { - return obj is SortedSetEntry && Equals((SortedSetEntry)obj); - } - - /// - /// Compares two values for equality - /// - public bool Equals(SortedSetEntry value) - { - return score == value.score && element == value.element; - } - - /// - /// Compares two values by score - /// - public int CompareTo(SortedSetEntry value) - { - return score.CompareTo(value.score); - } - - /// - /// Compares two values by score - /// - public int CompareTo(object value) - { - return value is SortedSetEntry ? CompareTo((SortedSetEntry)value) : -1; - } - - /// - /// Compares two values for equality - /// - public static bool operator ==(SortedSetEntry x, SortedSetEntry y) - { - return x.score == y.score && x.element == y.element; - } - /// - /// Compares two values for non-equality - /// - public static bool operator !=(SortedSetEntry x, SortedSetEntry y) - { - return x.score != y.score || x.element != y.element; - } - - } -} diff --git a/StackExchange.Redis/StackExchange/Redis/TaskSource.cs b/StackExchange.Redis/StackExchange/Redis/TaskSource.cs deleted file mode 100644 index d79bc33e5..000000000 --- a/StackExchange.Redis/StackExchange/Redis/TaskSource.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Threading.Tasks; -#if !PLAT_SAFE_CONTINUATIONS -using System; -using System.Diagnostics; -using System.Reflection; -using System.Reflection.Emit; -#endif - -namespace StackExchange.Redis -{ - /// - /// We want to prevent callers hijacking the reader thread; this is a bit nasty, but works; - /// see http://stackoverflow.com/a/22588431/23354 for more information; a huge - /// thanks to Eli Arbel for spotting this (even though it is pure evil; it is *my kind of evil*) - /// -#if DEBUG - public // for the unit tests in TaskTests.cs -#endif - static class TaskSource - { -#if !PLAT_SAFE_CONTINUATIONS - // on .NET < 4.6, it was possible to have threads hijacked; this is no longer a problem in 4.6 and core-clr 5, - // thanks to the new TaskCreationOptions.RunContinuationsAsynchronously, however we still need to be a little - // "test and react", as we could be targeting 4.5 but running on a 4.6 machine, in which case *it can still - // work the magic* (thanks to over-the-top install) - - /// - /// Indicates whether the specified task will not hijack threads when results are set - /// - public static readonly Func IsSyncSafe; - static TaskSource() - { - try - { - Type taskType = typeof(Task); - FieldInfo continuationField = taskType.GetField("m_continuationObject", BindingFlags.Instance | BindingFlags.NonPublic); - Type safeScenario = taskType.GetNestedType("SetOnInvokeMres", BindingFlags.NonPublic); - if (continuationField != null && continuationField.FieldType == typeof(object) && safeScenario != null) - { - var method = new DynamicMethod("IsSyncSafe", typeof(bool), new[] { typeof(Task) }, typeof(Task), true); - var il = method.GetILGenerator(); - //var hasContinuation = il.DefineLabel(); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, continuationField); - Label nonNull = il.DefineLabel(), goodReturn = il.DefineLabel(); - // check if null - il.Emit(OpCodes.Brtrue_S, nonNull); - il.MarkLabel(goodReturn); - il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Ret); - - // check if is a SetOnInvokeMres - if so, we're OK - il.MarkLabel(nonNull); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, continuationField); - il.Emit(OpCodes.Isinst, safeScenario); - il.Emit(OpCodes.Brtrue_S, goodReturn); - - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ret); - - IsSyncSafe = (Func)method.CreateDelegate(typeof(Func)); - - // and test them (check for an exception etc) - var tcs = new TaskCompletionSource(); - bool expectTrue = IsSyncSafe(tcs.Task); - tcs.Task.ContinueWith(delegate { }); - bool expectFalse = IsSyncSafe(tcs.Task); - tcs.SetResult(0); - if (!expectTrue || expectFalse) - { - // revert to not trusting /them - IsSyncSafe = null; - } - } - } - catch (Exception) - { - IsSyncSafe = null; - } - if (IsSyncSafe == null) - IsSyncSafe = t => false; // assume: not - } -#endif - /// - /// Create a new TaskCompletion source - /// - public static TaskCompletionSource Create(object asyncState) - { -#if PLAT_SAFE_CONTINUATIONS - return new TaskCompletionSource(asyncState, TaskCreationOptions.RunContinuationsAsynchronously); -#else - return new TaskCompletionSource(asyncState, TaskCreationOptions.None); -#endif - } - - /// - /// Create a new TaskCompletionSource that will not allow result-setting threads to be hijacked - /// - public static TaskCompletionSource CreateDenyExecSync(object asyncState) - { - var source = new TaskCompletionSource(asyncState); - //DenyExecSync(source.Task); - return source; - } - } -} diff --git a/StackExchange.Redis/build.cmd b/StackExchange.Redis/build.cmd deleted file mode 100644 index 7b5398723..000000000 --- a/StackExchange.Redis/build.cmd +++ /dev/null @@ -1 +0,0 @@ -dotnet msbuild "/t:Restore;Build;Pack" "/p:NuGetBuildTasksPackTargets='000'" "/p:PackageOutputPath=nupkgs" "/p:Configuration=Release" diff --git a/StrongName.ps1 b/StrongName.ps1 deleted file mode 100644 index 0def8818a..000000000 --- a/StrongName.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -$key = Import-StrongNameKeyPair -KeyFile StackExchange.Redis.snk -dir StackExchange.Redis*/bin/Release/StackExchange.Redis.dll | Set-StrongName -KeyPair $key -Verbose -NoBackup -Force -nuget pack StackExchange.Redis.StrongName.nuspec \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..678032414 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,84 @@ +image: +- Visual Studio 2019 + +init: + - git config --global core.autocrlf input + +install: +- cmd: >- + choco install dotnet-9.0-sdk + + cd tests\RedisConfigs\3.0.503 + + redis-server.exe --service-install --service-name "redis-6379" "..\Basic\primary-6379-3.0.conf" + + redis-server.exe --service-install --service-name "redis-6380" "..\Basic\replica-6380.conf" + + redis-server.exe --service-install --service-name "redis-6381" "..\Basic\secure-6381.conf" + + redis-server.exe --service-install --service-name "redis-6382" "..\Failover\primary-6382.conf" + + redis-server.exe --service-install --service-name "redis-6383" "..\Failover\replica-6383.conf" + + redis-server.exe --service-install --service-name "redis-7000" "..\Cluster\cluster-7000.conf" --dir "..\Cluster" + + redis-server.exe --service-install --service-name "redis-7001" "..\Cluster\cluster-7001.conf" --dir "..\Cluster" + + redis-server.exe --service-install --service-name "redis-7002" "..\Cluster\cluster-7002.conf" --dir "..\Cluster" + + redis-server.exe --service-install --service-name "redis-7003" "..\Cluster\cluster-7003.conf" --dir "..\Cluster" + + redis-server.exe --service-install --service-name "redis-7004" "..\Cluster\cluster-7004.conf" --dir "..\Cluster" + + redis-server.exe --service-install --service-name "redis-7005" "..\Cluster\cluster-7005.conf" --dir "..\Cluster" + + redis-server.exe --service-install --service-name "redis-7010" "..\Sentinel\redis-7010.conf" + + redis-server.exe --service-install --service-name "redis-7011" "..\Sentinel\redis-7011.conf" + + redis-server.exe --service-install --service-name "redis-26379" "..\Sentinel\sentinel-26379.conf" --sentinel + + redis-server.exe --service-install --service-name "redis-26380" "..\Sentinel\sentinel-26380.conf" --sentinel + + redis-server.exe --service-install --service-name "redis-26381" "..\Sentinel\sentinel-26381.conf" --sentinel + + cd ..\..\.. +- ps: >- + if (Get-Command "Start-Service" -errorAction SilentlyContinue) { + Start-Service redis-* + } + +branches: + only: + - main + +skip_branch_with_pr: true +skip_tags: true +skip_commits: + files: + - '**/*.md' + - docs/* + +environment: + Appveyor: true + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + +nuget: + disable_publish_on_pr: true + +build_script: +- ps: .\build.ps1 -PullRequestNumber "$env:APPVEYOR_PULL_REQUEST_NUMBER" -CreatePackages ($env:OS -eq "Windows_NT") -NetCoreOnlyTests + +test: off +artifacts: +- path: .\.nupkgs\*.nupkg +- path: '**\*.trx' + +deploy: +- provider: NuGet + server: https://www.myget.org/F/stackoverflow/api/v2 + on: + branch: main + api_key: + secure: P/UHxq2DEs0GI1SoDXDesHjRVsSVgdywz5vmsnhFQQY5aJgO3kP+QfhwfhXz19Rw + symbol_server: https://www.myget.org/F/stackoverflow/symbols/api/v2/package \ No newline at end of file diff --git a/build.cmd b/build.cmd new file mode 100644 index 000000000..7d4894cb4 --- /dev/null +++ b/build.cmd @@ -0,0 +1,2 @@ +@ECHO OFF +PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0build.ps1' %*; exit $LASTEXITCODE" \ No newline at end of file diff --git a/build.msbuild b/build.msbuild deleted file mode 100644 index 3f1eeb526..000000000 --- a/build.msbuild +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 000000000..3ace75a06 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,55 @@ +[CmdletBinding(PositionalBinding=$false)] +param( + [bool] $CreatePackages, + [switch] $StartServers, + [bool] $RunTests = $true, + [string] $PullRequestNumber, + [switch] $NetCoreOnlyTests +) + +Write-Host "Run Parameters:" -ForegroundColor Cyan +Write-Host " CreatePackages: $CreatePackages" +Write-Host " RunTests: $RunTests" +Write-Host " dotnet --version:" (dotnet --version) + +$packageOutputFolder = "$PSScriptRoot\.nupkgs" + +if ($PullRequestNumber) { + Write-Host "Building for a pull request (#$PullRequestNumber), skipping packaging." -ForegroundColor Yellow + $CreatePackages = $false +} + +Write-Host "Building all projects (Build.csproj traversal)..." -ForegroundColor "Magenta" +dotnet build ".\Build.csproj" -c Release /p:CI=true +Write-Host "Done building." -ForegroundColor "Green" + +if ($RunTests) { + if ($StartServers) { + Write-Host "Starting all servers for testing: $project (all frameworks)" -ForegroundColor "Magenta" + & .\RedisConfigs\start-all.cmd + Write-Host "Servers Started." -ForegroundColor "Green" + } + Write-Host "Running tests: Build.csproj traversal (all frameworks)" -ForegroundColor "Magenta" + if ($NetCoreOnlyTests) { + dotnet test ".\Build.csproj" -c Release -f net8.0 --no-build --logger trx + } else { + dotnet test ".\Build.csproj" -c Release --no-build --logger trx + } + if ($LastExitCode -ne 0) { + Write-Host "Error with tests, aborting build." -Foreground "Red" + Exit 1 + } + Write-Host "Tests passed!" -ForegroundColor "Green" +} + +if ($CreatePackages) { + New-Item -ItemType Directory -Path $packageOutputFolder -Force | Out-Null + Write-Host "Clearing existing $packageOutputFolder..." -NoNewline + Get-ChildItem $packageOutputFolder | Remove-Item + Write-Host "done." -ForegroundColor "Green" + + Write-Host "Building all packages" -ForegroundColor "Green" + dotnet pack ".\Build.csproj" --no-build -c Release /p:PackageOutputPath=$packageOutputFolder /p:CI=true +} + +Write-Host "Done." \ No newline at end of file diff --git a/build/dotnet-install.ps1 b/build/dotnet-install.ps1 new file mode 100644 index 000000000..89e6e74d8 --- /dev/null +++ b/build/dotnet-install.ps1 @@ -0,0 +1,510 @@ +# +# Copyright (c) .NET Foundation and contributors. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. +# + +<# +.SYNOPSIS + Installs dotnet cli +.DESCRIPTION + Installs dotnet cli. If dotnet installation already exists in the given directory + it will update it only if the requested version differs from the one already installed. +.PARAMETER Channel + Default: LTS + Download from the Channel specified. Possible values: + - Current - most current release + - LTS - most current supported release + - 2-part version in a format A.B - represents a specific release + examples: 2.0; 1.0 + - Branch name + examples: release/2.0.0; Master +.PARAMETER Version + Default: latest + Represents a build version on specific channel. Possible values: + - latest - most latest build on specific channel + - coherent - most latest coherent build on specific channel + coherent applies only to SDK downloads + - 3-part version in a format A.B.C - represents specific version of build + examples: 2.0.0-preview2-006120; 1.1.0 +.PARAMETER InstallDir + Default: %LocalAppData%\Microsoft\dotnet + Path to where to install dotnet. Note that binaries will be placed directly in a given directory. +.PARAMETER Architecture + Default: - this value represents currently running OS architecture + Architecture of dotnet binaries to be installed. + Possible values are: , x64 and x86 +.PARAMETER SharedRuntime + Default: false + Installs just the shared runtime bits, not the entire SDK +.PARAMETER DryRun + If set it will not perform installation but instead display what command line to use to consistently install + currently requested version of dotnet cli. In example if you specify version 'latest' it will display a link + with specific version so that this command can be used deterministicly in a build script. + It also displays binaries location if you prefer to install or download it yourself. +.PARAMETER NoPath + By default this script will set environment variable PATH for the current process to the binaries folder inside installation folder. + If set it will display binaries location but not set any environment variable. +.PARAMETER Verbose + Displays diagnostics information. +.PARAMETER AzureFeed + Default: https://dotnetcli.azureedge.net/dotnet + This parameter typically is not changed by the user. + It allows to change URL for the Azure feed used by this installer. +.PARAMETER UncachedFeed + This parameter typically is not changed by the user. + It allows to change URL for the Uncached feed used by this installer. +.PARAMETER ProxyAddress + If set, the installer will use the proxy when making web requests +.PARAMETER ProxyUseDefaultCredentials + Default: false + Use default credentials, when using proxy address. +.PARAMETER SkipNonVersionedFiles + Default: false + Skips installing non-versioned files if they already exist, such as dotnet.exe. +#> +[cmdletbinding()] +param( + [string]$Channel="LTS", + [string]$Version="Latest", + [string]$InstallDir="", + [string]$Architecture="", + [switch]$SharedRuntime, + [switch]$DryRun, + [switch]$NoPath, + [string]$AzureFeed="https://dotnetcli.azureedge.net/dotnet", + [string]$UncachedFeed="https://dotnetcli.blob.core.windows.net/dotnet", + [string]$ProxyAddress, + [switch]$ProxyUseDefaultCredentials, + [switch]$SkipNonVersionedFiles +) + +Set-StrictMode -Version Latest +$ErrorActionPreference="Stop" +$ProgressPreference="SilentlyContinue" + +$BinFolderRelativePath="" + +# example path with regex: shared/1.0.0-beta-12345/somepath +$VersionRegEx="/\d+\.\d+[^/]+/" +$OverrideNonVersionedFiles = !$SkipNonVersionedFiles + +function Say($str) { + Write-Host "dotnet-install: $str" +} + +function Say-Verbose($str) { + Write-Verbose "dotnet-install: $str" +} + +function Say-Invocation($Invocation) { + $command = $Invocation.MyCommand; + $args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ") + Say-Verbose "$command $args" +} + +function Invoke-With-Retry([ScriptBlock]$ScriptBlock, [int]$MaxAttempts = 3, [int]$SecondsBetweenAttempts = 1) { + $Attempts = 0 + + while ($true) { + try { + return $ScriptBlock.Invoke() + } + catch { + $Attempts++ + if ($Attempts -lt $MaxAttempts) { + Start-Sleep $SecondsBetweenAttempts + } + else { + throw + } + } + } +} + +function Get-Machine-Architecture() { + Say-Invocation $MyInvocation + + # possible values: AMD64, IA64, x86 + return $ENV:PROCESSOR_ARCHITECTURE +} + +# TODO: Architecture and CLIArchitecture should be unified +function Get-CLIArchitecture-From-Architecture([string]$Architecture) { + Say-Invocation $MyInvocation + + switch ($Architecture.ToLower()) { + { $_ -eq "" } { return Get-CLIArchitecture-From-Architecture $(Get-Machine-Architecture) } + { ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" } + { $_ -eq "x86" } { return "x86" } + default { throw "Architecture not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" } + } +} + +function Get-Version-Info-From-Version-Text([string]$VersionText) { + Say-Invocation $MyInvocation + + $Data = @($VersionText.Split([char[]]@(), [StringSplitOptions]::RemoveEmptyEntries)); + + $VersionInfo = @{} + $VersionInfo.CommitHash = $Data[0].Trim() + $VersionInfo.Version = $Data[1].Trim() + return $VersionInfo +} + +function Load-Assembly([string] $Assembly) { + try { + Add-Type -Assembly $Assembly | Out-Null + } + catch { + # On Nano Server, Powershell Core Edition is used. Add-Type is unable to resolve base class assemblies because they are not GAC'd. + # Loading the base class assemblies is not unnecessary as the types will automatically get resolved. + } +} + +function GetHTTPResponse([Uri] $Uri) +{ + Invoke-With-Retry( + { + + $HttpClient = $null + + try { + # HttpClient is used vs Invoke-WebRequest in order to support Nano Server which doesn't support the Invoke-WebRequest cmdlet. + Load-Assembly -Assembly System.Net.Http + + if(-not $ProxyAddress) { + try { + # Despite no proxy being explicitly specified, we may still be behind a default proxy + $DefaultProxy = [System.Net.WebRequest]::DefaultWebProxy; + if($DefaultProxy -and (-not $DefaultProxy.IsBypassed($Uri))) { + $ProxyAddress = $DefaultProxy.GetProxy($Uri).OriginalString + $ProxyUseDefaultCredentials = $true + } + } catch { + # Eat the exception and move forward as the above code is an attempt + # at resolving the DefaultProxy that may not have been a problem. + $ProxyAddress = $null + Say-Verbose("Exception ignored: $_.Exception.Message - moving forward...") + } + } + + if($ProxyAddress) { + $HttpClientHandler = New-Object System.Net.Http.HttpClientHandler + $HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{Address=$ProxyAddress;UseDefaultCredentials=$ProxyUseDefaultCredentials} + $HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler + } + else { + $HttpClient = New-Object System.Net.Http.HttpClient + } + # Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out + # 10 minutes allows it to work over much slower connections. + $HttpClient.Timeout = New-TimeSpan -Minutes 10 + $Response = $HttpClient.GetAsync($Uri).Result + if (($Response -eq $null) -or (-not ($Response.IsSuccessStatusCode))) { + $ErrorMsg = "Failed to download $Uri." + if ($Response -ne $null) { + $ErrorMsg += " $Response" + } + + throw $ErrorMsg + } + + return $Response + } + finally { + if ($HttpClient -ne $null) { + $HttpClient.Dispose() + } + } + }) +} + + +function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Coherent) { + Say-Invocation $MyInvocation + + $VersionFileUrl = $null + if ($SharedRuntime) { + $VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version" + } + else { + if ($Coherent) { + $VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.coherent.version" + } + else { + $VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.version" + } + } + + $Response = GetHTTPResponse -Uri $VersionFileUrl + $StringContent = $Response.Content.ReadAsStringAsync().Result + + switch ($Response.Content.Headers.ContentType) { + { ($_ -eq "application/octet-stream") } { $VersionText = $StringContent } + { ($_ -eq "text/plain") } { $VersionText = $StringContent } + { ($_ -eq "text/plain; charset=UTF-8") } { $VersionText = $StringContent } + default { throw "``$Response.Content.Headers.ContentType`` is an unknown .version file content type." } + } + + $VersionInfo = Get-Version-Info-From-Version-Text $VersionText + + return $VersionInfo +} + + +function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, [string]$Version) { + Say-Invocation $MyInvocation + + switch ($Version.ToLower()) { + { $_ -eq "latest" } { + $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $False + return $LatestVersionInfo.Version + } + { $_ -eq "coherent" } { + $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $True + return $LatestVersionInfo.Version + } + default { return $Version } + } +} + +function Get-Download-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { + Say-Invocation $MyInvocation + + if ($SharedRuntime) { + $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificVersion-win-$CLIArchitecture.zip" + } + else { + $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificVersion-win-$CLIArchitecture.zip" + } + + Say-Verbose "Constructed primary payload URL: $PayloadURL" + + return $PayloadURL +} + +function Get-LegacyDownload-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { + Say-Invocation $MyInvocation + + if ($SharedRuntime) { + $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-win-$CLIArchitecture.$SpecificVersion.zip" + } + else { + $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-dev-win-$CLIArchitecture.$SpecificVersion.zip" + } + + Say-Verbose "Constructed legacy payload URL: $PayloadURL" + + return $PayloadURL +} + +function Get-User-Share-Path() { + Say-Invocation $MyInvocation + + $InstallRoot = $env:DOTNET_INSTALL_DIR + if (!$InstallRoot) { + $InstallRoot = "$env:LocalAppData\Microsoft\dotnet" + } + return $InstallRoot +} + +function Resolve-Installation-Path([string]$InstallDir) { + Say-Invocation $MyInvocation + + if ($InstallDir -eq "") { + return Get-User-Share-Path + } + return $InstallDir +} + +function Get-Version-Info-From-Version-File([string]$InstallRoot, [string]$RelativePathToVersionFile) { + Say-Invocation $MyInvocation + + $VersionFile = Join-Path -Path $InstallRoot -ChildPath $RelativePathToVersionFile + Say-Verbose "Local version file: $VersionFile" + + if (Test-Path $VersionFile) { + $VersionText = cat $VersionFile + Say-Verbose "Local version file text: $VersionText" + return Get-Version-Info-From-Version-Text $VersionText + } + + Say-Verbose "Local version file not found." + + return $null +} + +function Is-Dotnet-Package-Installed([string]$InstallRoot, [string]$RelativePathToPackage, [string]$SpecificVersion) { + Say-Invocation $MyInvocation + + $DotnetPackagePath = Join-Path -Path $InstallRoot -ChildPath $RelativePathToPackage | Join-Path -ChildPath $SpecificVersion + Say-Verbose "Is-Dotnet-Package-Installed: Path to a package: $DotnetPackagePath" + return Test-Path $DotnetPackagePath -PathType Container +} + +function Get-Absolute-Path([string]$RelativeOrAbsolutePath) { + # Too much spam + # Say-Invocation $MyInvocation + + return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($RelativeOrAbsolutePath) +} + +function Get-Path-Prefix-With-Version($path) { + $match = [regex]::match($path, $VersionRegEx) + if ($match.Success) { + return $entry.FullName.Substring(0, $match.Index + $match.Length) + } + + return $null +} + +function Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package([System.IO.Compression.ZipArchive]$Zip, [string]$OutPath) { + Say-Invocation $MyInvocation + + $ret = @() + foreach ($entry in $Zip.Entries) { + $dir = Get-Path-Prefix-With-Version $entry.FullName + if ($dir -ne $null) { + $path = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $dir) + if (-Not (Test-Path $path -PathType Container)) { + $ret += $dir + } + } + } + + $ret = $ret | Sort-Object | Get-Unique + + $values = ($ret | foreach { "$_" }) -join ";" + Say-Verbose "Directories to unpack: $values" + + return $ret +} + +# Example zip content and extraction algorithm: +# Rule: files if extracted are always being extracted to the same relative path locally +# .\ +# a.exe # file does not exist locally, extract +# b.dll # file exists locally, override only if $OverrideFiles set +# aaa\ # same rules as for files +# ... +# abc\1.0.0\ # directory contains version and exists locally +# ... # do not extract content under versioned part +# abc\asd\ # same rules as for files +# ... +# def\ghi\1.0.1\ # directory contains version and does not exist locally +# ... # extract content +function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { + Say-Invocation $MyInvocation + + Load-Assembly -Assembly System.IO.Compression.FileSystem + Set-Variable -Name Zip + try { + $Zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath) + + $DirectoriesToUnpack = Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package -Zip $Zip -OutPath $OutPath + + foreach ($entry in $Zip.Entries) { + $PathWithVersion = Get-Path-Prefix-With-Version $entry.FullName + if (($PathWithVersion -eq $null) -Or ($DirectoriesToUnpack -contains $PathWithVersion)) { + $DestinationPath = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $entry.FullName) + $DestinationDir = Split-Path -Parent $DestinationPath + $OverrideFiles=$OverrideNonVersionedFiles -Or (-Not (Test-Path $DestinationPath)) + if ((-Not $DestinationPath.EndsWith("\")) -And $OverrideFiles) { + New-Item -ItemType Directory -Force -Path $DestinationDir | Out-Null + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $DestinationPath, $OverrideNonVersionedFiles) + } + } + } + } + finally { + if ($Zip -ne $null) { + $Zip.Dispose() + } + } +} + +function DownloadFile([Uri]$Uri, [string]$OutPath) { + $Stream = $null + + try { + $Response = GetHTTPResponse -Uri $Uri + $Stream = $Response.Content.ReadAsStreamAsync().Result + $File = [System.IO.File]::Create($OutPath) + $Stream.CopyTo($File) + $File.Close() + } + finally { + if ($Stream -ne $null) { + $Stream.Dispose() + } + } +} + +function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolderRelativePath) { + $BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath $BinFolderRelativePath) + if (-Not $NoPath) { + Say "Adding to current process PATH: `"$BinPath`". Note: This change will not be visible if PowerShell was run as a child process." + $env:path = "$BinPath;" + $env:path + } + else { + Say "Binaries of dotnet can be found in $BinPath" + } +} + +$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture +$SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version +$DownloadLink = Get-Download-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture +$LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture + +if ($DryRun) { + Say "Payload URLs:" + Say "Primary - $DownloadLink" + Say "Legacy - $LegacyDownloadLink" + Say "Repeatable invocation: .\$($MyInvocation.Line)" + exit 0 +} + +$InstallRoot = Resolve-Installation-Path $InstallDir +Say-Verbose "InstallRoot: $InstallRoot" + +$IsSdkInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage "sdk" -SpecificVersion $SpecificVersion +Say-Verbose ".NET SDK installed? $IsSdkInstalled" +if ($IsSdkInstalled) { + Say ".NET SDK version $SpecificVersion is already installed." + Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath + exit 0 +} + +New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null + +$installDrive = $((Get-Item $InstallRoot).PSDrive.Name); +$free = Get-CimInstance -Class win32_logicaldisk | where Deviceid -eq "${installDrive}:" +if ($free.Freespace / 1MB -le 100 ) { + Say "There is not enough disk space on drive ${installDrive}:" + exit 0 +} + +$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) +Say-Verbose "Zip path: $ZipPath" +Say "Downloading link: $DownloadLink" +try { + DownloadFile -Uri $DownloadLink -OutPath $ZipPath +} +catch { + Say "Cannot download: $DownloadLink" + $DownloadLink = $LegacyDownloadLink + $ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) + Say-Verbose "Legacy zip path: $ZipPath" + Say "Downloading legacy link: $DownloadLink" + DownloadFile -Uri $DownloadLink -OutPath $ZipPath +} + +Say "Extracting zip from $DownloadLink" +Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot + +Remove-Item $ZipPath + +Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath + +Say "Installation finished" +exit 0 diff --git a/build/dotnet-install.sh b/build/dotnet-install.sh new file mode 100644 index 000000000..e409fe06d --- /dev/null +++ b/build/dotnet-install.sh @@ -0,0 +1,863 @@ +#!/usr/bin/env bash +# Copyright (c) .NET Foundation and contributors. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. +# + +# Stop script on NZEC +set -e +# Stop script if unbound variable found (use ${var:-} if intentional) +set -u +# By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success +# This is causing it to fail +set -o pipefail + +# Use in the the functions: eval $invocation +invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' + +# standard output may be used as a return value in the functions +# we need a way to write text on the screen in the functions so that +# it won't interfere with the return value. +# Exposing stream 3 as a pipe to standard output of the script itself +exec 3>&1 + +# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors. +# See if stdout is a terminal +if [ -t 1 ]; then + # see if it supports colors + ncolors=$(tput colors) + if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then + bold="$(tput bold || echo)" + normal="$(tput sgr0 || echo)" + black="$(tput setaf 0 || echo)" + red="$(tput setaf 1 || echo)" + green="$(tput setaf 2 || echo)" + yellow="$(tput setaf 3 || echo)" + blue="$(tput setaf 4 || echo)" + magenta="$(tput setaf 5 || echo)" + cyan="$(tput setaf 6 || echo)" + white="$(tput setaf 7 || echo)" + fi +fi + +say_err() { + printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2 +} + +say() { + # using stream 3 (defined in the beginning) to not interfere with stdout of functions + # which may be used as return value + printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3 +} + +say_verbose() { + if [ "$verbose" = true ]; then + say "$1" + fi +} + +# This platform list is finite - if the SDK/Runtime has supported Linux distribution-specific assets, +# then and only then should the Linux distribution appear in this list. +# Adding a Linux distribution to this list does not imply distribution-specific support. +get_legacy_os_name_from_platform() { + eval $invocation + + platform="$1" + case "$platform" in + "centos.7") + echo "centos" + return 0 + ;; + "debian.8") + echo "debian" + return 0 + ;; + "fedora.23") + echo "fedora.23" + return 0 + ;; + "fedora.24") + echo "fedora.24" + return 0 + ;; + "opensuse.13.2") + echo "opensuse.13.2" + return 0 + ;; + "opensuse.42.1") + echo "opensuse.42.1" + return 0 + ;; + "rhel.7"*) + echo "rhel" + return 0 + ;; + "ubuntu.14.04") + echo "ubuntu" + return 0 + ;; + "ubuntu.16.04") + echo "ubuntu.16.04" + return 0 + ;; + "ubuntu.16.10") + echo "ubuntu.16.10" + return 0 + ;; + "alpine.3.4.3") + echo "alpine" + return 0 + ;; + esac + return 1 +} + +get_linux_platform_name() { + eval $invocation + + if [ -n "$runtime_id" ]; then + echo "${runtime_id%-*}" + return 0 + else + if [ -e /etc/os-release ]; then + . /etc/os-release + echo "$ID.$VERSION_ID" + return 0 + elif [ -e /etc/redhat-release ]; then + local redhatRelease=$( /dev/null 2>&1 + return $? +} + + +check_min_reqs() { + local hasMinimum=false + if machine_has "curl"; then + hasMinimum=true + elif machine_has "wget"; then + hasMinimum=true + fi + + if [ "$hasMinimum" = "false" ]; then + say_err "curl (recommended) or wget are required to download dotnet. Install missing prerequisite to proceed." + return 1 + fi + return 0 +} + +check_pre_reqs() { + eval $invocation + + local failing=false; + + if [ "${DOTNET_INSTALL_SKIP_PREREQS:-}" = "1" ]; then + return 0 + fi + + if [ "$(uname)" = "Linux" ]; then + if [ ! -x "$(command -v ldconfig)" ]; then + echo "ldconfig is not in PATH, trying /sbin/ldconfig." + LDCONFIG_COMMAND="/sbin/ldconfig" + else + LDCONFIG_COMMAND="ldconfig" + fi + + local librarypath=${LD_LIBRARY_PATH:-} + LDCONFIG_COMMAND="$LDCONFIG_COMMAND -NXv ${librarypath//:/ }" + + [ -z "$($LDCONFIG_COMMAND | grep libunwind)" ] && say_err "Unable to locate libunwind. Install libunwind to continue" && failing=true + [ -z "$($LDCONFIG_COMMAND | grep libssl)" ] && say_err "Unable to locate libssl. Install libssl to continue" && failing=true + [ -z "$($LDCONFIG_COMMAND | grep libicu)" ] && say_err "Unable to locate libicu. Install libicu to continue" && failing=true + [ -z "$($LDCONFIG_COMMAND | grep -F libcurl.so)" ] && say_err "Unable to locate libcurl. Install libcurl to continue" && failing=true + fi + + if [ "$failing" = true ]; then + return 1 + fi + + return 0 +} + +# args: +# input - $1 +to_lowercase() { + #eval $invocation + + echo "$1" | tr '[:upper:]' '[:lower:]' + return 0 +} + +# args: +# input - $1 +remove_trailing_slash() { + #eval $invocation + + local input="${1:-}" + echo "${input%/}" + return 0 +} + +# args: +# input - $1 +remove_beginning_slash() { + #eval $invocation + + local input="${1:-}" + echo "${input#/}" + return 0 +} + +# args: +# root_path - $1 +# child_path - $2 - this parameter can be empty +combine_paths() { + eval $invocation + + # TODO: Consider making it work with any number of paths. For now: + if [ ! -z "${3:-}" ]; then + say_err "combine_paths: Function takes two parameters." + return 1 + fi + + local root_path="$(remove_trailing_slash "$1")" + local child_path="$(remove_beginning_slash "${2:-}")" + say_verbose "combine_paths: root_path=$root_path" + say_verbose "combine_paths: child_path=$child_path" + echo "$root_path/$child_path" + return 0 +} + +get_machine_architecture() { + eval $invocation + + # Currently the only one supported + echo "x64" + return 0 +} + +# args: +# architecture - $1 +get_normalized_architecture_from_architecture() { + eval $invocation + + local architecture="$(to_lowercase "$1")" + case "$architecture" in + \) + echo "$(get_normalized_architecture_from_architecture "$(get_machine_architecture)")" + return 0 + ;; + amd64|x64) + echo "x64" + return 0 + ;; + x86) + say_err "Architecture \`x86\` currently not supported" + return 1 + ;; + esac + + say_err "Architecture \`$architecture\` not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" + return 1 +} + +# version_info is a conceptual two line string representing commit hash and 4-part version +# format: +# Line 1: # commit_hash +# Line 2: # 4-part version + +# args: +# version_text - stdin +get_version_from_version_info() { + eval $invocation + + cat | tail -n 1 | sed 's/\r$//' + return 0 +} + +# args: +# version_text - stdin +get_commit_hash_from_version_info() { + eval $invocation + + cat | head -n 1 | sed 's/\r$//' + return 0 +} + +# args: +# install_root - $1 +# relative_path_to_package - $2 +# specific_version - $3 +is_dotnet_package_installed() { + eval $invocation + + local install_root="$1" + local relative_path_to_package="$2" + local specific_version="${3//[$'\t\r\n']}" + + local dotnet_package_path="$(combine_paths "$(combine_paths "$install_root" "$relative_path_to_package")" "$specific_version")" + say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path" + + if [ -d "$dotnet_package_path" ]; then + return 0 + else + return 1 + fi +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# coherent - $4 +get_latest_version_info() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local coherent="$4" + + local version_file_url=null + if [ "$shared_runtime" = true ]; then + version_file_url="$uncached_feed/Runtime/$channel/latest.version" + else + if [ "$coherent" = true ]; then + version_file_url="$uncached_feed/Sdk/$channel/latest.coherent.version" + else + version_file_url="$uncached_feed/Sdk/$channel/latest.version" + fi + fi + say_verbose "get_latest_version_info: latest url: $version_file_url" + + download "$version_file_url" + return $? +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# version - $4 +get_specific_version_from_version() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local version="$(to_lowercase "$4")" + + case "$version" in + latest) + local version_info + version_info="$(get_latest_version_info "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1 + say_verbose "get_specific_version_from_version: version_info=$version_info" + echo "$version_info" | get_version_from_version_info + return 0 + ;; + coherent) + local version_info + version_info="$(get_latest_version_info "$azure_feed" "$channel" "$normalized_architecture" true)" || return 1 + say_verbose "get_specific_version_from_version: version_info=$version_info" + echo "$version_info" | get_version_from_version_info + return 0 + ;; + *) + echo "$version" + return 0 + ;; + esac +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# specific_version - $4 +construct_download_link() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local specific_version="${4//[$'\t\r\n']}" + + local osname + osname="$(get_current_os_name)" || return 1 + + local download_link=null + if [ "$shared_runtime" = true ]; then + download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_version-$osname-$normalized_architecture.tar.gz" + else + download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_version-$osname-$normalized_architecture.tar.gz" + fi + + echo "$download_link" + return 0 +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# specific_version - $4 +construct_legacy_download_link() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local specific_version="${4//[$'\t\r\n']}" + + local distro_specific_osname + distro_specific_osname="$(get_legacy_os_name)" || return 1 + + local legacy_download_link=null + if [ "$shared_runtime" = true ]; then + legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" + else + legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" + fi + + echo "$legacy_download_link" + return 0 +} + +get_user_install_path() { + eval $invocation + + if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then + echo "$DOTNET_INSTALL_DIR" + else + echo "$HOME/.dotnet" + fi + return 0 +} + +# args: +# install_dir - $1 +resolve_installation_path() { + eval $invocation + + local install_dir=$1 + if [ "$install_dir" = "" ]; then + local user_install_path="$(get_user_install_path)" + say_verbose "resolve_installation_path: user_install_path=$user_install_path" + echo "$user_install_path" + return 0 + fi + + echo "$install_dir" + return 0 +} + +# args: +# install_root - $1 +get_installed_version_info() { + eval $invocation + + local install_root="$1" + local version_file="$(combine_paths "$install_root" "$local_version_file_relative_path")" + say_verbose "Local version file: $version_file" + if [ ! -z "$version_file" ] | [ -r "$version_file" ]; then + local version_info="$(cat "$version_file")" + echo "$version_info" + return 0 + fi + + say_verbose "Local version file not found." + return 0 +} + +# args: +# relative_or_absolute_path - $1 +get_absolute_path() { + eval $invocation + + local relative_or_absolute_path=$1 + echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")" + return 0 +} + +# args: +# input_files - stdin +# root_path - $1 +# out_path - $2 +# override - $3 +copy_files_or_dirs_from_list() { + eval $invocation + + local root_path="$(remove_trailing_slash "$1")" + local out_path="$(remove_trailing_slash "$2")" + local override="$3" + local override_switch=$(if [ "$override" = false ]; then printf -- "-n"; fi) + + cat | uniq | while read -r file_path; do + local path="$(remove_beginning_slash "${file_path#$root_path}")" + local target="$out_path/$path" + if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then + mkdir -p "$out_path/$(dirname "$path")" + cp -R $override_switch "$root_path/$path" "$target" + fi + done +} + +# args: +# zip_path - $1 +# out_path - $2 +extract_dotnet_package() { + eval $invocation + + local zip_path="$1" + local out_path="$2" + + local temp_out_path="$(mktemp -d "$temporary_file_template")" + + local failed=false + tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true + + local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' + find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false + find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files" + + rm -rf "$temp_out_path" + + if [ "$failed" = true ]; then + say_err "Extraction failed" + return 1 + fi +} + +# args: +# remote_path - $1 +# [out_path] - $2 - stdout if not provided +download() { + eval $invocation + + local remote_path="$1" + local out_path="${2:-}" + + local failed=false + if machine_has "curl"; then + downloadcurl "$remote_path" "$out_path" || failed=true + elif machine_has "wget"; then + downloadwget "$remote_path" "$out_path" || failed=true + else + failed=true + fi + if [ "$failed" = true ]; then + say_verbose "Download failed: $remote_path" + return 1 + fi + return 0 +} + +downloadcurl() { + eval $invocation + local remote_path="$1" + local out_path="${2:-}" + + local failed=false + if [ -z "$out_path" ]; then + curl --retry 10 -sSL -f --create-dirs "$remote_path" || failed=true + else + curl --retry 10 -sSL -f --create-dirs -o "$out_path" "$remote_path" || failed=true + fi + if [ "$failed" = true ]; then + say_verbose "Curl download failed" + return 1 + fi + return 0 +} + +downloadwget() { + eval $invocation + local remote_path="$1" + local out_path="${2:-}" + + local failed=false + if [ -z "$out_path" ]; then + wget -q --tries 10 -O - "$remote_path" || failed=true + else + wget -v --tries 10 -O "$out_path" "$remote_path" || failed=true + fi + if [ "$failed" = true ]; then + say_verbose "Wget download failed" + return 1 + fi + return 0 +} + +calculate_vars() { + eval $invocation + valid_legacy_download_link=true + + normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")" + say_verbose "normalized_architecture=$normalized_architecture" + + specific_version="$(get_specific_version_from_version "$azure_feed" "$channel" "$normalized_architecture" "$version")" + say_verbose "specific_version=$specific_version" + if [ -z "$specific_version" ]; then + say_err "Could not get version information." + return 1 + fi + + download_link="$(construct_download_link "$azure_feed" "$channel" "$normalized_architecture" "$specific_version")" + say_verbose "download_link=$download_link" + + legacy_download_link="$(construct_legacy_download_link "$azure_feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false + + if [ "$valid_legacy_download_link" = true ]; then + say_verbose "legacy_download_link=$legacy_download_link" + else + say_verbose "Cound not construct a legacy_download_link; omitting..." + fi + + install_root="$(resolve_installation_path "$install_dir")" + say_verbose "install_root=$install_root" +} + +install_dotnet() { + eval $invocation + local download_failed=false + + if is_dotnet_package_installed "$install_root" "sdk" "$specific_version"; then + say ".NET SDK version $specific_version is already installed." + return 0 + fi + + mkdir -p "$install_root" + zip_path="$(mktemp "$temporary_file_template")" + say_verbose "Zip path: $zip_path" + + say "Downloading link: $download_link" + + # Failures are normal in the non-legacy case for ultimately legacy downloads. + # Do not output to stderr, since output to stderr is considered an error. + download "$download_link" "$zip_path" 2>&1 || download_failed=true + + # if the download fails, download the legacy_download_link + if [ "$download_failed" = true ] && [ "$valid_legacy_download_link" = true ]; then + say "Cannot download: $download_link" + download_link="$legacy_download_link" + zip_path="$(mktemp "$temporary_file_template")" + say_verbose "Legacy zip path: $zip_path" + say "Downloading legacy link: $download_link" + download "$download_link" "$zip_path" + fi + + say "Extracting zip from $download_link" + extract_dotnet_package "$zip_path" "$install_root" + + return 0 +} + +local_version_file_relative_path="/.version" +bin_folder_relative_path="" +temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX" + +channel="LTS" +version="Latest" +install_dir="" +architecture="" +dry_run=false +no_path=false +azure_feed="https://dotnetcli.azureedge.net/dotnet" +uncached_feed="https://dotnetcli.blob.core.windows.net/dotnet" +verbose=false +shared_runtime=false +runtime_id="" +override_non_versioned_files=true + +while [ $# -ne 0 ] +do + name="$1" + case "$name" in + -c|--channel|-[Cc]hannel) + shift + channel="$1" + ;; + -v|--version|-[Vv]ersion) + shift + version="$1" + ;; + -i|--install-dir|-[Ii]nstall[Dd]ir) + shift + install_dir="$1" + ;; + --arch|--architecture|-[Aa]rch|-[Aa]rchitecture) + shift + architecture="$1" + ;; + --shared-runtime|-[Ss]hared[Rr]untime) + shared_runtime=true + ;; + --dry-run|-[Dd]ry[Rr]un) + dry_run=true + ;; + --no-path|-[Nn]o[Pp]ath) + no_path=true + ;; + --verbose|-[Vv]erbose) + verbose=true + ;; + --azure-feed|-[Aa]zure[Ff]eed) + shift + azure_feed="$1" + ;; + --uncached-feed|-[Uu]ncached[Ff]eed) + shift + uncached_feed="$1" + ;; + --runtime-id|-[Rr]untime[Ii]d) + shift + runtime_id="$1" + ;; + --skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles) + shift + override_non_versioned_files=false + ;; + -?|--?|-h|--help|-[Hh]elp) + script_name="$(basename "$0")" + echo ".NET Tools Installer" + echo "Usage: $script_name [-c|--channel ] [-v|--version ] [-p|--prefix ]" + echo " $script_name -h|-?|--help" + echo "" + echo "$script_name is a simple command line interface for obtaining dotnet cli." + echo "" + echo "Options:" + echo " -c,--channel Download from the CHANNEL specified, Defaults to \`$channel\`." + echo " -Channel" + echo " Possible values:" + echo " - Current - most current release" + echo " - LTS - most current supported release" + echo " - 2-part version in a format A.B - represents a specific release" + echo " examples: 2.0; 1.0" + echo " - Branch name" + echo " examples: release/2.0.0; Master" + echo " -v,--version Use specific VERSION, Defaults to \`$version\`." + echo " -Version" + echo " Possible values:" + echo " - latest - most latest build on specific channel" + echo " - coherent - most latest coherent build on specific channel" + echo " coherent applies only to SDK downloads" + echo " - 3-part version in a format A.B.C - represents specific version of build" + echo " examples: 2.0.0-preview2-006120; 1.1.0" + echo " -i,--install-dir Install under specified location (see Install Location below)" + echo " -InstallDir" + echo " --architecture Architecture of .NET Tools. Currently only x64 is supported." + echo " --arch,-Architecture,-Arch" + echo " --shared-runtime Installs just the shared runtime bits, not the entire SDK." + echo " -SharedRuntime" + echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable." + echo " -SkipNonVersionedFiles" + echo " --dry-run,-DryRun Do not perform installation. Display download link." + echo " --no-path, -NoPath Do not set PATH for the current process." + echo " --verbose,-Verbose Display diagnostics information." + echo " --azure-feed,-AzureFeed Azure feed location. Defaults to $azure_feed, This parameter typically is not changed by the user." + echo " --uncached-feed,-UncachedFeed Uncached feed location. This parameter typically is not changed by the user." + echo " --runtime-id Installs the .NET Tools for the given platform (use linux-x64 for portable linux)." + echo " -RuntimeId" + echo " -?,--?,-h,--help,-Help Shows this help message" + echo "" + echo "Install Location:" + echo " Location is chosen in following order:" + echo " - --install-dir option" + echo " - Environmental variable DOTNET_INSTALL_DIR" + echo " - $HOME/.dotnet" + exit 0 + ;; + *) + say_err "Unknown argument \`$name\`" + exit 1 + ;; + esac + + shift +done + +check_min_reqs +calculate_vars + +if [ "$dry_run" = true ]; then + say "Payload URL: $download_link" + if [ "$valid_legacy_download_link" = true ]; then + say "Legacy payload URL: $legacy_download_link" + fi + say "Repeatable invocation: ./$(basename "$0") --version $specific_version --channel $channel --install-dir $install_dir" + exit 0 +fi + +check_pre_reqs +install_dotnet + +bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")" +if [ "$no_path" = false ]; then + say "Adding to current process PATH: \`$bin_path\`. Note: This change will be visible only when sourcing script." + export PATH="$bin_path":"$PATH" +else + say "Binaries of dotnet can be found in $bin_path" +fi + +say "Installation finished successfully." diff --git a/docs/AsyncTimeouts.md b/docs/AsyncTimeouts.md new file mode 100644 index 000000000..04892d59a --- /dev/null +++ b/docs/AsyncTimeouts.md @@ -0,0 +1,79 @@ +# Async timeouts and cancellation + +StackExchange.Redis directly supports timeout of *synchronous* operations, but for *asynchronous* operations, it is recommended +to use the inbuilt framework support for cancellation and timeouts, i.e. the [WaitAsync](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.waitasync) +family of methods. This allows the caller to control timeout (via `TimeSpan`), cancellation (via `CancellationToken`), or both. + +Note that it is possible that operations will still be buffered and may still be issued to the server *after* timeout/cancellation means +that the caller isn't observing the result. + +## Usage + +### Timeout + +Timeouts are probably the most common cancellation scenario: + +```csharp +var timeout = TimeSpan.FromSeconds(5); +await database.StringSetAsync("key", "value").WaitAsync(timeout); +var value = await database.StringGetAsync("key").WaitAsync(timeout); +``` + +### Cancellation + +You can also use `CancellationToken` to drive cancellation, identically: + +```csharp +CancellationToken token = ...; // for example, from HttpContext.RequestAborted +await database.StringSetAsync("key", "value").WaitAsync(token); +var value = await database.StringGetAsync("key").WaitAsync(token); +``` +### Combined Cancellation and Timeout + +These two concepts can be combined so that if either cancellation or timeout occur, the caller's +operation is cancelled: + +```csharp +var timeout = TimeSpan.FromSeconds(5); +CancellationToken token = ...; // for example, from HttpContext.RequestAborted +await database.StringSetAsync("key", "value").WaitAsync(timeout, token); +var value = await database.StringGetAsync("key").WaitAsync(timeout, token); +``` + +### Creating a timeout for multiple operations + +If you want a timeout to apply to a *group* of operations rather than individually, then you +can using `CancellationTokenSource` to create a `CancellationToken` that is cancelled after a +specified timeout. For example: + +```csharp +var timeout = TimeSpan.FromSeconds(5); +using var cts = new CancellationTokenSource(timeout); +await database.StringSetAsync("key", "value").WaitAsync(cts.Token); +var value = await database.StringGetAsync("key").WaitAsync(cts.Token); +``` + +This can additionally be combined with one-or-more cancellation tokens: + +```csharp +var timeout = TimeSpan.FromSeconds(5); +CancellationToken token = ...; // for example, from HttpContext.RequestAborted +using var cts = CancellationTokenSource.CreateLinkedTokenSource(token); // or multiple tokens +cts.CancelAfter(timeout); +await database.StringSetAsync("key", "value").WaitAsync(cts.Token); +var value = await database.StringGetAsync("key").WaitAsync(cts.Token); +``` + +### Cancelling keys enumeration + +Keys being enumerated (via `SCAN`) can *also* be cancelled, using the inbuilt `.WithCancellation(...)` method: + +```csharp +CancellationToken token = ...; // for example, from HttpContext.RequestAborted +await foreach (var key in server.KeysAsync(pattern: "*foo*").WithCancellation(token)) +{ + ... +} +``` + +To use a timeout instead, you can use the `CancellationTokenSource` approach shown above. \ No newline at end of file diff --git a/docs/Basics.md b/docs/Basics.md index 4ffe7d11f..4d843cb3a 100644 --- a/docs/Basics.md +++ b/docs/Basics.md @@ -3,7 +3,7 @@ The central object in StackExchange.Redis is the `ConnectionMultiplexer` class in the `StackExchange.Redis` namespace; this is the object that hides away the details of multiple servers. Because the `ConnectionMultiplexer` does a lot, it is designed to be **shared and reused** between callers. You should not create a `ConnectionMultiplexer` per operation. It is fully thread-safe and ready for this usage. In all the subsequent examples it will be assumed that you have a `ConnectionMultiplexer` instance stored away for re-use. But for now, let's create one. This is done using `ConnectionMultiplexer.Connect` or `ConnectionMultiplexer.ConnectAsync`, passing in either a configuration string or a `ConfigurationOptions` object. The configuration string can take the form of a comma-delimited series of nodes, so let's just connect to an instance on the local machine on the default port (6379): -```C# +```csharp using StackExchange.Redis; ... ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost"); @@ -12,18 +12,18 @@ ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost"); Note that `ConnectionMultiplexer` implements `IDisposable` and can be disposed when no longer required. This is deliberately not showing `using` statement usage, because it is exceptionally rare that you would want to use a `ConnectionMultiplexer` briefly, as the idea is to re-use this object. -A more complicated scenario might involve a master/slave setup; for this usage, simply specify all the desired nodes that make up that logical redis tier (it will automatically identify the master): +A more complicated scenario might involve a primary/replica setup; for this usage, simply specify all the desired nodes that make up that logical redis tier (it will automatically identify the primary): -```C# +```csharp ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("server1:6379,server2:6379"); ``` -If it finds both nodes are masters, a tie-breaker key can optionally be specified that can be used to resolve the issue, however such a condition is fortunately very rare. +If it finds both nodes are primaries, a tie-breaker key can optionally be specified that can be used to resolve the issue, however such a condition is fortunately very rare. Once you have a `ConnectionMultiplexer`, there are 3 main things you might want to do: - access a redis database (note that in the case of a cluster, a single logical database may be spread over multiple nodes) -- make use of the [pub/sub](http://redis.io/topics/pubsub) features of redis +- make use of the [pub/sub](https://redis.io/topics/pubsub) features of redis - access an individual server for maintenance / monitoring purposes Using a redis database @@ -31,23 +31,23 @@ Using a redis database Accessing a redis database is as simple as: -```C# +```csharp IDatabase db = redis.GetDatabase(); ``` The object returned from `GetDatabase` is a cheap pass-thru object, and does not need to be stored. Note that redis supports multiple databases (although this is not supported on "cluster"); this can be optionally specified in the call to `GetDatabase`. Additionally, if you plan to make use of the asynchronous API and you require the [`Task.AsyncState`][2] to have a value, this can also be specified: -```C# +```csharp int databaseNumber = ... object asyncState = ... IDatabase db = redis.GetDatabase(databaseNumber, asyncState); ``` -Once you have the `IDatabase`, it is simply a case of using the [redis API](http://redis.io/commands). Note that all methods have both synchronous and asynchronous implementations. In line with Microsoft's naming guidance, the asynchronous methods all end `...Async(...)`, and are fully `await`-able etc. +Once you have the `IDatabase`, it is simply a case of using the [redis API](https://redis.io/commands). Note that all methods have both synchronous and asynchronous implementations. In line with Microsoft's naming guidance, the asynchronous methods all end `...Async(...)`, and are fully `await`-able etc. The simplest operation would be to store and retrieve a value: -```C# +```csharp string value = "abcdefg"; db.StringSet("mykey", value); ... @@ -55,37 +55,53 @@ string value = db.StringGet("mykey"); Console.WriteLine(value); // writes: "abcdefg" ``` -Note that the `String...` prefix here denotes the [String redis type](http://redis.io/topics/data-types), and is largely separate to the [.NET String type][3], although both can store text data. However, redis allows raw binary data for both keys and values - the usage is identical: +Note that the `String...` prefix here denotes the [String redis type](https://redis.io/topics/data-types), and is largely separate to the [.NET String type][3], although both can store text data. However, redis allows raw binary data for both keys and values - the usage is identical: -```C# +```csharp byte[] key = ..., value = ...; db.StringSet(key, value); ... byte[] value = db.StringGet(key); ``` -The entire range of [redis database commands](http://redis.io/commands) covering all redis data types is available for use. +The entire range of [redis database commands](https://redis.io/commands) covering all redis data types is available for use. Using redis pub/sub ---- -Another common use of redis is as a [pub/sub message](http://redis.io/topics/pubsub) distribution tool; this is also simple, and in the event of connection failure, the `ConnectionMultiplexer` will handle all the details of re-subscribing to the requested channels. +Another common use of redis is as a [pub/sub message](https://redis.io/topics/pubsub) distribution tool; this is also simple, and in the event of connection failure, the `ConnectionMultiplexer` will handle all the details of re-subscribing to the requested channels. -```C# +```csharp ISubscriber sub = redis.GetSubscriber(); ``` -Again, the object returned from `GetSubscriber` is a cheap pass-thru object that does not need to be stored. The pub/sub API has no concept of databases, but as before we can optionally provide an async-state. Note that all subscriptions are global: they are not scoped to the lifetime of the `ISubscriber` instance. The pub/sub features in redis use named "channels"; channels do not need to be defined in advance on the server (an interesting use here is things like per-user notification channels, which is what drives parts of the realtime updates on [Stack Overflow](http://stackoverflow.com)). As is common in .NET, subscriptions take the form of callback delegates which accept the channel-name and the message: +Again, the object returned from `GetSubscriber` is a cheap pass-thru object that does not need to be stored. The pub/sub API has no concept of databases, but as before we can optionally provide an async-state. Note that all subscriptions are global: they are not scoped to the lifetime of the `ISubscriber` instance. The pub/sub features in redis use named "channels"; channels do not need to be defined in advance on the server (an interesting use here is things like per-user notification channels, which is what drives parts of the realtime updates on [Stack Overflow](https://stackoverflow.com)). As is common in .NET, subscriptions take the form of callback delegates which accept the channel-name and the message: -```C# +```csharp sub.Subscribe("messages", (channel, message) => { Console.WriteLine((string)message); }); ``` +Note: exceptions are caught and discarded by StackExchange.Redis here, to prevent cascading failures. To handle failures, use a `try`/`catch` inside your handler to do as you wish with any exceptions. + +In v2, you can subscribe without providing a callback directly to the `Subscribe()` method, and instead using the returned `ChannelMessageQueue`, which represents a message queue of ordered pub/sub notifications. This allows the usage of the `ChannelMessageQueue.OnMessage()` method, which provides overloads for both synchronous (`Action`) and asynchronous (`Func`) handlers to execute when receiving a message. + +```csharp +// Synchronous handler +sub.Subscribe("messages").OnMessage(channelMessage => { + Console.WriteLine((string) channelMessage.Message); +}); + +// Asynchronous handler +sub.Subscribe("messages").OnMessage(async channelMessage => { + await Task.Delay(1000); + Console.WriteLine((string) channelMessage.Message); +}); +``` Separately (and often in a separate process on a separate machine) you can publish to this channel: -```C# +```csharp sub.Publish("messages", "hello"); ``` @@ -98,19 +114,19 @@ Accessing individual servers For maintenance purposes, it is sometimes necessary to issue server-specific commands: -```C# +```csharp IServer server = redis.GetServer("localhost", 6379); ``` -The `GetServer` method will accept an [`EndPoint`](http://msdn.microsoft.com/en-us/library/system.net.endpoint(v=vs.110).aspx) or the name/value pair that uniquely identify the server. As before, the object returned from `GetServer` is a cheap pass-thru object that does not need to be stored, and async-state can be optionally specified. Note that the set of available endpoints is also available: +The `GetServer` method will accept an [`EndPoint`](https://docs.microsoft.com/en-us/dotnet/api/system.net.endpoint) or the name/value pair that uniquely identify the server. As before, the object returned from `GetServer` is a cheap pass-thru object that does not need to be stored, and async-state can be optionally specified. Note that the set of available endpoints is also available: -```C# +```csharp EndPoint[] endpoints = redis.GetEndPoints(); ``` -From the `IServer` instance, the [Server commands](http://redis.io/commands#server) are available; for example: +From the `IServer` instance, the [Server commands](https://redis.io/commands#server) are available; for example: -```C# +```csharp DateTime lastSave = server.LastSave(); ClientInfo[] clients = server.ClientList(); ``` @@ -122,16 +138,16 @@ There are 3 primary usage mechanisms with StackExchange.Redis: - Synchronous - where the operation completes before the methods returns to the caller (note that while this may block the caller, it absolutely **does not** block other threads: the key idea in StackExchange.Redis is that it aggressively shares the connection between concurrent callers) - Asynchronous - where the operation completes some time in the future, and a `Task` or `Task` is returned immediately, which can later: - - be `.Wait()`ed (blocking the current thread until the response is available) - - have a continuation callback added ([`ContinueWith`](http://msdn.microsoft.com/en-us/library/system.threading.tasks.task.continuewith(v=vs.110).aspx) in the TPL) - - be *awaited* (which is a language-level feature that simplifies the latter, while also continuing immediately if the reply is already known) + - be `.Wait()`ed (blocking the current thread until the response is available) + - have a continuation callback added ([`ContinueWith`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.continuewith) in the TPL) + - be *awaited* (which is a language-level feature that simplifies the latter, while also continuing immediately if the reply is already known) - Fire-and-Forget - where you really aren't interested in the reply, and are happy to continue irrespective of the response The synchronous usage is already shown in the examples above. This is the simplest usage, and does not involve the [TPL][1]. For asynchronous usage, the key difference is the `Async` suffix on methods, and (typically) the use of the `await` language feature. For example: -```C# +```csharp string value = "abcdefg"; await db.StringSetAsync("mykey", value); ... @@ -141,13 +157,10 @@ Console.WriteLine(value); // writes: "abcdefg" The fire-and-forget usage is accessed by the optional `CommandFlags flags` parameter on all methods (defaults to none). In this usage, the method returns the default value immediately (so a method that normally returns a `String` will always return `null`, and a method that normally returns an `Int64` will always return `0`). The operation will continue in the background. A typical use-case of this might be to increment page-view counts: -```C# +```csharp db.StringIncrement(pageKey, flags: CommandFlags.FireAndForget); ``` - - - - [1]: http://msdn.microsoft.com/en-us/library/dd460717%28v=vs.110%29.aspx - [2]: http://msdn.microsoft.com/en-us/library/system.threading.tasks.task.asyncstate(v=vs.110).aspx - [3]: http://msdn.microsoft.com/en-us/library/system.string(v=vs.110).aspx + [1]: https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl + [2]: https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.asyncstate + [3]: https://docs.microsoft.com/en-us/dotnet/api/system.string diff --git a/docs/Configuration.md b/docs/Configuration.md index 5c9c0fd7a..96e4b5bae 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1,9 +1,10 @@ -Configuration +# Configuration === +When connecting to Redis version 6 or above with an ACL configured, your ACL user needs to at least have permissions to run the ECHO command. We run this command to verify that we have a valid connection to the Redis service. Because there are lots of different ways to configure redis, StackExchange.Redis offers a rich configuration model, which is invoked when calling `Connect` (or `ConnectAsync`): -```C# +```csharp var conn = ConnectionMultiplexer.Connect(configuration); ``` @@ -14,36 +15,44 @@ The `configuration` here can be either: The latter is *basically* a tokenized form of the former. -Basic Configuration Strings +## Basic Configuration Strings - The *simplest* configuration example is just the host name: -```C# +```csharp var conn = ConnectionMultiplexer.Connect("localhost"); ``` This will connect to a single server on the local machine using the default redis port (6379). Additional options are simply appended (comma-delimited). Ports are represented with a colon (`:`) as is usual. Configuration *options* include an `=` after the name. For example: -```C# +```csharp var conn = ConnectionMultiplexer.Connect("redis0:6380,redis1:6380,allowAdmin=true"); ``` +If you specify a serviceName in the connection string, it will trigger sentinel mode. This example will connect to a sentinel server on the local machine +using the default sentinel port (26379), discover the current primary server for the `myprimary` service and return a managed connection +pointing to that primary server that will automatically be updated if the primary changes: + +```csharp +var conn = ConnectionMultiplexer.Connect("localhost,serviceName=myprimary"); +``` + An overview of mapping between the `string` and `ConfigurationOptions` representation is shown below, but you can switch between them trivially: -```C# +```csharp ConfigurationOptions options = ConfigurationOptions.Parse(configString); ``` or: -```C# +```csharp string configString = options.ToString(); ``` A common usage is to store the *basic* details in a string, and then apply specific details at runtime: -```C# +```csharp string configString = GetRedisConfiguration(); var options = ConfigurationOptions.Parse(configString); options.ClientName = GetAppName(); // only known at runtime @@ -53,11 +62,11 @@ conn = ConnectionMultiplexer.Connect(options); Microsoft Azure Redis example with password -```C# +```csharp var conn = ConnectionMultiplexer.Connect("contoso5.redis.cache.windows.net,ssl=true,password=..."); ``` -Configuration Options +## Configuration Options --- The `ConfigurationOptions` object has a wide range of properties, all of which are fully documented in intellisense. Some of the more common options to use include: @@ -67,36 +76,77 @@ The `ConfigurationOptions` object has a wide range of properties, all of which a | abortConnect={bool} | `AbortOnConnectFail` | `true` (`false` on Azure) | If true, `Connect` will not create a connection while no servers are available | | allowAdmin={bool} | `AllowAdmin` | `false` | Enables a range of commands that are considered risky | | channelPrefix={string} | `ChannelPrefix` | `null` | Optional channel prefix for all pub/sub operations | +| checkCertificateRevocation={bool} | `CheckCertificateRevocation` | `true` | A Boolean value that specifies whether the certificate revocation list is checked during authentication. | | connectRetry={int} | `ConnectRetry` | `3` | The number of times to repeat connect attempts during initial `Connect` | | connectTimeout={int} | `ConnectTimeout` | `5000` | Timeout (ms) for connect operations | | configChannel={string} | `ConfigurationChannel` | `__Booksleeve_MasterChanged` | Broadcast channel name for communicating configuration changes | +| configCheckSeconds={int} | `ConfigCheckSeconds` | `60` | Time (seconds) to check configuration. This serves as a keep-alive for interactive sockets, if it is supported. | | defaultDatabase={int} | `DefaultDatabase` | `null` | Default database index, from `0` to `databases - 1` | -| keepAlive={int} | `KeepAlive` | `-1` | Time (seconds) at which to send a message to help keep sockets alive | +| keepAlive={int} | `KeepAlive` | `-1` | Time (seconds) at which to send a message to help keep sockets alive (60 sec default) | | name={string} | `ClientName` | `null` | Identification for the connection within redis | | password={string} | `Password` | `null` | Password for the redis server | -| proxy={proxy type} | `Proxy` | `Proxy.None` | Type of proxy in use (if any); for example "twemproxy" | +| user={string} | `User` | `null` | User for the redis server (for use with ACLs on redis 6 and above) | +| proxy={proxy type} | `Proxy` | `Proxy.None` | Type of proxy in use (if any); for example "twemproxy/envoyproxy" | | resolveDns={bool} | `ResolveDns` | `false` | Specifies that DNS resolution should be explicit and eager, rather than implicit | -| serviceName={string} | `ServiceName` | `null` | Not currently implemented (intended for use with sentinel) | +| serviceName={string} | `ServiceName` | `null` | Used for connecting to a sentinel primary service | | ssl={bool} | `Ssl` | `false` | Specifies that SSL encryption should be used | | sslHost={string} | `SslHost` | `null` | Enforces a particular SSL host identity on the server's certificate | -| sslProtocols={enum} | `SslProtocols` | `null` | Ssl/Tls versions supported when using an encrypted connection. Use '\|' to provide multiple values. | -| syncTimeout={int} | `SyncTimeout` | `1000` | Time (ms) to allow for synchronous operations | -| tiebreaker={string} | `TieBreaker` | `__Booksleeve_TieBreak` | Key to use for selecting a server in an ambiguous master scenario | -| version={string} | `DefaultVersion` | (`3.0` in Azure, else `2.0`) | Redis version level (useful when the server does not make this available) | -| writeBuffer={int} | `WriteBuffer` | `4096` | Size of the output buffer | +| sslProtocols={enum} | `SslProtocols` | `null` | Ssl/Tls versions supported when using an encrypted connection. Use '\|' to provide multiple values. | +| syncTimeout={int} | `SyncTimeout` | `5000` | Time (ms) to allow for synchronous operations | +| asyncTimeout={int} | `AsyncTimeout` | `SyncTimeout` | Time (ms) to allow for asynchronous operations | +| tiebreaker={string} | `TieBreaker` | `__Booksleeve_TieBreak` | Key to use for selecting a server in an ambiguous primary scenario | +| version={string} | `DefaultVersion` | (`4.0` in Azure, else `2.0`) | Redis version level (useful when the server does not make this available) | +| tunnel={string} | `Tunnel` | `null` | Tunnel for connections (use `http:{proxy url}` for "connect"-based proxy server) | +| setlib={bool} | `SetClientLibrary` | `true` | Whether to attempt to use `CLIENT SETINFO` to set the library name/version on the connection | +| protocol={string} | `Protocol` | `null` | Redis protocol to use; see section below | +| highIntegrity={bool} | `HighIntegrity` | `false` | High integrity (incurs overhead) sequence checking on every command; see section below | Additional code-only options: -- ReconnectRetryPolicy (`IReconnectRetryPolicy`) - Default: `ReconnectRetryPolicy = LinearRetry(ConnectTimeout);` +- LoggerFactory (`ILoggerFactory`) - Default: `null` + - The logger to use for connection events (not per command), e.g. connection log, disconnects, reconnects, server errors. +- ReconnectRetryPolicy (`IReconnectRetryPolicy`) - Default: `ReconnectRetryPolicy = ExponentialRetry(ConnectTimeout / 2);` + - Determines how often a multiplexer will try to reconnect after a failure +- BacklogPolicy - Default: `BacklogPolicy = BacklogPolicy.Default;` + - Determines how commands will be queued (or not) during a disconnect, for sending when it's available again +- BeforeSocketConnect - Default: `null` + - Allows modifying a `Socket` before connecting (for advanced scenarios) +- SslClientAuthenticationOptions (`netcoreapp3.1`/`net5.0` and higher) - Default: `null` + - Allows specifying exact options for SSL/TLS authentication against a server (e.g. cipher suites, protocols, etc.) - overrides all other SSL configuration options. This is a `Func` which receives the host (or `SslHost` if set) to get the options for. If `null` is returned from the `Func`, it's the same as this property not being set at all when connecting. +- SocketManager - Default: `SocketManager.Shared`: + - The thread pool to use for scheduling work to and from the socket connected to Redis, one of... + - `SocketManager.Shared`: Use a shared dedicated thread pool for _all_ multiplexers (defaults to 10 threads) - best balance for most scenarios. + - `SocketManager.ThreadPool`: Use the build-in .NET thread pool for scheduling. This can perform better for very small numbers of cores or with large apps on large machines that need to use more than 10 threads (total, across all multiplexers) under load. **Important**: this option isn't the default because it's subject to thread pool growth/starvation and if for example synchronous calls are waiting on a redis command to come back to unblock other threads, stalls/hangs can result. Use with caution, especially if you have sync-over-async work in play. +- HighIntegrity - Default: `false` + - This enables sending a sequence check command after _every single command_ sent to Redis. This is an opt-in option that incurs overhead to add this integrity check which isn't in the Redis protocol (RESP2/3) itself. The impact on this for a given workload depends on the number of commands, size of payloads, etc. as to how proportionately impactful it will be - you should test with your workloads to assess this. + - This is especially relevant if your primary use case is all strings (e.g. key/value caching) where the protocol would otherwise not error. + - Intended for cases where network drops (e.g. bytes from the Redis stream, not packet loss) are suspected and integrity of responses is critical. +- HeartbeatConsistencyChecks - Default: `false` + - Allows _always_ sending keepalive checks even if a connection isn't idle. This trades extra commands (per `HeartbeatInterval` - default 1 second) to check the network stream for consistency. If any data was lost, the result won't be as expected and the connection will be terminated ASAP. This is a check to react to any data loss at the network layer as soon as possible. +- HeartbeatInterval - Default: `1000ms` + - Allows running the heartbeat more often which importantly includes timeout evaluation for async commands. For example if you have a 50ms async command timeout, we're only actually checking it during the heartbeat (once per second by default), so it's possible 50-1050ms pass _before we notice it timed out_. If you want more fidelity in that check and to observe that a server failed faster, you can lower this to run the heartbeat more often to achieve that. + - **Note: heartbeats are not free and that's why the default is 1 second. There is additional overhead to running this more often simply because it does some work each time it fires.** +- LibraryName - Default: `SE.Redis` (unless a `DefaultOptionsProvider` specifies otherwise) + - The library name to use with `CLIENT SETINFO` when setting the library name/version on the connection Tokens in the configuration string are comma-separated; any without an `=` sign are assumed to be redis server endpoints. Endpoints without an explicit port will use 6379 if ssl is not enabled, and 6380 if ssl is enabled. Tokens starting with `$` are taken to represent command maps, for example: `$config=cfg`. -Automatic and Manual Configuration +## Obsolete Configuration Options +--- + +These options are parsed in connection strings for backwards compatibility (meaning they do not error as invalid), but no longer have any effect. + +| Configuration string | `ConfigurationOptions` | Previous Default | Previous Meaning | +| ---------------------- | ---------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------- | +| responseTimeout={int} | `ResponseTimeout` | `SyncTimeout` | Time (ms) to decide whether the socket is unhealthy | +| writeBuffer={int} | `WriteBuffer` | `4096` | Size of the output buffer | + +## Automatic and Manual Configuration --- -In many common scenarios, StackExchange.Redis will automatically configure a lot of settings, including the server type and version, connection timeouts, and master/slave relationships. Sometimes, though, the commands for this have been disabled on the redis server. In this case, it is useful to provide more information: +In many common scenarios, StackExchange.Redis will automatically configure a lot of settings, including the server type and version, connection timeouts, and primary/replica relationships. Sometimes, though, the commands for this have been disabled on the redis server. In this case, it is useful to provide more information: -```C# +```csharp ConfigurationOptions config = new ConfigurationOptions { EndPoints = @@ -120,12 +170,13 @@ Which is equivalent to the command string: ```config redis0:6379,redis1:6380,keepAlive=180,version=2.8.8,$CLIENT=,$CLUSTER=,$CONFIG=,$ECHO=,$INFO=,$PING= ``` -Renaming Commands + +## Renaming Commands --- A slightly unusual feature of redis is that you can disable and/or rename individual commands. As per the previous example, this is done via the `CommandMap`, but instead of passing a `HashSet` to `Create()` (to indicate the available or unavailable commands), you pass a `Dictionary`. All commands not mentioned in the dictionary are assumed to be enabled and not renamed. A `null` or blank value records that the command is disabled. For example: -```C# +```csharp var commands = new Dictionary { { "info", null }, // disabled { "select", "use" }, // renamed to SQL equivalent for some reason @@ -143,12 +194,34 @@ The above is equivalent to (in the connection string): $INFO=,$SELECT=use ``` -Twemproxy +## Redis Server Permissions +--- + +If the user you're connecting to Redis with is limited, it still needs to have certain commands enabled for the StackExchange.Redis to succeed in connecting. The client uses: +- `AUTH` to authenticate +- `CLIENT` to set the client name +- `INFO` to understand server topology/settings +- `ECHO` for heartbeat. +- (Optional) `SUBSCRIBE` to observe change events +- (Optional) `CONFIG` to get/understand settings +- (Optional) `CLUSTER` to get cluster nodes +- (Optional) `SENTINEL` only for Sentinel servers +- (Optional) `GET` to determine tie breakers +- (Optional) `SET` (_only_ if `INFO` is disabled) to see if we're writable + +For example, a common _very_ minimal configuration ACL on the server (non-cluster) would be: +```bash +-@all +@pubsub +@read +echo +info +``` + +Note that if you choose to disable access to the above commands, it needs to be done via the `CommandMap` and not only the ACL on the server (otherwise we'll attempt the command and fail the handshake). Also, if any of the these commands are disabled, some functionality may be diminished or broken. + +## twemproxy --- -[Twemproxy](https://github.com/twitter/twemproxy) is a tool that allows multiple redis instances to be used as though it were a single server, with inbuilt sharding and fault tolerance (much like redis cluster, but implemented separately). The feature-set available to Twemproxy is reduced. To avoid having to configure this manually, the `Proxy` option can be used: +[twemproxy](https://github.com/twitter/twemproxy) is a tool that allows multiple redis instances to be used as though it were a single server, with inbuilt sharding and fault tolerance (much like redis cluster, but implemented separately). The feature-set available to Twemproxy is reduced. To avoid having to configure this manually, the `Proxy` option can be used: -```C# +```csharp var options = new ConfigurationOptions { EndPoints = { "my-server" }, @@ -156,25 +229,38 @@ var options = new ConfigurationOptions }; ``` -Tiebreakers and Configuration Change Announcements +##envoyproxy +--- + +[Envoyproxy](https://github.com/envoyproxy/envoy) is a tool that allows to front a redis cluster with a set of proxies, with inbuilt discovery and fault tolerance. The feature-set available to Envoyproxy is reduced. To avoid having to configure this manually, the `Proxy` option can be used: +```csharp +var options = new ConfigurationOptions+{ + EndPoints = { "my-proxy1", "my-proxy2", "my-proxy3" }, + Proxy = Proxy.Envoyproxy +}; +``` + + +## Tiebreakers and Configuration Change Announcements --- -Normally StackExchange.Redis will resolve master/slave nodes automatically. However, if you are not using a management tool such as redis-sentinel or redis cluster, there is a chance that occasionally you will get multiple master nodes (for example, while resetting a node for maintenance it may reappear on the network as a master). To help with this, StackExchange.Redis can use the notion of a *tie-breaker* - which is only used when multiple masters are detected (not including redis cluster, where multiple masters are *expected*). For compatibility with BookSleeve, this defaults to the key named `"__Booksleeve_TieBreak"` (always in database 0). This is used as a crude voting mechanism to help determine the *preferred* master, so that work is routed correctly. +Normally StackExchange.Redis will resolve primary/replica nodes automatically. However, if you are not using a management tool such as redis-sentinel or redis cluster, there is a chance that occasionally you will get multiple primary nodes (for example, while resetting a node for maintenance it may reappear on the network as a primary). To help with this, StackExchange.Redis can use the notion of a *tie-breaker* - which is only used when multiple primaries are detected (not including redis cluster, where multiple primaries are *expected*). For compatibility with BookSleeve, this defaults to the key named `"__Booksleeve_TieBreak"` (always in database 0). This is used as a crude voting mechanism to help determine the *preferred* primary, so that work is routed correctly. -Likewise, when the configuration is changed (especially the master/slave configuration), it will be important for connected instances to make themselves aware of the new situation (via `INFO`, `CONFIG`, etc - where available). StackExchange.Redis does this by automatically subscribing to a pub/sub channel upon which such notifications may be sent. For similar reasons, this defaults to `"__Booksleeve_MasterChanged"`. +Likewise, when the configuration is changed (especially the primary/replica configuration), it will be important for connected instances to make themselves aware of the new situation (via `INFO`, `CONFIG`, etc - where available). StackExchange.Redis does this by automatically subscribing to a pub/sub channel upon which such notifications may be sent. For similar reasons, this defaults to `"__Booksleeve_MasterChanged"`. Both options can be customized or disabled (set to `""`), via the `.ConfigurationChannel` and `.TieBreaker` configuration properties. -These settings are also used by the `IServer.MakeMaster()` method, which can set the tie-breaker in the database and broadcast the configuration change message. The configuration message can also be used separately to master/slave changes simply to request all nodes to refresh their configurations, via the `ConnectionMultiplexer.PublishReconfigure` method. +These settings are also used by the `IServer.MakeMaster()` method, which can set the tie-breaker in the database and broadcast the configuration change message. The configuration message can also be used separately to primary/replica changes simply to request all nodes to refresh their configurations, via the `ConnectionMultiplexer.PublishReconfigure` method. -ReconnectRetryPolicy +## ReconnectRetryPolicy --- + StackExchange.Redis automatically tries to reconnect in the background when the connection is lost for any reason. It keeps retrying until the connection has been restored. It would use ReconnectRetryPolicy to decide how long it should wait between the retries. -ReconnectRetryPolicy can be linear (default), exponential or a custom retry policy. +ReconnectRetryPolicy can be exponential (default), linear or a custom retry policy. Examples: -```C# +```csharp config.ReconnectRetryPolicy = new ExponentialRetry(5000); // defaults maxDeltaBackoff to 10000 ms //retry# retry to re-connect after time in milliseconds //1 a random value between 5000 and 5500 @@ -184,12 +270,25 @@ config.ReconnectRetryPolicy = new ExponentialRetry(5000); // defaults maxDeltaBa //5 a random value between 5000 and 10000, since maxDeltaBackoff was 10000 ms //6 a random value between 5000 and 10000 -config.ReconnectRetryPolicy = new LinearRetry(5000); +config.ReconnectRetryPolicy = new LinearRetry(5000); //retry# retry to re-connect after time in milliseconds -//1 5000 +//1 5000 //2 5000 //3 5000 -//4 5000 -//5 5000 -//6 5000 +//4 5000 +//5 5000 +//6 5000 ``` + +## Redis protocol + +Without specific configuration, StackExchange.Redis will use the RESP2 protocol; this means that pub/sub requires a separate connection to the server. RESP3 is a newer protocol +(usually, but not always, available on v6 servers and above) which allows (among other changes) pub/sub messages to be communicated on the *same* connection - which can be very +desirable in servers with a large number of clients. The protocol handshake needs to happen very early in the connection, so *by default* the library does not attempt a RESP3 connection +unless it has reason to expect it to work. + +The library determines whether to use RESP3 by: +- The `HELLO` command has been disabled: RESP2 is used +- A protocol *other than* `resp3` or `3` is specified: RESP2 is used +- A protocol of `resp3` or `3` is specified: RESP3 is attempted (with fallback if it fails) +- In all other scenarios: RESP2 is used diff --git a/docs/ExecSync.md b/docs/ExecSync.md index 2b3409fa2..e4a09ec95 100644 --- a/docs/ExecSync.md +++ b/docs/ExecSync.md @@ -1,5 +1,5 @@ The Dangers of Synchronous Continuations === -Once, there was more content here; then [a suitably evil workaround was found](http://stackoverflow.com/a/22588431/23354). This page is not +Once, there was more content here; then [a suitably evil workaround was found](https://stackoverflow.com/a/22588431/23354). This page is not listed in the index, but remains for your curiosity. \ No newline at end of file diff --git a/docs/KeysScan.md b/docs/KeysScan.md index d0af03a02..001ed056d 100644 --- a/docs/KeysScan.md +++ b/docs/KeysScan.md @@ -17,7 +17,7 @@ The key word here, oddly enough, is the last one: database. Because StackExchang - `CLIENT` - `CLUSTER` - `CONFIG` / `INFO` / `TIME` -- `SLAVEOF` +- `REPLICAOF` - `SAVE` / `BGSAVE` / `LASTSAVE` - `SCRIPT` (not to be confused with `EVAL` / `EVALSHA`) - `SHUTDOWN` @@ -38,7 +38,7 @@ So how do I use them? Simple: start from a server, not a database. -```C# +```csharp // get the target server var server = conn.GetServer(someServer); @@ -53,9 +53,9 @@ server.FlushDatabase(); Note that unlike the `IDatabase` API (where the target database has already been selected in the `GetDatabase()` call), these methods take an optional parameter for the database, or it defaults to `0`. -The `Keys(...)` method deserves special mention: it is unusual in that it does not have an `*Async` counterpart. The reason for this is that behind the scenes, the system will determine the most appropriate method to use (`KEYS` vs `SCAN`, based on the server version), and if possible will use the `SCAN` approach to hand you back an `IEnumerable` that does all the paging internally - so you never need to see the implementation details of the cursor operations. If `SCAN` is not available, it will use `KEYS`, which can cause blockages at the server. Either way, both `SCAN` and `KEYS` will need to sweep the entire keyspace, so should be avoided on production servers - or at least, targeted at slaves. +The `Keys(...)` method deserves special mention: it is unusual in that it does not have an `*Async` counterpart. The reason for this is that behind the scenes, the system will determine the most appropriate method to use (`KEYS` vs `SCAN`, based on the server version), and if possible will use the `SCAN` approach to hand you back an `IEnumerable` that does all the paging internally - so you never need to see the implementation details of the cursor operations. If `SCAN` is not available, it will use `KEYS`, which can cause blockages at the server. Either way, both `SCAN` and `KEYS` will need to sweep the entire keyspace, so should be avoided on production servers - or at least, targeted at replicas. So I need to remember which server I connected to? That sucks! --- -No, not quite. You can use `conn.GetEndPoints()` to list the endpoints (either all known, or the ones specified in the original configuration - these are not necessarily the same thing), and iterate with `GetServer()` to find the server you want (for example, selecting a slave). +No, not quite. You can use `conn.GetEndPoints()` to list the endpoints (either all known, or the ones specified in the original configuration - these are not necessarily the same thing), and iterate with `GetServer()` to find the server you want (for example, selecting a replica). diff --git a/docs/KeysValues.md b/docs/KeysValues.md index 36e535782..0a414ce21 100644 --- a/docs/KeysValues.md +++ b/docs/KeysValues.md @@ -1,9 +1,9 @@ Keys, Values and Channels === -In dealing with redis, there is quite an important distinction between *keys* and *everything else*. A key is the unique name of a piece of data (which could be a String, a List, Hash, or any of the other [redis data types](http://redis.io/topics/data-types)) within a database. Keys are never interpreted as... well, anything: they are simply inert names. Further - when dealing with clustered or sharded systems, it is the key that defines the node (or nodes if there are slaves) that contain this data - so keys are crucial for routing commands. +In dealing with redis, there is quite an important distinction between *keys* and *everything else*. A key is the unique name of a piece of data (which could be a String, a List, Hash, or any of the other [redis data types](https://redis.io/topics/data-types)) within a database. Keys are never interpreted as... well, anything: they are simply inert names. Further - when dealing with clustered or sharded systems, it is the key that defines the node (or nodes if there are replicas) that contain this data - so keys are crucial for routing commands. -This contrasts with *values*; values are the *things that you store* against keys - either individually (for String data) or as groups. Values do not affect command routing (caveat: except for [the `SORT` command](http://redis.io/commands/sort) when `BY` or `GET` is specified, but that is *really* complicated to explain). Likewise, values are often *interpreted* by redis for the purposes of an operation: +This contrasts with *values*; values are the *things that you store* against keys - either individually (for String data) or as groups. Values do not affect command routing (caveat: except for [the `SORT` command](https://redis.io/commands/sort) when `BY` or `GET` is specified, but that is *really* complicated to explain). Likewise, values are often *interpreted* by redis for the purposes of an operation: - `incr` (and the various similar commands) interpret String values as numeric data - sorting can interpret values using either numeric or unicode rules @@ -18,21 +18,21 @@ Keys StackExchange.Redis represents keys by the `RedisKey` type. The good news, though, is that this has implicit conversions to and from both `string` and `byte[]`, allowing both text and binary keys to be used without any complication. For example, the `StringIncrement` method takes a `RedisKey` as the first parameter, but *you don't need to know that*; for example: -```C# +```csharp string key = ... db.StringIncrement(key); ``` or -```C# +```csharp byte[] key = ... db.StringIncrement(key); ``` Likewise, there are operations that *return* keys as `RedisKey` - and again, it simply works: -```C# +```csharp string someKey = db.KeyRandom(); ``` @@ -41,13 +41,13 @@ Values StackExchange.Redis represents values by the `RedisValue` type. As with `RedisKey`, there are implicit conversions in place which mean that most of the time you never see this type, for example: -```C# +```csharp db.StringSet("mykey", "myvalue"); ``` However, in addition to text and binary contents, values can also need to represent typed primitive data - most commonly (in .NET terms) `Int32`, `Int64`, `Double` or `Boolean`. Because of this, `RedisValue` provides a lot more conversion support than `RedisKey`: -```C# +```csharp db.StringSet("mykey", 123); // this is still a RedisKey and RedisValue ... int i = (int)db.StringGet("mykey"); @@ -57,14 +57,14 @@ Note that while the conversions from primitives to `RedisValue` are implicit, ma Note additionally that *when treated numerically*, redis treats a non-existent key as zero; for consistency with this, nil responses are treated as zero: -```C# +```csharp db.KeyDelete("abc"); int i = (int)db.StringGet("abc"); // this is ZERO ``` If you need to detect the nil condition, then you can check for that: -```C# +```csharp db.KeyDelete("abc"); var value = db.StringGet("abc"); bool isNil = value.IsNull; // this is true @@ -72,7 +72,7 @@ bool isNil = value.IsNull; // this is true or perhaps more simply, just use the provided `Nullable` support: -```C# +```csharp db.KeyDelete("abc"); var value = (int?)db.StringGet("abc"); // behaves as you would expect ``` @@ -90,14 +90,14 @@ Channel names for pub/sub are represented by the `RedisChannel` type; this is la Scripting --- -[Lua scripting in redis](http://redis.io/commands/EVAL) has two notable features: +[Lua scripting in redis](https://redis.io/commands/EVAL) has two notable features: - the inputs must keep keys and values separate (which inside the script become `KEYS` and `ARGV`, respectively) - the return format is not defined in advance: it is specific to your script Because of this, the `ScriptEvaluate` method accepts two separate input arrays: one `RedisKey[]` for the keys, one `RedisValue[]` for the values (both are optional, and are assumed to be empty if omitted). This is probably one of the few times that you'll actually need to type `RedisKey` or `RedisValue` in your code, and that is just because of array variance rules: -```C# +```csharp var result = db.ScriptEvaluate(TransferScript, new RedisKey[] { from, to }, new RedisValue[] { quantity }); ``` @@ -106,7 +106,7 @@ var result = db.ScriptEvaluate(TransferScript, The response uses the `RedisResult` type (this is unique to scripting; usually the API tries to represent the response as directly and clearly as possible). As before, `RedisResult` offers a range of conversion operations - more, in fact than `RedisValue`, because in addition to being interpreted as text, binary, primitives and nullable-primitives, the response can *also* be interpreted as *arrays* of such, for example: -```C# +```csharp string[] items = db.ScriptEvaluate(...); ``` diff --git a/docs/PipelinesMultiplexers.md b/docs/PipelinesMultiplexers.md index be4662c1f..b1711531f 100644 --- a/docs/PipelinesMultiplexers.md +++ b/docs/PipelinesMultiplexers.md @@ -3,7 +3,7 @@ Latency sucks. Modern computers can churn data at an alarming rate, and high speed networking (often with multiple parallel links between important servers) provides enormous bandwidth, but... that damned latency means that computers spend an awful lot of time *waiting for data* and that is one of the several reasons that continuation-based programming is becoming increasingly popular. Let's consider some regular procedural code: -```C# +```csharp string a = db.StringGet("a"); string b = db.StringGet("b"); ``` @@ -42,7 +42,7 @@ Because of this, many redis clients allow you to make use of *pipelining*; this For example, to pipeline the two gets using procedural (blocking) code, we could use: -```C# +```csharp var aPending = db.StringGetAsync("a"); var bPending = db.StringGetAsync("b"); var a = db.Wait(aPending); @@ -56,7 +56,7 @@ Fire and Forget A special-case of pipelining is when we expressly don't care about the response from a particular operation, which allows our code to continue immediately while the enqueued operation proceeds in the background. Often, this means that we can put concurrent work on the connection from a single caller. This is achieved using the `flags` parameter: -```C# +```csharp // sliding expiration db.KeyExpire(key, TimeSpan.FromMinutes(5), flags: CommandFlags.FireAndForget); var value = (string)db.StringGet(key); @@ -69,9 +69,9 @@ Multiplexing Pipelining is all well and good, but often any single block of code only wants a single value (or maybe wants to perform a few operations, but which depend on each-other). This means that we still have the problem that we spend most of our time waiting for data to transfer between client and server. Now consider a busy application, perhaps a web-server. Such applications are generally inherently concurrent, so if you have 20 parallel application requests all requiring data, you might think of spinning up 20 connections, or you could synchronize access to a single connection (which would mean the last caller would need to wait for the latency of all the other 19 before it even got started). Or as a compromise, perhaps a pool of 5 connections which are leased - no matter how you are doing it, there is going to be a lot of waiting. **StackExchange.Redis does not do this**; instead, it does a *lot* of work for you to make effective use of all this idle time by *multiplexing* a single connection. When used concurrently by different callers, it **automatically pipelines the separate requests**, so regardless of whether the requests use blocking or asynchronous access, the work is all pipelined. So we could have 10 or 20 of our "get a and b" scenario from earlier (from different application requests), and they would all get onto the connection as soon as possible. Essentially, it fills the `waiting` time with work from other callers. -For this reason, the only redis features that StackExchange.Redis does not offer (and *will not ever offer*) are the "blocking pops" ([BLPOP](http://redis.io/commands/blpop), [BRPOP](http://redis.io/commands/brpop) and [BRPOPLPUSH](http://redis.io/commands/brpoplpush)) - because this would allow a single caller to stall the entire multiplexer, blocking all other callers. The only other time that StackExchange.Redis needs to hold work is when verifying pre-conditions for a transaction, which is why StackExchange.Redis encapsulates such conditions into internally managed `Condition` instances. [Read more about transactions here](Transactions). If you feel you want "blocking pops", then I strongly suggest you consider pub/sub instead: +For this reason, the only redis features that StackExchange.Redis does not offer (and *will not ever offer*) are the "blocking pops" ([BLPOP](https://redis.io/commands/blpop), [BRPOP](https://redis.io/commands/brpop) and [BRPOPLPUSH](https://redis.io/commands/brpoplpush)) - because this would allow a single caller to stall the entire multiplexer, blocking all other callers. The only other time that StackExchange.Redis needs to hold work is when verifying pre-conditions for a transaction, which is why StackExchange.Redis encapsulates such conditions into internally managed `Condition` instances. [Read more about transactions here](Transactions). If you feel you want "blocking pops", then I strongly suggest you consider pub/sub instead: -```C# +```csharp sub.Subscribe(channel, delegate { string work = db.ListRightPop(key); if (work != null) Process(work); @@ -96,7 +96,7 @@ Concurrency It should be noted that the pipeline / multiplexer / future-value approach also plays very nicely with continuation-based asynchronous code; for example you could write: -```C# +```csharp string value = await db.StringGetAsync(key); if (value == null) { value = await ComputeValueFromDatabase(...); @@ -105,6 +105,6 @@ if (value == null) { return value; ``` - [1]: http://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx - [2]: http://msdn.microsoft.com/en-us/library/system.threading.tasks.task(v=vs.110).aspx - [3]: http://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).aspx + [1]: https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl + [2]: https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task + [3]: https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1 diff --git a/docs/Profiling.md b/docs/Profiling.md index f39c824f4..cffdae25d 100644 --- a/docs/Profiling.md +++ b/docs/Profiling.md @@ -1,208 +1,10 @@ Profiling === -StackExchange.Redis exposes a handful of methods and types to enable performance profiling. Due to its asynchronous and multiplexing -behavior profiling is a somewhat complicated topic. +The profiling API has undergone breaking changes between 1.* and 2.*; in 1.*; in particular, +the `object GetContext()` API was unintuitive for consumers and expensive for the library (due to book-keeping). The API in 2.* is much +simpler and more "obvious". This is a breaking change. -Interfaces ---- +[Profiling in 1.\*](Profiling_v1.md) -The profiling interface is composed of `IProfiler`, `ConnectionMultiplexer.RegisterProfiler(IProfiler)`, `ConnectionMultiplexer.BeginProfiling(object)`, -`ConnectionMultiplexer.FinishProfiling(object)`, and `IProfiledCommand`. - -You register a single `IProfiler` with a `ConnectionMultiplexer` instance, it cannot be changed. You begin profiling for a given context (ie. Thread, -Http Request, and so on) by calling `BeginProfiling(object)`, and finish by calling `FinishProfiling(object)`. `FinishProfiling(object)` returns -a collection of `IProfiledCommand`s which contain timing information for all commands sent to redis by the configured `ConnectionMultiplexer` between -the `(Begin|Finish)Profiling` calls with the given context. - -What "context" object should be used is application specific. - -Available Timings ---- - -StackExchange.Redis exposes information about: - - - The redis server involved - - The redis DB being queried - - The redis command run - - The flags used to route the command - - The initial creation time of a command - - How long it took to enqueue the command - - How long it took to send the command, after it was enqueued - - How long it took the response from redis to be received, after the command was sent - - How long it took for the response to be processed, after it was received - - If the command was sent in response to a cluster ASK or MOVED response - - If so, what the original command was - -`TimeSpan`s are high resolution, if supported by the runtime. `DateTime`s are only as precise as `DateTime.UtcNow`. - -Choosing Context ---- - -Due to StackExchange.Redis's asynchronous interface, profiling requires outside assistance to group related commands together. This is achieved -by providing context objects when you start and end profiling (via the `BeginProfiling(object)` & `FinishProfiling(object)` methods), and when a -command is sent (via the `IProfiler` interface's `GetContext()` method). - -A toy example of associating commands issued from many different threads together - -```C# -class ToyProfiler : IProfiler -{ - public ConcurrentDictionary Contexts = new ConcurrentDictionary(); - - public object GetContext() - { - object ctx; - if(!Contexts.TryGetValue(Thread.CurrentThread, out ctx)) ctx = null; - - return ctx; - } -} - -// ... - -ConnectionMultiplexer conn = /* initialization */; -var profiler = new ToyProfiler(); -var thisGroupContext = new object(); - -conn.RegisterProfiler(profiler); - -var threads = new List(); - -for (var i = 0; i < 16; i++) -{ - var db = conn.GetDatabase(i); - - var thread = - new Thread( - delegate() - { - var threadTasks = new List(); - - for (var j = 0; j < 1000; j++) - { - var task = db.StringSetAsync("" + j, "" + j); - threadTasks.Add(task); - } - - Task.WaitAll(threadTasks.ToArray()); - } - ); - - profiler.Contexts[thread] = thisGroupContext; - - threads.Add(thread); -} - -conn.BeginProfiling(thisGroupContext); - -threads.ForEach(thread => thread.Start()); -threads.ForEach(thread => thread.Join()); - -IEnumerable timings = conn.FinishProfiling(thisGroupContext); -``` - -At the end, `timings` will contain 16,000 `IProfiledCommand` objects - one for each command issued to redis. - -If instead you did the following: - -```C# -ConnectionMultiplexer conn = /* initialization */; -var profiler = new ToyProfiler(); - -conn.RegisterProfiler(profiler); - -var threads = new List(); - -var perThreadTimings = new ConcurrentDictionary>(); - -for (var i = 0; i < 16; i++) -{ - var db = conn.GetDatabase(i); - - var thread = - new Thread( - delegate() - { - var threadTasks = new List(); - - conn.BeginProfiling(Thread.CurrentThread); - - for (var j = 0; j < 1000; j++) - { - var task = db.StringSetAsync("" + j, "" + j); - threadTasks.Add(task); - } - - Task.WaitAll(threadTasks.ToArray()); - - perThreadTimings[Thread.CurrentThread] = conn.FinishProfiling(Thread.CurrentThread).ToList(); - } - ); - - profiler.Contexts[thread] = thread; - - threads.Add(thread); -} - -threads.ForEach(thread => thread.Start()); -threads.ForEach(thread => thread.Join()); -``` - -`perThreadTimings` would end up with 16 entries of 1,000 `IProfilingCommand`s, keyed by the `Thread` the issued them. - -Moving away from toy examples, here's how you can profile StackExchange.Redis in an MVC5 application. - -First register the following `IProfiler` against your `ConnectionMultiplexer`: - -```C# -public class RedisProfiler : IProfiler -{ - const string RequestContextKey = "RequestProfilingContext"; - - public object GetContext() - { - var ctx = HttpContext.Current; - if (ctx == null) return null; - - return ctx.Items[RequestContextKey]; - } - - public object CreateContextForCurrentRequest() - { - var ctx = HttpContext.Current; - if (ctx == null) return null; - - object ret; - ctx.Items[RequestContextKey] = ret = new object(); - - return ret; - } -} -``` - -Then, add the following to your Global.asax.cs file: - -```C# -protected void Application_BeginRequest() -{ - var ctxObj = RedisProfiler.CreateContextForCurrentRequest(); - if (ctxObj != null) - { - RedisConnection.BeginProfiling(ctxObj); - } -} - -protected void Application_EndRequest() -{ - var ctxObj = RedisProfiler.GetContext(); - if (ctxObj != null) - { - var timings = RedisConnection.FinishProfiling(ctxObj); - - // do what you will with `timings` here - } -} -``` - -This implementation will group all redis commands, including `async/await`-ed ones, with the http request that initiated them. \ No newline at end of file +[Profiling in 2.\*](Profiling_v2.md) diff --git a/docs/Profiling_v1.md b/docs/Profiling_v1.md new file mode 100644 index 000000000..f55ff7965 --- /dev/null +++ b/docs/Profiling_v1.md @@ -0,0 +1,208 @@ +Profiling +=== + +StackExchange.Redis exposes a handful of methods and types to enable performance profiling. Due to its asynchronous and multiplexing +behavior profiling is a somewhat complicated topic. + +Interfaces +--- + +The profiling interface is composed of `IProfiler`, `ConnectionMultiplexer.RegisterProfiler(IProfiler)`, `ConnectionMultiplexer.BeginProfiling(object)`, +`ConnectionMultiplexer.FinishProfiling(object)`, and `IProfiledCommand`. + +You register a single `IProfiler` with a `ConnectionMultiplexer` instance, it cannot be changed. You begin profiling for a given context (ie. Thread, +Http Request, and so on) by calling `BeginProfiling(object)`, and finish by calling `FinishProfiling(object)`. `FinishProfiling(object)` returns +a collection of `IProfiledCommand`s which contain timing information for all commands sent to redis by the configured `ConnectionMultiplexer` between +the `(Begin|Finish)Profiling` calls with the given context. + +What "context" object should be used is application specific. + +Available Timings +--- + +StackExchange.Redis exposes information about: + + - The redis server involved + - The redis DB being queried + - The redis command run + - The flags used to route the command + - The initial creation time of a command + - How long it took to enqueue the command + - How long it took to send the command, after it was enqueued + - How long it took the response from redis to be received, after the command was sent + - How long it took for the response to be processed, after it was received + - If the command was sent in response to a cluster ASK or MOVED response + - If so, what the original command was + +`TimeSpan`s are high resolution, if supported by the runtime. `DateTime`s are only as precise as `DateTime.UtcNow`. + +Choosing Context +--- + +Due to StackExchange.Redis's asynchronous interface, profiling requires outside assistance to group related commands together. This is achieved +by providing context objects when you start and end profiling (via the `BeginProfiling(object)` & `FinishProfiling(object)` methods), and when a +command is sent (via the `IProfiler` interface's `GetContext()` method). + +A toy example of associating commands issued from many different threads together + +```csharp +class ToyProfiler : IProfiler +{ + public ConcurrentDictionary Contexts = new ConcurrentDictionary(); + + public object GetContext() + { + object ctx; + if(!Contexts.TryGetValue(Thread.CurrentThread, out ctx)) ctx = null; + + return ctx; + } +} + +// ... + +ConnectionMultiplexer conn = /* initialization */; +var profiler = new ToyProfiler(); +var thisGroupContext = new object(); + +conn.RegisterProfiler(profiler); + +var threads = new List(); + +for (var i = 0; i < 16; i++) +{ + var db = conn.GetDatabase(i); + + var thread = + new Thread( + delegate() + { + var threadTasks = new List(); + + for (var j = 0; j < 1000; j++) + { + var task = db.StringSetAsync("" + j, "" + j); + threadTasks.Add(task); + } + + Task.WaitAll(threadTasks.ToArray()); + } + ); + + profiler.Contexts[thread] = thisGroupContext; + + threads.Add(thread); +} + +conn.BeginProfiling(thisGroupContext); + +threads.ForEach(thread => thread.Start()); +threads.ForEach(thread => thread.Join()); + +IEnumerable timings = conn.FinishProfiling(thisGroupContext); +``` + +At the end, `timings` will contain 16,000 `IProfiledCommand` objects - one for each command issued to redis. + +If instead you did the following: + +```csharp +ConnectionMultiplexer conn = /* initialization */; +var profiler = new ToyProfiler(); + +conn.RegisterProfiler(profiler); + +var threads = new List(); + +var perThreadTimings = new ConcurrentDictionary>(); + +for (var i = 0; i < 16; i++) +{ + var db = conn.GetDatabase(i); + + var thread = + new Thread( + delegate() + { + var threadTasks = new List(); + + conn.BeginProfiling(Thread.CurrentThread); + + for (var j = 0; j < 1000; j++) + { + var task = db.StringSetAsync("" + j, "" + j); + threadTasks.Add(task); + } + + Task.WaitAll(threadTasks.ToArray()); + + perThreadTimings[Thread.CurrentThread] = conn.FinishProfiling(Thread.CurrentThread).ToList(); + } + ); + + profiler.Contexts[thread] = thread; + + threads.Add(thread); +} + +threads.ForEach(thread => thread.Start()); +threads.ForEach(thread => thread.Join()); +``` + +`perThreadTimings` would end up with 16 entries of 1,000 `IProfilingCommand`s, keyed by the `Thread` that issued them. + +Moving away from toy examples, here's how you can profile StackExchange.Redis in an MVC5 application. + +First register the following `IProfiler` against your `ConnectionMultiplexer`: + +```csharp +public class RedisProfiler : IProfiler +{ + const string RequestContextKey = "RequestProfilingContext"; + + public object GetContext() + { + var ctx = HttpContext.Current; + if (ctx == null) return null; + + return ctx.Items[RequestContextKey]; + } + + public object CreateContextForCurrentRequest() + { + var ctx = HttpContext.Current; + if (ctx == null) return null; + + object ret; + ctx.Items[RequestContextKey] = ret = new object(); + + return ret; + } +} +``` + +Then, add the following to your Global.asax.cs file: + +```csharp +protected void Application_BeginRequest() +{ + var ctxObj = RedisProfiler.CreateContextForCurrentRequest(); + if (ctxObj != null) + { + RedisConnection.BeginProfiling(ctxObj); + } +} + +protected void Application_EndRequest() +{ + var ctxObj = RedisProfiler.GetContext(); + if (ctxObj != null) + { + var timings = RedisConnection.FinishProfiling(ctxObj); + + // do what you will with `timings` here + } +} +``` + +This implementation will group all redis commands, including `async/await`-ed ones, with the http request that initiated them. diff --git a/docs/Profiling_v2.md b/docs/Profiling_v2.md new file mode 100644 index 000000000..a6ac9943f --- /dev/null +++ b/docs/Profiling_v2.md @@ -0,0 +1,229 @@ +Profiling +=== + +StackExchange.Redis exposes a handful of methods and types to enable performance profiling. Due to its asynchronous and multiplexing +behavior profiling is a somewhat complicated topic. + +Interfaces +--- + +The profiling API is composed of `ProfilingSession`, `ConnectionMultiplexer.RegisterProfiler(Func)`, +`ProfilingSession.FinishProfiling()`, and `IProfiledCommand`. + +You register a callback (`Func`) that provides an ambient `ProfilingSession` with a `ConnectionMultiplexer` instance. When needed, +the library invokes this callback, and *if* a non-null session is returned: operations are attached to that session. Calling `FinishProfiling` on +a particular profiling sesssion returns a collection of `IProfiledCommand`s which contain timing information for all commands sent to redis by the +configured `ConnectionMultiplexer`. It is the callback's responsibility to maintain any state required to track individual sessions. + +Available Timings +--- + +StackExchange.Redis exposes information about: + + - The redis server involved + - The redis DB being queried + - The redis command run + - The flags used to route the command + - The initial creation time of a command + - How long it took to enqueue the command + - How long it took to send the command, after it was enqueued + - How long it took the response from redis to be received, after the command was sent + - How long it took for the response to be processed, after it was received + - If the command was sent in response to a cluster ASK or MOVED response + - If so, what the original command was + +`TimeSpan`s are high resolution, if supported by the runtime. `DateTime`s are only as precise as `DateTime.UtcNow`. + +Example profilers +--- + +Due to StackExchange.Redis's asynchronous interface, profiling requires outside assistance to group related commands together. +This is achieved by providing the desired `ProfilingSession` object via the callback, and (later) calling `FinishProfiling()` on that session. + +Probably the most useful general-purpose session-provider is one that provides sessions automatically and works between `async` calls; this is simply: + +```csharp +class AsyncLocalProfiler +{ + private readonly AsyncLocal perThreadSession = new AsyncLocal(); + + public ProfilingSession GetSession() + { + var val = perThreadSession.Value; + if (val == null) + { + perThreadSession.Value = val = new ProfilingSession(); + } + return val; + } +} +... +var profiler = new AsyncLocalProfiler(); +multiplexer.RegisterProfiler(profiler.GetSession); +``` + +This will automatically create a profiling session per async-context (re-using the existing session if there is one). At the end of some unit of work, the +calling code can use `var commands = profiler.GetSession().FinishProfiling();` to get the operations performed and timings data. + + +--- + + +A toy example of associating commands issued from many different threads together (while still allowing unrelated work not to be profiled) + +1.* + +```csharp +class ToyProfiler +{ + // note this won't work over "await" boundaries; "AsyncLocal" would be necessary there + private readonly ThreadLocal perThreadSession = new ThreadLocal(); + public ProfilingSession PerThreadSession + { + get => perThreadSession.Value; + set => perThreadSession.Value = value; + } +} + +// ... + +ConnectionMultiplexer conn = /* initialization */; +var profiler = new ToyProfiler(); +var sharedSession = new ProfilingSession(); + +conn.RegisterProfiler(() => profiler.PerThreadSession); + +var threads = new List(); + +for (var i = 0; i < 16; i++) +{ + var db = conn.GetDatabase(i); + + var thread = + new Thread( + delegate() + { + // set each thread to share a session + profiler.PerThreadSession = sharedSession; + + var threadTasks = new List(); + + for (var j = 0; j < 1000; j++) + { + var task = db.StringSetAsync("" + j, "" + j); + threadTasks.Add(task); + } + + Task.WaitAll(threadTasks.ToArray()); + } + ); + + threads.Add(thread); +} + +threads.ForEach(thread => thread.Start()); +threads.ForEach(thread => thread.Join()); + +var timings = sharedSession.FinishProfiling(); +``` + +At the end, `timings` will contain 16,000 `IProfiledCommand` objects - one for each command issued to redis. + +If instead you did the following: + +```csharp +ConnectionMultiplexer conn = /* initialization */; +var profiler = new ToyProfiler(); + +conn.RegisterProfiler(() => profiler.PerThreadSession); + +var threads = new List(); + +var perThreadTimings = new ConcurrentDictionary>(); + +for (var i = 0; i < 16; i++) +{ + var db = conn.GetDatabase(i); + + var thread = + new Thread( + delegate() + { + var threadTasks = new List(); + profiler.PerThreadSession = new ProfilingSession(); + + for (var j = 0; j < 1000; j++) + { + var task = db.StringSetAsync("" + j, "" + j); + threadTasks.Add(task); + } + + Task.WaitAll(threadTasks.ToArray()); + + perThreadTimings[Thread.CurrentThread] = profiler.PerThreadSession.FinishProfiling().ToList(); + } + ); + threads.Add(thread); +} + +threads.ForEach(thread => thread.Start()); +threads.ForEach(thread => thread.Join()); +``` + +`perThreadTimings` would end up with 16 entries of 1,000 `IProfilingCommand`s, keyed by the `Thread` that issued them. + +Moving away from toy examples, here's how you can profile StackExchange.Redis in an MVC5 application. + +First register the following `IProfiler` against your `ConnectionMultiplexer`: + +```csharp +public class RedisProfiler +{ + const string RequestContextKey = "RequestProfilingContext"; + + public ProfilingSession GetSession() + { + var ctx = HttpContext.Current; + if (ctx == null) return null; + + return (ProfilingSession)ctx.Items[RequestContextKey]; + } + + public void CreateSessionForCurrentRequest() + { + var ctx = HttpContext.Current; + if (ctx != null) + { + ctx.Items[RequestContextKey] = new ProfilingSession(); + } + } +} +``` + +Then, add the following to your Global.asax.cs file (where `_redisProfiler` is the *instance* of the profiler): + +```csharp +protected void Application_BeginRequest() +{ + _redisProfiler.CreateSessionForCurrentRequest(); +} + +protected void Application_EndRequest() +{ + var session = _redisProfiler.GetSession(); + if (session != null) + { + var timings = session.FinishProfiling(); + + // do what you will with `timings` here + } +} +``` + +and ensure that the connection has the profiler registered when the connection is created: + +```csharp +connection.RegisterProfiler(() => _redisProfiler.GetSession()); +``` + +This implementation will group all redis commands, including `async/await`-ed ones, with the http request that initiated them. diff --git a/docs/PubSubOrder.md b/docs/PubSubOrder.md index 26321c969..9a7104962 100644 --- a/docs/PubSubOrder.md +++ b/docs/PubSubOrder.md @@ -1,20 +1,25 @@ Pub/Sub Message Order === -When using the pub/sub API, there is a decision to be made as to whether messages from the same connection should be processed *sequentially* vs *concurrently*. +When using the pub/sub API, there is a decision to be made as to whether messages from the same connection should be processed *sequentially* vs *concurrently*. It is strongly recommended that you use concurrent processing whenever possible Processing them sequentially means that you don't need to worry (quite as much) about thread-safety, and means that you preserve the order of events - they will be processed in exactly the same order in which they are received (via a queue) - but as a consequence it means that messages can delay each-other. +```csharp +var channel = multiplexer.GetSubscriber().Subscribe("messages"); +channel.OnMessage(message => +{ + Console.WriteLine((string)message.Message); +}); +``` + The other option is *concurrent* processing. This makes **no specific guarantees** about the order in which work gets processed, and your code is entirely responsible for ensuring that concurrent messages don't corrupt your internal state - but it can be significantly faster and much more scalable. This works *particularly* well if messages are generally unrelated. -For safety, **the default is sequential**; however, it is strongly recommended that you use concurrent processing whenever possible. This is a simple change: - -```C# -multiplexer.PreserveAsyncOrder = false; +```csharp +multiplexer.GetSubscriber().Subscribe("messages", (channel, message) => { + Console.WriteLine((string)message); +}); ``` - -The reason that this is not a *configuration* option is that whether it is appropriate to do this depends *entirely* on the code that is subscribing to -messages. \ No newline at end of file diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 46a70f3a3..fa2773519 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -1,68 +1,583 @@ # Release Notes -# Important: .NET 4.0 support is currently **disabled**; if you need .NET 4.0, please stick with 1.2.1 while we keep investigating +Current package versions: + +| NuGet Stable | NuGet Pre-release | MyGet | +| ------------ | ----------------- | ----- | +| [![StackExchange.Redis](https://img.shields.io/nuget/v/StackExchange.Redis.svg)](https://www.nuget.org/packages/StackExchange.Redis/) | [![StackExchange.Redis](https://img.shields.io/nuget/vpre/StackExchange.Redis.svg)](https://www.nuget.org/packages/StackExchange.Redis/) | [![StackExchange.Redis MyGet](https://img.shields.io/myget/stackoverflow/vpre/StackExchange.Redis.svg)](https://www.myget.org/feed/stackoverflow/package/nuget/StackExchange.Redis) | + +## Unreleased + +## 2.9.32 + +- Fix `SSUBSCRIBE` routing during slot migrations ([#2969 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2969)) + +## 2.9.25 + +- (build) Fix SNK on non-Windows builds ([#2963 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2963)) + +## 2.9.24 + +- Fix [#2951](https://github.com/StackExchange/StackExchange.Redis/issues/2951) - sentinel reconnection failure ([#2956 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2956)) +- Mitigate [#2955](https://github.com/StackExchange/StackExchange.Redis/issues/2955) (unbalanced pub/sub routing) / add `RedisValue.WithKeyRouting()` ([#2958 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2958)) +- Fix envoyproxy command exclusions ([#2957 by sshumakov](https://github.com/StackExchange/StackExchange.Redis/pull/2957)) +- Restrict `RedisValue` hex fallback (`string` conversion) to encoding failures ([2954 by jcaspes](https://github.com/StackExchange/StackExchange.Redis/pull/2954)) +- (internals) prefer `Volatile.Read` over `Thread.VolatileRead` ([2960 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2960)) + +## 2.9.17 + +- Add vector-set support ([#2939 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2939)) +- Fix `RedisValue` special-value (NaN, Inf, etc) handling when casting from raw/string values to `double` ([#2950 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2950)) +- Internals: + - Use `sealed` classes where possible ([#2942 by Henr1k80](https://github.com/StackExchange/StackExchange.Redis/pull/2942)) + - Add overlapped flushing in `LoggingTunnel` and avoid double-lookups ([#2943 by Henr1k80](https://github.com/StackExchange/StackExchange.Redis/pull/2943)) + +## 2.9.11 + +- Add `HGETDEL`, `HGETEX` and `HSETEX` support ([#2863 by atakavci](https://github.com/StackExchange/StackExchange.Redis/pull/2863)) +- Fix key-prefix omission in `SetIntersectionLength` and `SortedSet{Combine[WithScores]|IntersectionLength}` ([#2863 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2863)) +- Add `Condition.SortedSet[Not]ContainsStarting` condition for transactions ([#2638 by ArnoKoll](https://github.com/StackExchange/StackExchange.Redis/pull/2638)) +- Add support for XPENDING Idle time filter ([#2822 by david-brink-talogy](https://github.com/StackExchange/StackExchange.Redis/pull/2822)) +- Improve `double` formatting performance on net8+ ([#2928 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2928)) +- Add `GetServer(RedisKey, ...)` API ([#2936 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2936)) +- Fix error constructing `StreamAdd` message ([#2941 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2941)) + +## 2.8.58 + +- Fix [#2679](https://github.com/StackExchange/StackExchange.Redis/issues/2679) - blocking call in long-running connects ([#2680 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2680)) +- Support async cancellation of `SCAN` enumeration ([#2911 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2911)) +- Add `XTRIM MINID` support ([#2842 by kijanawoodard](https://github.com/StackExchange/StackExchange.Redis/pull/2842)) +- Add new CE 8.2 stream support - `XDELEX`, `XACKDEL`, `{XADD|XTRIM} [KEEPREF|DELREF|ACKED]` ([#2912 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2912)) +- Fix `ZREVRANGEBYLEX` open-ended commands ([#2636 by ArnoKoll](https://github.com/StackExchange/StackExchange.Redis/pull/2636)) +- Fix `StreamGroupInfo.Lag` when `null` ([#2902 by robhop](https://github.com/StackExchange/StackExchange.Redis/pull/2902)) +- Internals + - Logging improvements ([#2903 by Meir017](https://github.com/StackExchange/StackExchange.Redis/pull/2903) and [#2917 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2917)) + - Update tests to xUnit v3 ([#2907 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2907)) + - Avoid `CLIENT PAUSE` in CI tests ([#2916 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2916)) + +## 2.8.47 + +- Add support for new `BITOP` operations in CE 8.2 ([#2900 by atakavci](https://github.com/StackExchange/StackExchange.Redis/pull/2900)) +- Package updates ([#2906 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2906)) +- Docs: added [guidance on async timeouts](https://stackexchange.github.io/StackExchange.Redis/AsyncTimeouts) ([#2910 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2910)) +- Fix handshake error with `CLIENT ID` ([#2909 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2909)) + +## 2.8.41 + +- Add support for sharded pub/sub via `RedisChannel.Sharded` - ([#2887 by vandyvilla, atakavci and mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2887)) + +## 2.8.37 + +- Add `ConfigurationOptions.SetUserPemCertificate(...)` and `ConfigurationOptions.SetUserPfxCertificate(...)` methods to simplify using client certificates ([#2873 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2873)) +- Add logging for when a Multiplexer reconfigures ([#2864 by st-dev-gh](https://github.com/StackExchange/StackExchange.Redis/pull/2864)) +- Fix: Move `AuthenticateAsClient` to fully async after dropping older framework support, to help client thread starvation in cases TLS negotiation stalls server-side ([#2878 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2878)) + +## 2.8.31 + +- Fix: Respect `IReconnectRetryPolicy` timing in the case that a node that was present disconnects indefinitely ([#2853](https://github.com/StackExchange/StackExchange.Redis/pull/2853) & [#2856](https://github.com/StackExchange/StackExchange.Redis/pull/2856) by NickCraver) + - Special thanks to [sampdei](https://github.com/sampdei) tracking this down and working a fix +- Changes max default retry policy backoff to 60 seconds ([#2853 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2853)) +- Fix [#2652](https://github.com/StackExchange/StackExchange.Redis/issues/2652): Track client-initiated shutdown for any pipe type ([#2814 by bgrainger](https://github.com/StackExchange/StackExchange.Redis/pull/2814)) + +## 2.8.24 + +- Update Envoy command definitions to [allow `UNWATCH`](https://github.com/envoyproxy/envoy/pull/37620) ([#2824 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2824)) + +## 2.8.22 + +- Format IPv6 endpoints correctly when rewriting configration strings ([#2813 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2813)) +- Update default Redis version from `4.0.0` to `6.0.0` for Azure Redis resources ([#2810 by philon-msft](https://github.com/StackExchange/StackExchange.Redis/pull/2810)) +- Detect Azure Managed Redis caches and tune default connection settings for them ([#2818 by philon-msft](https://github.com/StackExchange/StackExchange.Redis/pull/2818)) +- Bump `Microsoft.Bcl.AsyncInterfaces` dependency from `5.0.0` to `6.0.0` ([#2820 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2820)) + +## 2.8.16 + +- Fix: PhysicalBridge: Always perform "last read" check in heartbeat when `HeartbeatConsistencyChecks` is enabled ([#2795 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2795)) + +## 2.8.14 + +- Fix [#2793](https://github.com/StackExchange/StackExchange.Redis/issues/2793): Update Envoyproxy's command map according to latest Envoy documentation ([#2794 by dbarbosapn](https://github.com/StackExchange/StackExchange.Redis/pull/2794)) + +## 2.8.12 + +- Add support for hash field expiration (see [#2715](https://github.com/StackExchange/StackExchange.Redis/issues/2715)) ([#2716 by atakavci](https://github.com/StackExchange/StackExchange.Redis/pull/2716])) +- Add support for `HSCAN NOVALUES` (see [#2721](https://github.com/StackExchange/StackExchange.Redis/issues/2721)) ([#2722 by atakavci](https://github.com/StackExchange/StackExchange.Redis/pull/2722)) +- Fix [#2763](https://github.com/StackExchange/StackExchange.Redis/issues/2763): Make ConnectionMultiplexer.Subscription thread-safe ([#2769 by Chuck-EP](https://github.com/StackExchange/StackExchange.Redis/pull/2769)) +- Fix [#2778](https://github.com/StackExchange/StackExchange.Redis/issues/2778): Run `CheckInfoReplication` even with `HeartbeatConsistencyChecks` ([#2784 by NickCraver and leachdaniel-clark](https://github.com/StackExchange/StackExchange.Redis/pull/2784)) + +## 2.8.0 + +- Add high-integrity mode ([docs](https://stackexchange.github.io/StackExchange.Redis/Configuration), [#2471 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2741)) +- TLS certificate/`TrustIssuer`: Check EKU in X509 chain checks when validating certificates ([#2670 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2670)) + +## 2.7.33 + +- **Potentially Breaking**: Fix `CheckTrustedIssuer` certificate validation for broken chain scenarios ([#2665 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2665)) + - Users inadvertently trusting a remote cert with a broken chain could not be failing custom validation before this change. This is only in play if you are using `ConfigurationOptions.TrustIssuer` at all. +- Add new `LoggingTunnel` API; see [https://stackexchange.github.io/StackExchange.Redis/RespLogging](https://stackexchange.github.io/StackExchange.Redis/RespLogging) ([#2660 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2660)) +- Fix [#2664](https://github.com/StackExchange/StackExchange.Redis/issues/2664): Move ProcessBacklog to fully sync to prevent thread pool hopping and blocking on awaits ([#2667 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2667)) + +## 2.7.27 + +- Support `HeartbeatConsistencyChecks` and `HeartbeatInterval` in `Clone()` ([#2658 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2658)) +- Add `AddLibraryNameSuffix` to multiplexer; allows usage-specific tokens to be appended *after connect* ([#2659 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2659)) + +## 2.7.23 + +- Fix [#2653](https://github.com/StackExchange/StackExchange.Redis/issues/2653): Client library metadata should validate contents ([#2654 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2654)) +- Add `HeartbeatConsistencyChecks` option (opt-in) to enabled per-heartbeat (defaults to once per second) checks to be sent to ensure no network stream corruption has occurred ([#2656 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2656)) + +## 2.7.20 + +- Fix [#2642](https://github.com/StackExchange/StackExchange.Redis/issues/2642): Detect and support multi-DB pseudo-cluster/proxy scenarios ([#2646](https://github.com/StackExchange/StackExchange.Redis/pull/2646) by mgravell) + +## 2.7.17 + +- Fix [#2321](https://github.com/StackExchange/StackExchange.Redis/issues/2321): Honor disposition of select command in Command Map for transactions [(#2322 by slorello89)](https://github.com/StackExchange/StackExchange.Redis/pull/2322) +- Fix [#2619](https://github.com/StackExchange/StackExchange.Redis/issues/2619): Type-forward `IsExternalInit` to support down-level TFMs ([#2621 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2621)) +- `InternalsVisibleTo` `PublicKey` enhancements([#2623 by WeihanLi](https://github.com/StackExchange/StackExchange.Redis/pull/2623)) +- Fix [#2576](https://github.com/StackExchange/StackExchange.Redis/issues/2576): Prevent `NullReferenceException` during shutdown of connections ([#2629 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2629)) + +## 2.7.10 + +- Fix [#2593](https://github.com/StackExchange/StackExchange.Redis/issues/2593): `EXPIRETIME` and `PEXPIRETIME` miscategorized as `PrimaryOnly` commands causing them to fail when issued against a read-only replica ([#2593 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2593)) +- Fix [#2591](https://github.com/StackExchange/StackExchange.Redis/issues/2591): Add `HELLO` to Sentinel connections so they can support RESP3 ([#2601 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2601)) +- Fix [#2595](https://github.com/StackExchange/StackExchange.Redis/issues/2595): Add detection handling for dead sockets that the OS says are okay, seen especially in Linux environments ([#2610 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2610)) + +## 2.7.4 + +- Adds: RESP3 support ([#2396 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2396)) - see https://stackexchange.github.io/StackExchange.Redis/Resp3 +- Fix [#2507](https://github.com/StackExchange/StackExchange.Redis/issues/2507): Pub/sub with multi-item payloads should be usable ([#2508 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2508)) +- Add: connection-id tracking (internal only, no public API) ([#2508 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2508)) +- Add: `ConfigurationOptions.LoggerFactory` for logging to an `ILoggerFactory` (e.g. `ILogger`) all connection and error events ([#2051 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2051)) +- Fix [#2467](https://github.com/StackExchange/StackExchange.Redis/issues/2467): Add StreamGroupInfo EntriesRead and Lag ([#2510 by tvdias](https://github.com/StackExchange/StackExchange.Redis/pull/2510)) + +## 2.6.122 + +- Change: Target net6.0 instead of net5.0, since net5.0 is end of life. ([#2497 by eerhardt](https://github.com/StackExchange/StackExchange.Redis/pull/2497)) +- Fix: Fix nullability annotation of IConnectionMultiplexer.RegisterProfiler ([#2494 by eerhardt](https://github.com/StackExchange/StackExchange.Redis/pull/2494)) +- Fix [#2520](https://github.com/StackExchange/StackExchange.Redis/issues/2520): Improve cluster connections in down scenarios by not re-pinging successful nodes ([#2525 by Matiszak](https://github.com/StackExchange/StackExchange.Redis/pull/2525)) +- Add: `Timer.ActiveCount` under `POOL` in timeout messages on .NET 6+ to help diagnose timer overload affecting timeout evaluations ([#2500 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2500)) +- Add: `LibraryName` configuration option; allows the library name to be controlled at the individual options level (in addition to the existing controls in `DefaultOptionsProvider`) ([#2502 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2502)) +- Add: `DefaultOptionsProvider.GetProvider` allows lookup of provider by endpoint ([#2502 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2502)) + +## 2.6.116 + +- Fix [#2479](https://github.com/StackExchange/StackExchange.Redis/issues/2479): Add `RedisChannel.UseImplicitAutoPattern` (global) and `RedisChannel.IsPattern` ([#2480 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2480)) +- Fix [#2479](https://github.com/StackExchange/StackExchange.Redis/issues/2479): Mark `RedisChannel` conversion operators as obsolete; add `RedisChannel.Literal` and `RedisChannel.Pattern` helpers ([#2481 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2481)) +- Fix [#2449](https://github.com/StackExchange/StackExchange.Redis/issues/2449): Update `Pipelines.Sockets.Unofficial` to `v2.2.8` to support native AOT ([#2456 by eerhardt](https://github.com/StackExchange/StackExchange.Redis/pull/2456)) + +## 2.6.111 + +- Fix [#2426](https://github.com/StackExchange/StackExchange.Redis/issues/2426): Don't restrict multi-slot operations on Envoy proxy; let the proxy decide ([#2428 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2428)) +- Add: Support for `User`/`Password` in `DefaultOptionsProvider` to support token rotation scenarios ([#2445 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2445)) +- Fix [#2449](https://github.com/StackExchange/StackExchange.Redis/issues/2449): Resolve AOT trim warnings in `TryGetAzureRoleInstanceIdNoThrow` ([#2451 by eerhardt](https://github.com/StackExchange/StackExchange.Redis/pull/2451)) +- Adds: Support for `HTTP/1.1 200 Connection established` in HTTP Tunnel ([#2448 by flobernd](https://github.com/StackExchange/StackExchange.Redis/pull/2448)) +- Adds: Timeout duration to backlog timeout error messages ([#2452 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2452)) +- Adds: `DefaultOptionsProvider.LibraryName` for specifying lib-name passed to `CLIENT SETINFO` in Redis 7.2+ ([#2453 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2453)) + +## 2.6.104 + +- Fix [#2412](https://github.com/StackExchange/StackExchange.Redis/issues/2412): Critical (but rare) GC bug that can lead to async tasks never completing if the multiplexer is not held by the consumer ([#2408 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2408)) +- Add: Better error messages (over generic timeout) when commands are backlogged and unable to write to any connection ([#2408 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2408)) +- Fix [#2392](https://github.com/StackExchange/StackExchange.Redis/issues/2392): Dequeue *all* timed out messages from the backlog when not connected (including Fire+Forget) ([#2397 by kornelpal](https://github.com/StackExchange/StackExchange.Redis/pull/2397)) +- Fix [#2400](https://github.com/StackExchange/StackExchange.Redis/issues/2400): Expose `ChannelMessageQueue` as `IAsyncEnumerable` ([#2402 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2402)) +- Add: Support for `CLIENT SETINFO` (lib name/version) during handshake; opt-out is via `ConfigurationOptions`; also support read of `resp`, `lib-ver` and `lib-name` via `CLIENT LIST` ([#2414 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2414)) +- Documentation: clarify the meaning of `RedisValue.IsInteger` re [#2418](https://github.com/StackExchange/StackExchange.Redis/issues/2418) ([#2420 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2420)) + +## 2.6.96 + +- Fix [#2350](https://github.com/StackExchange/StackExchange.Redis/issues/2350): Properly parse lua script parameters in all cultures ([#2351 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2351)) +- Fix [#2362](https://github.com/StackExchange/StackExchange.Redis/issues/2362): Set `RedisConnectionException.FailureType` to `AuthenticationFailure` on all authentication scenarios for better handling ([#2367 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2367)) +- Fix [#2368](https://github.com/StackExchange/StackExchange.Redis/issues/2368): Support `RedisValue.Length()` for all storage types ([#2370 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2370)) +- Fix [#2376](https://github.com/StackExchange/StackExchange.Redis/issues/2376): Avoid a (rare) deadlock scenario ([#2378 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2378)) + +## 2.6.90 + +- Adds: Support for `EVAL_RO` and `EVALSHA_RO` via `IDatabase.ScriptEvaluateReadOnly`/`IDatabase.ScriptEvaluateReadOnlyAsync` ([#2168 by shacharPash](https://github.com/StackExchange/StackExchange.Redis/pull/2168)) +- Fix [#1458](https://github.com/StackExchange/StackExchange.Redis/issues/1458): Fixes a leak condition when a connection completes on the TCP phase but not the Redis handshake ([#2238 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2238)) +- Internal: ServerSnapshot: Improve API and allow filtering with custom struct enumerator ([#2337 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2337)) + + +## 2.6.86 + +- Fix [#1520](https://github.com/StackExchange/StackExchange.Redis/issues/1520) & [#1660](https://github.com/StackExchange/StackExchange.Redis/issues/1660): When `MOVED` is encountered from a cluster, a reconfigure will happen proactively to react to cluster changes ASAP ([#2286 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2286)) +- Fix [#2249](https://github.com/StackExchange/StackExchange.Redis/issues/2249): Properly handle a `fail` state (new `ClusterNode.IsFail` property) for `CLUSTER NODES` and expose `fail?` as a property (`IsPossiblyFail`) as well ([#2288 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2288)) +- Adds: `IConnectionMultiplexer.ServerMaintenanceEvent` (was on `ConnectionMultiplexer` but not the interface) ([#2306 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2306)) +- Adds: To timeout messages, additional debug information: `Sync-Ops` (synchronous operations), `Async-Ops` (asynchronous operations), and `Server-Connected-Seconds` (how long the connection in question has been connected, or `"n/a"`) ([#2300 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2300)) + +## 2.6.80 + +- Adds: `last-in` and `cur-in` (bytes) to timeout exceptions to help identify timeouts that were just-behind another large payload off the wire ([#2276 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2276)) +- Adds: general-purpose tunnel support, with HTTP proxy "connect" support included ([#2274 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2274)) +- Removes: Package dependency (`System.Diagnostics.PerformanceCounter`) ([#2285 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2285)) + + +## 2.6.70 + +- Fix: `MOVED` with `NoRedirect` (and other non-reachable errors) should respect the `IncludeDetailInExceptions` setting ([#2267 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2267)) +- Fix [#2251](https://github.com/StackExchange/StackExchange.Redis/issues/2251) & [#2265](https://github.com/StackExchange/StackExchange.Redis/issues/2265): Cluster endpoint connections weren't proactively connecting subscriptions in all cases and taking the full connection timeout to complete as a result ([#2268 by iteplov](https://github.com/StackExchange/StackExchange.Redis/pull/2268)) + + +## 2.6.66 + +- Fix [#2182](https://github.com/StackExchange/StackExchange.Redis/issues/2182): Be more flexible in which commands are "primary only" in order to support users with replicas that are explicitly configured to allow writes ([#2183 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2183)) +- Adds: `IConnectionMultiplexer` now implements `IAsyncDisposable` ([#2161 by kimsey0](https://github.com/StackExchange/StackExchange.Redis/pull/2161)) +- Adds: `IConnectionMultiplexer.GetServers()` to get all `IServer` instances for a multiplexer ([#2203 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2203)) +- Fix [#2016](https://github.com/StackExchange/StackExchange.Redis/issues/2016): Align server selection with supported commands (e.g. with writable servers) to reduce `Command cannot be issued to a replica` errors ([#2191 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2191)) +- Performance: Optimization around timeout processing to reduce lock contention in the case of many items that haven't yet timed out during a heartbeat ([#2217 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2217)) +- Fix [#2223](https://github.com/StackExchange/StackExchange.Redis/issues/2223): Resolve sync-context issues (missing `ConfigureAwait(false)`) ([#2229 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2229)) +- Fix [#1968](https://github.com/StackExchange/StackExchange.Redis/issues/1968): Improved handling of EVAL scripts during server restarts and failovers, detecting and re-sending the script for a retry when needed ([#2170 by martintmk](https://github.com/StackExchange/StackExchange.Redis/pull/2170)) +- Adds: `ConfigurationOptions.SslClientAuthenticationOptions` (`netcoreapp3.1`/`net5.0`+ only) to give more control over SSL/TLS authentication ([#2224 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2224)) +- Fix [#2240](https://github.com/StackExchange/StackExchange.Redis/pull/2241): Improve support for DNS-based IPv6 endpoints ([#2241 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2241)) +- Adds: `ConfigurationOptions.HeartbeatInterval` (**Advanced Setting** - [see docs](https://stackexchange.github.io/StackExchange.Redis/Configuration#configuration-options)) To allow more finite control of the client heartbeat, which encompases how often command timeouts are actually evaluated - still defaults to 1,000 ms ([#2243 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2243)) +- Fix [#1879](https://github.com/StackExchange/StackExchange.Redis/issues/1879): Improve exception message when the wrong password is used ([#2246 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2246)) +- Fix [#2233](https://github.com/StackExchange/StackExchange.Redis/issues/2233): Repeated connection to Sentinel servers using the same ConfigurationOptions would fail ([#2242 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2242)) + + +## 2.6.48 + +- URGENT Fix: [#2167](https://github.com/StackExchange/StackExchange.Redis/issues/2167), [#2176](https://github.com/StackExchange/StackExchange.Redis/issues/2176): fix error in batch/transaction handling that can result in out-of-order instructions ([#2177 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2177)) +- Fix: [#2164](https://github.com/StackExchange/StackExchange.Redis/issues/2164): fix `LuaScript.Prepare` for scripts that don't have parameters ([#2166 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2166)) + +## 2.6.45 + +- Adds: [Nullable reference type](https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references) annotations ([#2041 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2041)) + - Adds annotations themselves for nullability to everything in the library + - Fixes a few internal edge cases that will now throw proper errors (rather than a downstream null reference) + - Fixes inconsistencies with `null` vs. empty array returns (preferring an not-null empty array in those edge cases) + - Note: does *not* increment a major version (as these are warnings to consumers), because: they're warnings (errors are opt-in), removing obsolete types with a 3.0 rev _would_ be binary breaking (this isn't), and reving to 3.0 would cause binding redirect pain for consumers. Bumping from 2.5 to 2.6 only for this change. +- Adds: Support for `COPY` with `.KeyCopy()`/`.KeyCopyAsync()` ([#2064 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2064)) +- Adds: Support for `LMOVE` with `.ListMove()`/`.ListMoveAsync()` ([#2065 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2065)) +- Adds: Support for `ZRANDMEMBER` with `.SortedSetRandomMember()`/`.SortedSetRandomMemberAsync()`, `.SortedSetRandomMembers()`/`.SortedSetRandomMembersAsync()`, and `.SortedSetRandomMembersWithScores()`/`.SortedSetRandomMembersWithScoresAsync()` ([#2076 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2076)) +- Adds: Support for `SMISMEMBER` with `.SetContains()`/`.SetContainsAsync()` ([#2077 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2077)) +- Adds: Support for `ZDIFF`, `ZDIFFSTORE`, `ZINTER`, `ZINTERCARD`, and `ZUNION` with `.SortedSetCombine()`/`.SortedSetCombineAsync()`, `.SortedSetCombineWithScores()`/`.SortedSetCombineWithScoresAsync()`, and `.SortedSetIntersectionLength()`/`.SortedSetIntersectionLengthAsync()` ([#2075 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2075)) +- Adds: Support for `SINTERCARD` with `.SetIntersectionLength()`/`.SetIntersectionLengthAsync()` ([#2078 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2078)) +- Adds: Support for `LPOS` with `.ListPosition()`/`.ListPositionAsync()` and `.ListPositions()`/`.ListPositionsAsync()` ([#2080 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2080)) +- Adds: Support for `ZMSCORE` with `.SortedSetScores()`/.`SortedSetScoresAsync()` ([#2082 by ttingen](https://github.com/StackExchange/StackExchange.Redis/pull/2082)) +- Adds: Support for `NX | XX | GT | LT` to `EXPIRE`, `EXPIREAT`, `PEXPIRE`, and `PEXPIREAT` with `.KeyExpire()`/`.KeyExpireAsync()` ([#2083 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2083)) +- Adds: Support for `EXPIRETIME`, and `PEXPIRETIME` with `.KeyExpireTime()`/`.KeyExpireTimeAsync()` ([#2083 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2083)) +- Fix: For streams, properly hash `XACK`, `XCLAIM`, and `XPENDING` in cluster scenarios to eliminate `MOVED` retries ([#2085 by nielsderdaele](https://github.com/StackExchange/StackExchange.Redis/pull/2085)) +- Adds: Support for `OBJECT REFCOUNT` with `.KeyRefCount()`/`.KeyRefCountAsync()` ([#2087 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2087)) +- Adds: Support for `OBJECT ENCODING` with `.KeyEncoding()`/`.KeyEncodingAsync()` ([#2088 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2088)) +- Adds: Support for `GEOSEARCH` with `.GeoSearch()`/`.GeoSearchAsync()` ([#2089 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2089)) +- Adds: Support for `GEOSEARCHSTORE` with `.GeoSearchAndStore()`/`.GeoSearchAndStoreAsync()` ([#2089 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2089)) +- Adds: Support for `HRANDFIELD` with `.HashRandomField()`/`.HashRandomFieldAsync()`, `.HashRandomFields()`/`.HashRandomFieldsAsync()`, and `.HashRandomFieldsWithValues()`/`.HashRandomFieldsWithValuesAsync()` ([#2090 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2090)) +- Adds: Support for `LMPOP` with `.ListLeftPop()`/`.ListLeftPopAsync()` and `.ListRightPop()`/`.ListRightPopAsync()` ([#2094 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2094)) +- Adds: Support for `ZMPOP` with `.SortedSetPop()`/`.SortedSetPopAsync()` ([#2094 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2094)) +- Adds: Support for `XAUTOCLAIM` with `.StreamAutoClaim()`/.`StreamAutoClaimAsync()` and `.StreamAutoClaimIdsOnly()`/.`StreamAutoClaimIdsOnlyAsync()` ([#2095 by ttingen](https://github.com/StackExchange/StackExchange.Redis/pull/2095)) +- Fix [#2071](https://github.com/StackExchange/StackExchange.Redis/issues/2071): Add `.StringSet()`/`.StringSetAsync()` overloads for source compat broken for 1 case in 2.5.61 ([#2098 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2098)) +- Fix [#2086](https://github.com/StackExchange/StackExchange.Redis/issues/2086): Correct HashSlot calculations for `XREAD` and `XREADGROUP` commands ([#2093 by nielsderdaele](https://github.com/StackExchange/StackExchange.Redis/pull/2093)) +- Adds: Support for `LCS` with `.StringLongestCommonSubsequence()`/`.StringLongestCommonSubsequence()`, `.StringLongestCommonSubsequenceLength()`/`.StringLongestCommonSubsequenceLengthAsync()`, and `.StringLongestCommonSubsequenceWithMatches()`/`.StringLongestCommonSubsequenceWithMatchesAsync()` ([#2104 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2104)) +- Adds: Support for `OBJECT FREQ` with `.KeyFrequency()`/`.KeyFrequencyAsync()` ([#2105 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2105)) +- Performance: Avoids allocations when computing cluster hash slots or testing key equality ([#2110 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2110)) +- Adds: Support for `SORT_RO` with `.Sort()`/`.SortAsync()` ([#2111 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2111)) +- Adds: Support for `BIT | BYTE` to `BITCOUNT` and `BITPOS` with `.StringBitCount()`/`.StringBitCountAsync()` and `.StringBitPosition()`/`.StringBitPositionAsync()` ([#2116 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2116)) +- Adds: Support for pub/sub payloads that are unary arrays ([#2118 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2118)) +- Fix: Sentinel timer race during dispose ([#2133 by ewisuri](https://github.com/StackExchange/StackExchange.Redis/pull/2133)) +- Adds: Support for `GT`, `LT`, and `CH` on `ZADD` with `.SortedSetAdd()`/`.SortedSetAddAsync()` and `.SortedSetUpdate()`/`.SortedSetUpdateAsync()` ([#2136 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2136)) +- Adds: Support for `COMMAND COUNT`, `COMMAND GETKEYS`, and `COMMAND LIST`, with `.CommandCount()`/`.CommandCountAsync()`, `.CommandGetKeys()`/`.CommandGetKeysAsync()`, and `.CommandList()`/`.CommandListAsync()` ([#2143 by shacharPash](https://github.com/StackExchange/StackExchange.Redis/pull/2143)) + +## 2.5.61 + +- Adds: `GETEX` support with `.StringGetSetExpiry()`/`.StringGetSetExpiryAsync()` ([#1743 by benbryant0](https://github.com/StackExchange/StackExchange.Redis/pull/1743)) +- Fix [#1988](https://github.com/StackExchange/StackExchange.Redis/issues/1988): Don't issue `SELECT` commands if explicitly disabled ([#2023 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2023)) +- Adds: `KEEPTTL` support on `SET` operations ([#2029 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2029)) +- Fix: Allow `XTRIM` `MAXLEN` argument to be `0` ([#2030 by NicoAvanzDev](https://github.com/StackExchange/StackExchange.Redis/pull/2030)) +- Adds: `ConfigurationOptions.BeforeSocketConnect` for configuring sockets between creation and connection ([#2031 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2031)) +- Fix [#1813](https://github.com/StackExchange/StackExchange.Redis/issues/1813): Don't connect to endpoints we failed to parse ([#2042 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2042)) +- Fix: `ClientKill`/`ClientKillAsync` when using `ClientType` ([#2048 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2048)) +- Adds: Most `ConfigurationOptions` changes after `ConnectionMultiplexer` connections will now be respected, e.g. changing a timeout will work and changing a password for auth rotation would be used at the next reconnect ([#2050 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2050)) + - **Obsolete**: This change also moves `ConnectionMultiplexer.IncludeDetailInExceptions` and `ConnectionMultiplexer.IncludePerformanceCountersInExceptions` to `ConfigurationOptions`. The old properties are `[Obsolete]` proxies that work until 3.0 for compatibility. +- Adds: Support for `ZRANGESTORE` with `.SortedSetRangeAndStore()`/`.SortedSetRangeAndStoreAsync()` ([#2052 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2052)) + +## 2.5.43 + +- Adds: Bounds checking for `ExponentialRetry` backoff policy ([#1921 by gliljas](https://github.com/StackExchange/StackExchange.Redis/pull/1921)) +- Adds: `DefaultOptionsProvider` support for endpoint-based defaults configuration ([#1987 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1987)) +- Adds: Envoy proxy support ([#1989 by rkarthick](https://github.com/StackExchange/StackExchange.Redis/pull/1989)) +- Performance: When `SUBSCRIBE` is disabled, give proper errors and connect faster ([#2001 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2001)) +- Adds: `GET` on `SET` command support (present in Redis 6.2+ - [#2003 by martinekvili](https://github.com/StackExchange/StackExchange.Redis/pull/2003)) +- Performance: Improves concurrent load performance when backlogs are utilized ([#2008 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2008)) +- Stability: Improves cluster connections when `CLUSTER` command is disabled ([#2014 by tylerohlsen](https://github.com/StackExchange/StackExchange.Redis/pull/2014)) +- Logging: Improves connection logging and adds overall timing to it ([#2019 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2019)) + +## 2.5.27 (prerelease) + +- Adds: a backlog/retry mechanism for commands issued while a connection isn't available ([#1912 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1912)) + - Commands will be queued if a multiplexer isn't yet connected to a Redis server. + - Commands will be queued if a connection is lost and then sent to the server when the connection is restored. + - All commands queued will only remain in the backlog for the duration of the configured timeout. + - To revert to previous behavior, a new `ConfigurationOptions.BacklogPolicy` is available - old behavior is configured via `options.BacklogPolicy = BacklogPolicy.FailFast`. This backlogs nothing and fails commands immediately if no connection is available. +- Adds: Makes `StreamEntry` constructor public for better unit test experience ([#1923 by WeihanLi](https://github.com/StackExchange/StackExchange.Redis/pull/1923)) +- Fix: Integer overflow error (issue #1926) with 2GiB+ result payloads ([#1928 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/1928)) +- Change: Update assumed redis versions to v2.8 or v4.0 in the Azure case ([#1929 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1929)) +- Fix: Profiler showing `EVAL` instead `EVALSHA` ([#1930 by martinpotter](https://github.com/StackExchange/StackExchange.Redis/pull/1930)) +- Performance: Moved tiebreaker fetching in connections into the handshake phase (streamline + simplification) ([#1931 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1931)) +- Stability: Fixed potential disposed object usage around Arenas (pulling in [Piplines.Sockets.Unofficial#63](https://github.com/mgravell/Pipelines.Sockets.Unofficial/pull/63) by MarcGravell) +- Adds: Thread pool work item stats to exception messages to help diagnose contention ([#1964 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1964)) +- Fix/Performance: Overhauls pub/sub implementation for correctness ([#1947 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1947)) + - Fixes a race in subscribing right after connected + - Fixes a race in subscribing immediately before a publish + - Fixes subscription routing on clusters (spreading instead of choosing 1 node) + - More correctly reconnects subscriptions on connection failures, including to other endpoints +- Adds "(vX.X.X)" version suffix to the default client ID so server-side `CLIENT LIST` can more easily see what's connected ([#1985 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1985)) +- Fix: Properly including or excluding key names on some message failures ([#1990 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1990)) +- Fix: Correct return of nil results in `LPOP`, `RPOP`, `SRANDMEMBER`, and `SPOP` ([#1993 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1993)) + +## 2.2.88 + +- Change: Connection backoff default is now exponential instead of linear ([#1896 by lolodi](https://github.com/StackExchange/StackExchange.Redis/pull/1896)) +- Adds: Support for `NodeMaintenanceScaleComplete` event (handles Redis cluster scaling) ([#1902 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1902)) + +## 2.2.79 + +- NRediSearch: Support on json index ([#1808 by AvitalFineRedis](https://github.com/StackExchange/StackExchange.Redis/pull/1808)) +- NRediSearch: Support sortable TagFields and unNormalizedForm for Tag & Text Fields ([#1862 by slorello89 & AvitalFineRedis](https://github.com/StackExchange/StackExchange.Redis/pull/1862)) +- Fix: Potential errors getting socket bytes ([#1836 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1836)) +- Logging: Adds (.NET Version and timestamps) for better debugging ([#1796 by philon-msft](https://github.com/StackExchange/StackExchange.Redis/pull/1796)) +- Adds: `Condition` APIs (transactions), now supports `StreamLengthEqual` and variants ([#1807 by AlphaGremlin](https://github.com/StackExchange/StackExchange.Redis/pull/1807)) +- Adds: Support for count argument to `ListLeftPop`, `ListLeftPopAsync`, `ListRightPop`, and `ListRightPopAsync` ([#1850 by jjfmarket](https://github.com/StackExchange/StackExchange.Redis/pull/1850)) +- Fix: Potential task/thread exhaustion from the backlog processor ([#1854 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/1854)) +- Adds: Support for listening to Azure Maintenance Events ([#1876 by amsoedal](https://github.com/StackExchange/StackExchange.Redis/pull/1876)) +- Adds: `StringGetDelete`/`StringGetDeleteAsync` APIs for Redis `GETDEL` command([#1840 by WeihanLi](https://github.com/StackExchange/StackExchange.Redis/pull/1840)) + +## 2.2.62 + +- Stability: Sentinel potential memory leak fix in OnManagedConnectionFailed handler ([#1710 by alexSatov](https://github.com/StackExchange/StackExchange.Redis/pull/1710)) +- Fix: `GetOutstandingCount` could obscure underlying faults by faulting itself ([#1792 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/1792)) +- Fix [#1719](https://github.com/StackExchange/StackExchange.Redis/issues/1791): With backlog messages becoming reordered ([#1779 by TimLovellSmith](https://github.com/StackExchange/StackExchange.Redis/pull/1779)) + +## 2.2.50 + +- Performance: Optimization for PING accuracy ([#1714 by eduardobr](https://github.com/StackExchange/StackExchange.Redis/pull/1714)) +- Fix: Improvement to reconnect logic (exponential backoff) ([#1735 by deepakverma](https://github.com/StackExchange/StackExchange.Redis/pull/1735)) +- Adds: Refresh replica endpoint list on failover ([#1684 by laurauzcategui](https://github.com/StackExchange/StackExchange.Redis/pull/1684)) +- Fix: `ReconfigureAsync` re-entrancy (caused connection issues) ([#1772 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1772)) +- Fix: `ReconfigureAsync` Sentinel race resulting in NoConnectionAvailable when using DemandMaster ([#1773 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1773)) +- Stability: Resolve race in AUTH and other connection reconfigurations ([#1759 by TimLovellSmith and NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1759)) + +## 2.2.4 + +- Fix: Ambiguous signature of the new `RPUSHX`/`LPUSHX` methods ([#1620 by stefanloerwald](https://github.com/StackExchange/StackExchange.Redis/pull/1620)) + +## 2.2.3 + +- Adds: .NET 5 target +- Fix: Mutex race condition ([#1585 by arsnyder16](https://github.com/StackExchange/StackExchange.Redis/pull/1585)) +- Adds: `CheckCertificateRevocation` can be controlled via the config string ([#1591 by lwlwalker](https://github.com/StackExchange/StackExchange.Redis/pull/1591)) +- Fix: Range end-value inversion ([#1573 by tombatron](https://github.com/StackExchange/StackExchange.Redis/pull/1573)) +- Adds: `ROLE` support ([#1551 by zmj](https://github.com/StackExchange/StackExchange.Redis/pull/1551)) +- Adds: varadic `RPUSHX`/`LPUSHX` support ([#1557 by dmytrohridin](https://github.com/StackExchange/StackExchange.Redis/pull/1557)) +- Fix: Server-selection strategy race condition ([#1532 by deepakverma](https://github.com/StackExchange/StackExchange.Redis/pull/1532)) +- Fix: Sentinel default port ([#1525 by ejsmith](https://github.com/StackExchange/StackExchange.Redis/pull/1525)) +- Fix: `Int64` parse scenario ([#1568 by arsnyder16](https://github.com/StackExchange/StackExchange.Redis/pull/1568)) +- Add: Force replication check during failover ([#1563 by aravindyeduvaka & joroda](https://github.com/StackExchange/StackExchange.Redis/pull/1563)) +- Documentation tweaks (multiple) +- Fix: Backlog contention issue ([#1612 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/1612/), see also [#1574 by devbv](https://github.com/StackExchange/StackExchange.Redis/pull/1574/)) + +## 2.1.58 + +- Fix: `[*]SCAN` - fix possible NRE scenario if the iterator is disposed with an incomplete operation in flight +- Fix: `[*]SCAN` - treat the cursor as an opaque value whenever possible, for compatibility with `redis-cluster-proxy` +- Adds: `[*]SCAN` - include additional exception data in the case of faults + +## 2.1.55 + +- Adds: Identification of assembly binding problem on .NET Framework. Drops `System.IO.Pipelines` to 4.7.1, and identifies new `System.Buffers` binding failure on 4.7.2 + +## 2.1.50 + +- Adds: Bind directly to sentinel-managed instances from a configuration string/object ([#1431 by ejsmith](https://github.com/StackExchange/StackExchange.Redis/pull/1431)) +- Adds: `last-delivered-id` to `StreamGroupInfo` ([#1477 by AndyPook](https://github.com/StackExchange/StackExchange.Redis/pull/1477)) +- Change: Update naming of replication-related commands to reflect Redis 5 naming ([#1488 by mgravell](https://github.com/StackExchange/StackExchange.Redis/issues/1488) & [#945 by mgravell](https://github.com/StackExchange/StackExchange.Redis/issues/945)) +- Fix [#1460](https://github.com/StackExchange/StackExchange.Redis/issues/1460): `IServer` commands that are database-specific (`DBSIZE`, `FLUSHDB`, `KEYS`, `SCAN`) now respect the default database on the config ([#1468 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/1468)) +- Library updates + +## 2.1.39 + +- Fix: Mutex around connection was not "fair"; in specific scenario could lead to out-of-order commands ([#1440 by kennygea](https://github.com/StackExchange/StackExchange.Redis/pull/1440)) +- Fix [#1432](https://github.com/StackExchange/StackExchange.Redis/issues/1432): Update dependencies +- Fix: Timing error on linux ([#1433 by pengweiqhca](https://github.com/StackExchange/StackExchange.Redis/pull/1433)) +- Fix: Add `auth` to command-map for Sentinel ([#1428 by ejsmith](https://github.com/StackExchange/StackExchange.Redis/pull/1428)) + +## 2.1.30 + +- Build: Fix deterministic builds ([#1420 by @mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/1420)) + +## 2.1.28 + +- Fix: Stability in new sentinel APIs +- Fix [#1407](https://github.com/StackExchange/StackExchange.Redis/issues/1407): Include `SslProtocolos` in `ConfigurationOptions.ToString()` ([#1408 by vksampath and Sampath Vuyyuru](https://github.com/StackExchange/StackExchange.Redis/pull/1408)) +- Fix: Clarify messaging around disconnected multiplexers ([#1396 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1396)) +- Change: Tweak methods of new sentinel API (this is technically a breaking change, but since this is a new API that was pulled quickly, we consider this to be acceptable) +- Adds: New thread `SocketManager` mode (opt-in) to always use the regular thread-pool instead of the dedicated pool +- Adds: Improved counters in/around error messages +- Adds: New `User` property on `ConfigurationOptions` +- Build: Enable deterministic builds (note: this failed; fixed in 2.1.30) + +## 2.1.0 + +- Fix: Ensure active-message is cleared ([#1374 by hamish-omny](https://github.com/StackExchange/StackExchange.Redis/pull/1374)) +- Adds: Sentinel support ([#1067 by shadim](https://github.com/StackExchange/StackExchange.Redis/pull/1067), [#692 by lexxdark](https://github.com/StackExchange/StackExchange.Redis/pull/692)) +- Adds: `IAsyncEnumerable` scanning APIs now supported ([#1087 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/1087)) +- Adds: New API for use with misbehaving sync-contexts ([more info](https://stackexchange.github.io/StackExchange.Redis/ThreadTheft)) +- Adds: `TOUCH` support ([#1291 by gkorland](https://github.com/StackExchange/StackExchange.Redis/pull/1291)) +- Adds: `Condition` API (transactions) now supports `SortedSetLengthEqual` ([#1332 by phosphene47](https://github.com/StackExchange/StackExchange.Redis/pull/1332)) +- Adds: `SocketManager` is now more configurable ([#1115 by naile](https://github.com/StackExchange/StackExchange.Redis/pull/1115)) +- Adds: NRediSearch updated in line with JRediSearch ([#1267 by tombatron](https://github.com/StackExchange/StackExchange.Redis/pull/1267), [#1199 by oruchreis](https://github.com/StackExchange/StackExchange.Redis/pull/1199)) +- Adds: Support for `CheckCertificatRevocation` configuration ([#1234 by BLun78 and V912736](https://github.com/StackExchange/StackExchange.Redis/pull/1234)) +- Adds: More details about exceptions ([#1190 by marafiq](https://github.com/StackExchange/StackExchange.Redis/pull/1190)) +- Adds: Updated `StreamCreateConsumerGroup` methods to use the `MKSTREAM` option ([#1141 via ttingen](https://github.com/StackExchange/StackExchange.Redis/pull/1141)) +- Adds: Support for NOACK in the StreamReadGroup methods ([#1154 by ttingen](https://github.com/StackExchange/StackExchange.Redis/pull/1154)) +- Adds: Event-args now mockable ([#1326 by n1l](https://github.com/StackExchange/StackExchange.Redis/pull/1326)) +- Fix: No-op when adding 0 values to a set ([#1283 by omeaart](https://github.com/StackExchange/StackExchange.Redis/pull/1283)) +- Adds: Support for `LATENCY` and `MEMORY` ([#1204 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/1204)) +- Adds: Support for `HSTRLEN` ([#1241 by eitanhs](https://github.com/StackExchange/StackExchange.Redis/pull/1241)) +- Adds: `GeoRadiusResult` is now mockable ([#1175 by firenero](https://github.com/StackExchange/StackExchange.Redis/pull/1175)) +- Fix: Various documentation fixes ([#1162 by SnakyBeaky](https://github.com/StackExchange/StackExchange.Redis/pull/1162), [#1135 by ttingen](https://github.com/StackExchange/StackExchange.Redis/pull/1135), [#1203 by caveman-dick](https://github.com/StackExchange/StackExchange.Redis/pull/1203), [#1240 by Excelan](https://github.com/StackExchange/StackExchange.Redis/pull/1240), [#1245 by francoance](https://github.com/StackExchange/StackExchange.Redis/pull/1245), [#1159 by odyth](https://github.com/StackExchange/StackExchange.Redis/pull/1159), [#1311 by DillonAd](https://github.com/StackExchange/StackExchange.Redis/pull/1311), [#1339 by vp89](https://github.com/StackExchange/StackExchange.Redis/pull/1339), [#1336 by ERGeorgiev](https://github.com/StackExchange/StackExchange.Redis/issues/1336)) +- Fix: Rare race-condition around exception data ([#1342 by AdamOutcalt](https://github.com/StackExchange/StackExchange.Redis/pull/1342)) +- Fix: `ScriptEvaluateAsync` keyspace isolation ([#1377 by gliljas](https://github.com/StackExchange/StackExchange.Redis/pull/1377)) +- Fix: F# compatibility enhancements ([#1386 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1386)) +- Fix: Improved `ScriptResult` null support ([#1392 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/1392)) +- Fix: Error with DNS resolution breaking endpoint iterator ([#1393 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/1393)) +- Tests: Better docker support for tests ([#1389 by ejsmith](https://github.com/StackExchange/StackExchange.Redis/pull/1389), [#1391 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1391)) +- Tests: General test improvements ([#1183 by mtreske](https://github.com/StackExchange/StackExchange.Redis/issues/1183), [#1385 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1385), [#1384 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1384)) + +## 2.0.601 + +- Adds: Tracking for current and next messages to help with debugging timeout issues - helpful in cases of large pipeline blockers + +## 2.0.600 + +- Adds: `ulong` support to `RedisValue` and `RedisResult` ([#1104 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/1104)) +- Fix: Remove odd equality: `"-" != 0` (we do, however, still allow `"-0"`, as that is at least semantically valid, and is logically `== 0`) (related to [#1103](https://github.com/StackExchange/StackExchange.Redis/issues/1103)) +- Performance: Rework how pub/sub queues are stored - reduces delegate overheads (related to [#1101](https://github.com/StackExchange/StackExchange.Redis/issues/1101)) +- Fix [#1108](https://github.com/StackExchange/StackExchange.Redis/issues/1108): Ensure that we don't try appending log data to the `TextWriter` once we've returned from a method that accepted one + +## 2.0.593 + +- Performance: Unify spin-wait usage on sync/async paths to one competitor +- Fix [#1101](https://github.com/StackExchange/StackExchange.Redis/issues/1101): When a `ChannelMessageQueue` is involved, unsubscribing *via any route* should still unsubscribe and mark the queue-writer as complete + +## 2.0.588 + +- Stability/Performance: Resolve intermittent stall in the write-lock that could lead to unexpected timeouts even when at low/reasonable (but concurrent) load + +## 2.0.571 + +- Performance: Use new [arena allocation API](https://mgravell.github.io/Pipelines.Sockets.Unofficial/docs/arenas) to avoid `RawResult[]` overhead +- Performance: Massively simplified how `ResultBox` is implemented, in particular to reduce `TaskCompletionSource` allocations +- Performance: Fix sync-over-async issue with async call paths, and fix the [SemaphoreSlim](https://blog.marcgravell.com/2019/02/fun-with-spiral-of-death.html) problems that this uncovered +- Performance: Reintroduce the unsent backlog queue, in particular to improve async performance +- Performance: Simplify how completions are reactivated, so that external callers use their originating pool, not the dedicated IO pools (prevent thread stealing) +- Fix: Update `Pipelines.Sockets.Unofficial` to prevent issue with incorrect buffer re-use in corner-case +- Fix: `KeyDeleteAsync` could, in some cases, always use `DEL` (instead of `UNLINK`) +- Fix: Last unanswered write time was incorrect +- Change: Use higher `Pipe` thresholds when sending + +## 2.0.519 + +- Fix [#1007](https://github.com/StackExchange/StackExchange.Redis/issues/1007): Adapt to late changes in the RC streams API ([#983 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/983)) +- Documentation fixes ([#997 by MerelyRBLX](https://github.com/StackExchange/StackExchange.Redis/pull/997), [#1005 by zBrianW](https://github.com/StackExchange/StackExchange.Redis/pull/1005)) +- Build: Switch to SDK 2.1.500 + +## 2.0.513 + +- Fix [#961](https://github.com/StackExchange/StackExchange.Redis/issues/962): fix assembly binding redirect problems; IMPORTANT: this drops to an older `System.Buffers` version - if you have manually added redirects for `4.0.3.0`, you may need to manually update to `4.0.2.0` (or remove completely) +- Fix [#962](https://github.com/StackExchange/StackExchange.Redis/issues/962): Avoid NRE in edge-case when fetching bridge + +## 2.0.505 + +- Fix [#943](https://github.com/StackExchange/StackExchange.Redis/issues/943): Ensure transaction inner tasks are completed prior to completing the outer transaction task +- Fix [#946](https://github.com/StackExchange/StackExchange.Redis/issues/946): Reinstate missing `TryParse` methods on `RedisValue` +- Fix [#940](https://github.com/StackExchange/StackExchange.Redis/issues/940): Off-by-one on pre-boxed integer cache (NRediSearch) + +## 2.0.495 + +2.0 is a large - and breaking - change. The key focus of this release is stability and reliability. + +- **Hard Break**: The package identity has changed; instead of `StackExchange.Redis` (not strong-named) and `StackExchange.Redis.StrongName` (strong-named), we are now + only releasing `StackExchange.Redis` (strong-named). This is a binary breaking change that requires consumers to be re-compiled; it cannot be applied via binding-redirects +- **Hard Break**: The platform targets have been rationalized - supported targets are .NETStandard 2.0 (and above), .NETFramework 4.6.1 (and above), and .NETFramework 4.7.2 (and above) + (note - the last two are mainly due to assembly binding problems) +- **Hard Break**: The profiling API has been overhauled and simplified; full documentation is [provided here](https://stackexchange.github.io/StackExchange.Redis/Profiling_v2.html) +- **Soft Break**: The `PreserveAsyncOrder` behaviour of the pub/sub API has been deprecated; a *new* API has been provided for scenarios that require in-order pub/sub handling - + the `Subscribe` method has a new overload *without* a handler parameter which returns a `ChannelMessageQueue`, which provides `async` ordered access to messages) +- Internal: The network architecture has moved to use `System.IO.Pipelines`; this has allowed us to simplify and unify a lot of the network code, and in particular fix a lot of problems relating to how the library worked with TLS and/or .NETStandard +- Change: As a result of the `System.IO.Pipelines` change, the error-reporting on timeouts is now much simpler and clearer; the [timeouts documentation](Timeouts.md) has been updated +- Removed: The `HighPriority` (queue-jumping) flag is now deprecated +- Internal: Most buffers internally now make use of pooled memory; `RedisValue` no longer preemptively allocates buffers +- Internal: Added new custom thread-pool for handling async continuations to avoid thread-pool starvation issues +- Internal: All IL generation has been removed; the library should now work on platforms that do not allow runtime-emit +- Adds: asynchronous operations now have full support for reporting timeouts +- Adds: new APIs now exist to work with pooled memory without allocations - `RedisValue.CreateFrom(MemoryStream)` and `operator` support for `Memory` and `ReadOnlyMemory`; and `IDatabase.StringGetLease[Async](...)`, `IDatabase.HashGetLease[Async](...)`, `Lease.AsStream()`) +- Adds: ["streams"](https://redis.io/topics/streams-intro) support (thanks to [ttingen](https://github.com/ttingen) for their contribution) +- Adds: Various missing commands / overloads have been added; `Execute[Async]` for additional commands is now available on `IServer` +- Fix: A *lot* of general bugs and issues have been resolved +- **Break**: `RedisValue.TryParse` was accidentally omitted in the overhaul; this has been rectified and will be available in the next build + +A more complete list of issues addressed can be seen in [this tracking issue](https://github.com/StackExchange/StackExchange.Redis/issues/871) + +Note: we currently have no plans to do an additional `1.*` release. In particular, even though there was a `1.2.7-alpha` build on nuget, we *do not* currently have plans to release `1.2.7`. + +--- ## 1.2.6 -- fix change to `cluster nodes` output when using cluster-enabled target and 4.0+ (see [redis #4186](https://github.com/antirez/redis/issues/4186) +- Change: `cluster nodes` output when using cluster-enabled target and 4.0+ (see [redis #4186](https://github.com/antirez/redis/issues/4186) ## 1.2.5 -- critical fix: "poll mode" was disabled in the build for net45/net60 - impact: IO jams and lack of reader during high load +- (Critical) Fix: "poll mode" was disabled in the build for `net45`/`net46` - Impact: IO jams and lack of reader during high load ## 1.2.4 -- fix: incorrect build configuration (#649) +- Fix: Incorrect build configuration ([#649 by jrlost](https://github.com/StackExchange/StackExchange.Redis/issues/649)) ## 1.2.3 -- fix: when using `redis-cluster` with multiple replicas, use round-robin when selecting replica (#610) -- add: can specify `NoScriptCache` flag when using `ScriptEvaluate` to bypass all cache features (always uses `EVAL` instead of `SCRIPT LOAD` and `EVALSHA`) (#617) +- Fix: When using `redis-cluster` with multiple replicas, use round-robin when selecting replica ([#610 by mgravell](https://github.com/StackExchange/StackExchange.Redis/issues/610)) +- Adds: Can specify `NoScriptCache` flag when using `ScriptEvaluate` to bypass all cache features (always uses `EVAL` instead of `SCRIPT LOAD` and `EVALSHA`) ([#617 by Funbit](https://github.com/StackExchange/StackExchange.Redis/issues/617)) -## 1.2.2 (preview): +## 1.2.2 (preview) -- **UNAVAILABLE**: .NET 4.0 support is not in this build, due to [a build issue](https://github.com/dotnet/cli/issues/5993) - looking into solutions -- add: make performance-counter tracking opt-in (`IncludePerformanceCountersInExceptions`) as it was causing problems (#587) -- add: can now specifiy allowed SSL/TLS protocols (#603) -- add: track message status in exceptions (#576) -- add: `GetDatabase()` optimization for DB 0 and low numbered databases: `IDatabase` instance is retained and recycled (as long as no `asyncState` is provided) -- improved connection retry policy (#510, #572) -- add `Execute`/`ExecuteAsync` API to support "modules"; [more info](http://blog.marcgravell.com/2017/04/stackexchangeredis-and-redis-40-modules.html) -- fix: timeout link fixed re /docs change (below) +- **Break**: .NET 4.0 support is not in this build, due to [a build issue](https://github.com/dotnet/cli/issues/5993) - looking into solutions +- Adds: Make performance-counter tracking opt-in (`IncludePerformanceCountersInExceptions`) as it was causing problems ([#587 by AlexanderKot](https://github.com/StackExchange/StackExchange.Redis/issues/587)) +- Adds: Can now specifiy allowed SSL/TLS protocols ([#603 by JonCole](https://github.com/StackExchange/StackExchange.Redis/pull/603)) +- Adds: Track message status in exceptions ([#576 by deepakverma](https://github.com/StackExchange/StackExchange.Redis/pull/576)) +- Adds: `GetDatabase()` optimization for DB 0 and low numbered databases: `IDatabase` instance is retained and recycled (as long as no `asyncState` is provided) +- Performance: Improved connection retry policy ([#510 by deepakverma](https://github.com/StackExchange/StackExchange.Redis/pull/510), [#572 by deepakverma](https://github.com/StackExchange/StackExchange.Redis/pull/572)) +- Adds: `Execute`/`ExecuteAsync` API to support "modules"; [more info](https://blog.marcgravell.com/2017/04/stackexchangeredis-and-redis-40-modules.html) +- Fix: Timeout link fixed re /docs change (below) - [`NRediSearch`](https://www.nuget.org/packages/NRediSearch/) added as exploration into "modules" - -Other changes (not library related) - -- (project) refactor /docs for github pages -- improve release note tracking -- rework build process to use csproj +- Other changes (not library related) + - Change: Refactor /docs for github pages + - Change: Improve release note tracking + - Build: Rework build process to use csproj ## 1.2.1 -- fix: avoid overlapping per-endpoint heartbeats - -## 1.2.0 - -- (same as 1.2.0-alpha1) +- Fix: Avoid overlapping per-endpoint heartbeats -## 1.2.0-alpha1 +## 1.2.0 (same as 1.2.0-alpha1) -- add: GEO commands (#489) -- add: ZADD support for new NX/XX switches (#520) -- add: core-clr preview support improvements +- Adds: GEO commands ([#489 by wjdavis5](https://github.com/StackExchange/StackExchange.Redis/pull/489)) +- Adds: ZADD support for new NX/XX switches ([#520 by seniorquico](https://github.com/StackExchange/StackExchange.Redis/pull/520)) +- Adds: core-clr preview support improvements ## 1.1.608 -- fix: bug with race condition in servers indexer (related: 1.1.606) +- Fix: Bug with race condition in servers indexer (related: 1.1.606) ## 1.1.607 -- fix: ensure socket-mode polling is enabled (.net) +- Fix: Ensure socket-mode polling is enabled (.net) ## 1.1.606 -- fix: bug with race condition in servers indexer +- Fix: Bug with race condition in servers indexer -## and the rest +## ...and the rest -(I'm happy to take PRs for change history going back in time) \ No newline at end of file +(We're happy to take PRs for change history going back in time or any fixes here!) diff --git a/docs/Resp3.md b/docs/Resp3.md new file mode 100644 index 000000000..126b460f4 --- /dev/null +++ b/docs/Resp3.md @@ -0,0 +1,45 @@ +# RESP3 and StackExchange.Redis + +RESP2 and RESP3 are evolutions of the Redis protocol, with RESP3 existing from Redis server version 6 onwards (v7.2+ for Redis Enterprise). The main differences are: + +1. RESP3 can carry out-of-band / "push" messages on a single connection, where-as RESP2 requires a separate connection for these messages +2. RESP3 can (when appropriate) convey additional semantic meaning about returned payloads inside the same result structure +3. Some commands (see [this topic](https://github.com/redis/redis-doc/issues/2511)) return different result structures in RESP3 mode; for example a flat interleaved array might become a jagged array + +For most people, #1 is the main reason to consider RESP3, as in high-usage servers - this can halve the number of connections required. +This is particularly useful in hosted environments where the number of inbound connections to the server is capped as part of a service plan. +Alternatively, where users are currently choosing to disable the out-of-band connection to achieve this, they may now be able to re-enable this +(for example, to receive server maintenance notifications) *without* incurring any additional connection overhead. + +Because of the significance of #3 (and to avoid breaking your code), the library does not currently default to RESP3 mode. This must be enabled explicitly +via `ConfigurationOptions.Protocol` or by adding `,protocol=resp3` (or `,protocol=3`) to the configuration string. + +--- + +#3 is a critical one - the library *should* already handle all documented commands that have revised results in RESP3, but if you're using +`Execute[Async]` to issue ad-hoc commands, you may need to update your processing code to compensate for this, ideally using detection to handle +*either* format so that the same code works in both REP2 and RESP3. Since the impacted commands are handled internally by the library, in reality +this should not usually present a difficulty. + +The minor (#2) and major (#3) differences to results are only visible to your code when using: + +- Lua scripts invoked via the `ScriptEvaluate[Async](...)` or related APIs, that either: + - Uses the `redis.setresp(3)` API and returns a value from `redis.[p]call(...)` + - Returns a value that satisfies the [LUA to RESP3 type conversion rules](https://redis.io/docs/manual/programmability/lua-api/#lua-to-resp3-type-conversion) +- Ad-hoc commands (in particular: *modules*) that are invoked via the `Execute[Async](string command, ...)` API + +...both which return `RedisResult`. **If you are not using these APIs, you should not need to do anything additional.** + +Historically, you could use the `RedisResult.Type` property to query the type of data returned (integer, string, etc). In particular: + +- Two new properties are added: `RedisResult.Resp2Type` and `RedisResult.Resp3Type` + - The `Resp3Type` property exposes the new semantic data (when using RESP3) - for example, it can indicate that a value is a double-precision number, a boolean, a map, etc (types that did not historically exist) + - The `Resp2Type` property exposes the same value that *would* have been returned if this data had been returned over RESP2 + - The `Type` property is now marked obsolete, but functions identically to `Resp2Type`, so that pre-existing code (for example, that has a `switch` on the type) is not impacted by RESP3 +- The `ResultType.MultiBulk` is superseded by `ResultType.Array` (this is a nomenclature change only; they are the same value and function identically) + +Possible changes required due to RESP3: + +1. To prevent build warnings, replace usage of `ResultType.MultiBulk` with `ResultType.Array`, and usage of `RedisResult.Type` with `RedisResult.Resp2Type` +2. If you wish to exploit the additional semantic data when enabling RESP3, use `RedisResult.Resp3Type` where appropriate +3. If you are enabling RESP3, you must verify whether the commands you are using can give different result shapes on RESP3 connections \ No newline at end of file diff --git a/docs/RespLogging.md b/docs/RespLogging.md new file mode 100644 index 000000000..3ff3b0164 --- /dev/null +++ b/docs/RespLogging.md @@ -0,0 +1,150 @@ +Logging and validating the underlying RESP stream +=== + +Sometimes (rarely) there is a question over the validity of the RESP stream from a server (especially when using proxies +or a "redis-like-but-not-actually-redis" server), and it is hard to know whether the *data sent* was bad, vs +the client library tripped over the data. + +To help with this, an experimental API exists to help log and validate RESP streams. This API is not intended +for routine use (and may change at any time), but can be useful for diagnosing problems. + +For example, consider we have the following load test which (on some setup) causes a failure with some +degree of reliability (even if you need to run it 6 times to see a failure): + +``` c# +// connect +Console.WriteLine("Connecting..."); +var options = ConfigurationOptions.Parse(ConnectionString); +await using var muxer = await ConnectionMultiplexer.ConnectAsync(options); +var db = muxer.GetDatabase(); + +// load +RedisKey testKey = "marc_abc"; +await db.KeyDeleteAsync(testKey); +Console.WriteLine("Writing..."); +for (int i = 0; i < 100; i++) +{ + // sync every 50 iterations (pipeline the rest) + var flags = (i % 50) == 0 ? CommandFlags.None : CommandFlags.FireAndForget; + await db.SetAddAsync(testKey, Guid.NewGuid().ToString(), flags); +} + +// fetch +Console.WriteLine("Reading..."); +int count = 0; +for (int i = 0; i < 10; i++) +{ + // this is deliberately not using SCARD + // (to put load on the inbound) + count += (await db.SetMembersAsync(testKey)).Length; +} +Console.WriteLine("all done"); +``` + +## Logging RESP streams + +When this fails, it will not be obvious exactly who is to blame. However, we can ask for the data streams +to be logged to the local file-system. + +**Obviously, this may leave data on disk, so this may present security concerns if used with production data; use +this feature sparingly, and clean up after yourself!** + +``` c# +// connect +Console.WriteLine("Connecting..."); +var options = ConfigurationOptions.Parse(ConnectionString); +LoggingTunnel.LogToDirectory(options, @"C:\Code\RedisLog"); // <=== added! +await using var muxer = await ConnectionMultiplexer.ConnectAsync(options); +... +``` + +This API is marked `[Obsolete]` simply to discourage usage, but you can ignore this warning once you +understand what it is saying (using `#pragma warning disable CS0618` if necessary). + +This will update the `ConfigurationOptions` with a custom `Tunnel` that performs file-based mirroring +of the RESP streams. If `Ssl` is enabled on the `ConfigurationOptions`, the `Tunnel` will *take over that responsibility* +(so that the unencrypted data can be logged), and will *disable* `Ssl` on the `ConfigurationOptions` - but TLS +will still be used correctly. + +If we run our code, we will see that 2 files are written per connection ("in" and "out"); if you are using RESP2 (the default), +then 2 connections are usually established (one for regular "interactive" commands, and one for pub/sub messages), so this will +typically create 4 files. + +## Validating RESP streams + +RESP is *mostly* text, so a quick eyeball can be achieved using any text tool; an "out" file will typically start: + +``` txt +$6 +CLIENT +$7 +SETNAME +... +``` + +and an "in" file will typically start: + +``` txt ++OK ++OK ++OK +... +``` + +This is the start of the handshakes for identifying the client to the redis server, and the server acknowledging this (if +you have authentication enabled, there will be a `AUTH` command first, or `HELLO` on RESP3). + +If there is a failure, you obviously don't want to manually check these files. Instead, an API exists to validate RESP streams: + +``` c# +var messages = await LoggingTunnel.ValidateAsync(@"C:\Code\RedisLog"); +Console.WriteLine($"{messages} RESP fragments validated"); +``` + +If the RESP streams are *not* valid, an exception will provide further details. + +**An exception here is strong evidence that there is a fault either in the redis server, or an intermediate proxy**. + +Conversely, if the library reported a protocol failure but the validation step here *does not* report an error, then +that is strong evidence of a library error; [**please report this**](https://github.com/StackExchange/StackExchange.Redis/issues/new) (with details). + +You can also *replay* the conversation locally, seeing the individual requests and responses: + +``` c# +var messages = await LoggingTunnel.ReplayAsync(@"C:\Code\RedisLog", (cmd, resp) => +{ + if (cmd.IsNull) + { + // out-of-band/"push" response + Console.WriteLine("<< " + LoggingTunnel.DefaultFormatResponse(resp)); + } + else + { + Console.WriteLine(" > " + LoggingTunnel.DefaultFormatCommand(cmd)); + Console.WriteLine(" < " + LoggingTunnel.DefaultFormatResponse(resp)); + } +}); +Console.WriteLine($"{messages} RESP commands validated"); +``` + +The `DefaultFormatCommand` and `DefaultFormatResponse` methods are provided for convenience, but you +can perform your own formatting logic if required. If a RESP erorr is encountered in the response to +a particular message, the callback will still be invoked to indicate that error. For example, after deliberately +introducing an error into the captured file, we might see: + +``` txt + > CLUSTER NODES + < -ERR This instance has cluster support disabled + > GET __Booksleeve_TieBreak + < (null) + > ECHO ... + < -Invalid bulk string terminator +Unhandled exception. StackExchange.Redis.RedisConnectionException: Invalid bulk string terminator +``` + +The `-ERR` message is not a problem - that's normal and simply indicates that this is not a redis cluster; however, the +final pair is an `ECHO` request, for which the corresponding response was invalid. This information is useful for finding +out what happened. + +Emphasis: this API is not intended for common/frequent usage; it is intended only to assist validating the underlying +RESP stream. \ No newline at end of file diff --git a/docs/Scripting.md b/docs/Scripting.md index e04b2d19a..56af7afc1 100644 --- a/docs/Scripting.md +++ b/docs/Scripting.md @@ -1,24 +1,23 @@ Scripting === -Basic [Lua scripting](http://redis.io/commands/EVAL) is supported by the `IServer.ScriptLoad(Async)`, `IServer.ScriptExists(Async)`, `IServer.ScriptFlush(Async)`, `IDatabase.ScriptEvaluate`, and `IDatabaseAsync.ScriptEvaluateAsync` methods. +Basic [Lua scripting](https://redis.io/commands/EVAL) is supported by the `IServer.ScriptLoad(Async)`, `IServer.ScriptExists(Async)`, `IServer.ScriptFlush(Async)`, `IDatabase.ScriptEvaluate`, and `IDatabaseAsync.ScriptEvaluateAsync` methods. These methods expose the basic commands necessary to submit and execute Lua scripts to redis. -More sophisticated scripting is available through the `LuaScript` class. The `LuaScript` class makes it simpler to prepare and submit parameters along with a script, as well as allowing you to use -cleaner variables names. +More sophisticated scripting is available through the `LuaScript` class. The `LuaScript` class makes it simpler to prepare and submit parameters along with a script, as well as allowing you to use cleaner variables names. An example use of the `LuaScript`: -``` - const string Script = "redis.call('set', @key, @value)"; +```csharp +const string Script = "redis.call('set', @key, @value)"; - using (ConnectionMultiplexer conn = /* init code */) - { - var db = conn.GetDatabase(0); +using (ConnectionMultiplexer conn = /* init code */) +{ + var db = conn.GetDatabase(0); - var prepared = LuaScript.Prepare(Script); - db.ScriptEvaluate(prepared, new { key = (RedisKey)"mykey", value = 123 }); - } + var prepared = LuaScript.Prepare(Script); + db.ScriptEvaluate(prepared, new { key = (RedisKey)"mykey", value = 123 }); +} ``` The `LuaScript` class rewrites variables in scripts of the form `@myVar` into the appropriate `ARGV[someIndex]` required by redis. If the @@ -36,24 +35,25 @@ Any object that exposes field or property members with the same name as @-prefix - RedisKey - RedisValue +StackExchange.Redis handles Lua script caching internally. It automatically transmits the Lua script to redis on the first call to 'ScriptEvaluate'. For further calls of the same script only the hash with [`EVALSHA`](https://redis.io/commands/evalsha) is used. -To avoid retransmitting the Lua script to redis each time it is evaluated, `LuaScript` objects can be converted into `LoadedLuaScript`s via `LuaScript.Load(IServer)`. -`LoadedLuaScripts` are evaluated with the [`EVALSHA`](http://redis.io/commands/evalsha), and referred to by hash. +For more control of the Lua script transmission to redis, `LuaScript` objects can be converted into `LoadedLuaScript`s via `LuaScript.Load(IServer)`. +`LoadedLuaScripts` are evaluated with the [`EVALSHA`](https://redis.io/commands/evalsha), and referred to by hash. An example use of `LoadedLuaScript`: -``` - const string Script = "redis.call('set', @key, @value)"; +```csharp +const string Script = "redis.call('set', @key, @value)"; - using (ConnectionMultiplexer conn = /* init code */) - { - var db = conn.GetDatabase(0); - var server = conn.GetServer(/* appropriate parameters*/); +using (ConnectionMultiplexer conn = /* init code */) +{ + var db = conn.GetDatabase(0); + var server = conn.GetServer(/* appropriate parameters*/); - var prepared = LuaScript.Prepare(Script); - var loaded = prepared.Load(server); - loaded.Evaluate(db, new { key = (RedisKey)"mykey", value = 123 }); - } + var prepared = LuaScript.Prepare(Script); + var loaded = prepared.Load(server); + loaded.Evaluate(db, new { key = (RedisKey)"mykey", value = 123 }); +} ``` All methods on both `LuaScript` and `LoadedLuaScript` have Async alternatives, and expose the actual script submitted to redis as the `ExecutableScript` property. diff --git a/docs/Server.md b/docs/Server.md new file mode 100644 index 000000000..a0777c478 --- /dev/null +++ b/docs/Server.md @@ -0,0 +1,29 @@ +Running a Redis Server +=== + +StackExchange.Redis is a client library that connects to an existing redis server. So; how do you *get* a running redis server? The good news is that it isn't tricky. + +## Linux + +The main redis build targets linux, so you can simply download, make, and run redis from there; follow the instructions [here](https://redis.io/download#installation) + +## Windows + +There are multiple ways of running redis on windows: + +- [Memurai](https://www.memurai.com/) : a fully supported, well-maintained port of redis for Windows (this is a commercial product, with a free developer version available, and free trials) + - previous to Memurai, MSOpenTech had a Windows port of linux, but this is no longer maintained and is now very out of date; it is not recommended, but: [here](https://www.nuget.org/packages/redis-64/) +- WSL/WSL2 : on Windows 10+, you can run redis for linux in the Windows Subsystem for Linux; note, however, that WSL may have some significant performance implications, and WSL2 appears as a *different* machine (not the local machine), due to running as a VM + +## Docker + +If you are happy to run redis in a container, [an image is available on Docker Hub](https://hub.docker.com/_/redis/) + +## Cloud + +If you don't want to run your own redis servers, multiple commercial cloud offerings are available, including + +- RedisLabs +- Azure Redis Cache +- AWS ElastiCache for Redis +- GCP Memorystore for Redis diff --git a/docs/ServerMaintenanceEvent.md b/docs/ServerMaintenanceEvent.md new file mode 100644 index 000000000..2f4ba1c29 --- /dev/null +++ b/docs/ServerMaintenanceEvent.md @@ -0,0 +1,67 @@ +# Introducing ServerMaintenanceEvents + +StackExchange.Redis now automatically subscribes to notifications about upcoming maintenance from supported Redis providers. The ServerMaintenanceEvent on the ConnectionMultiplexer raises events in response to notifications about server maintenance, and application code can subscribe to the event to handle connection drops more gracefully during these maintenance operations. + +If you are a Redis vendor and want to integrate support for ServerMaintenanceEvents into StackExchange.Redis, we recommend opening an issue so we can discuss the details. + +## Types of events + +Azure Cache for Redis currently sends the following notifications: +* `NodeMaintenanceScheduled`: Indicates that a maintenance event is scheduled. Can be 10-15 minutes in advance. +* `NodeMaintenanceStarting`: This event gets fired ~20s before maintenance begins +* `NodeMaintenanceStart`: This event gets fired when maintenance is imminent (<5s) +* `NodeMaintenanceFailoverComplete`: Indicates that a replica has been promoted to primary +* `NodeMaintenanceEnded`: Indicates that the node maintenance operation is over + +## Sample code + +The library will automatically subscribe to the pub/sub channel to receive notifications from the server, if one exists. For Azure Redis caches, this is the 'AzureRedisEvents' channel. To plug in your maintenance handling logic, you can pass in an event handler via the `ServerMaintenanceEvent` event on your `ConnectionMultiplexer`. For example: + +```csharp +multiplexer.ServerMaintenanceEvent += (object sender, ServerMaintenanceEvent e) => +{ + if (e is AzureMaintenanceEvent azureEvent && azureEvent.NotificationType == AzureNotificationType.NodeMaintenanceStart) + { + // Take whatever action is appropriate for your application to handle the maintenance operation gracefully. + // This might mean writing a log entry, redirecting traffic away from the impacted Redis server, or + // something entirely different. + } +}; +``` +You can see the schema for the `AzureMaintenanceEvent` class [here](https://github.com/StackExchange/StackExchange.Redis/blob/main/src/StackExchange.Redis/Maintenance/AzureMaintenanceEvent.cs). Note that the library automatically sets the `ReceivedTimeUtc` timestamp when the event is received, so if you see in your logs that `ReceivedTimeUtc` is after `StartTimeUtc`, this may indicate that your connections are under high load. + +## Walking through a sample maintenance event + +1. App is connected to Redis and everything is working fine. +2. Current Time: [16:21:39] -> `NodeMaintenanceScheduled` event is raised, with a `StartTimeUtc` of 16:35:57 (about 14 minutes from current time). + * Note: the start time for this event is an approximation, because we will start getting ready for the update proactively and the node may become unavailable up to 3 minutes sooner. We recommend listening for `NodeMaintenanceStarting` and `NodeMaintenanceStart` for the highest level of accuracy (these are only likely to differ by a few seconds at most). +3. Current Time: [16:34:26] -> `NodeMaintenanceStarting` message is received, and `StartTimeUtc` is 16:34:46, about 20 seconds from the current time. +4. Current Time: [16:34:46] -> `NodeMaintenanceStart` message is received, so we know the node maintenance is about to happen. We break the circuit and stop sending new operations to the Redis connection. (Note: the appropriate action for your application may be different.) StackExchange.Redis will automatically refresh its view of the overall server topology. +5. Current Time: [16:34:47] -> The connection is closed by the Redis server. +6. Current Time: [16:34:56] -> `NodeMaintenanceFailoverComplete` message is received. This tells us that the replica node has promoted itself to primary, so the other node can go offline for maintenance. +7. Current Time [16:34:56] -> The connection to the Redis server is restored. It is safe to send commands again to the connection and all commands will succeed. +8. Current Time [16:37:48] -> `NodeMaintenanceEnded` message is received, with a `StartTimeUtc` of 16:37:48. Nothing to do here if you are talking to the load balancer endpoint (port 6380 or 6379). For clustered servers, you can resume sending readonly workloads to the replica(s). + +## Azure Cache for Redis Maintenance Event details + +#### NodeMaintenanceScheduled event + +`NodeMaintenanceScheduled` events are raised for maintenance scheduled by Azure, up to 15 minutes in advance. This event will not get fired for user-initiated reboots. + +#### NodeMaintenanceStarting event + +`NodeMaintenanceStarting` events are raised ~20 seconds ahead of upcoming maintenance. This means that one of the primary or replica nodes will be going down for maintenance. + +It's important to understand that this does *not* mean downtime if you are using a Standard/Premier SKU cache. If the replica is targeted for maintenance, disruptions should be minimal. If the primary node is the one going down for maintenance, a failover will occur, which will close existing connections going through the load balancer port (6380/6379) or directly to the node (15000/15001). You may want to pause sending write commands until the replica node has assumed the primary role and the failover is complete. + +#### NodeMaintenanceStart event + +`NodeMaintenanceStart` events are raised when maintenance is imminent (within seconds). These messages do not include a `StartTimeUtc` because they are fired immediately before maintenance occurs. + +#### NodeMaintenanceFailoverComplete event + +`NodeMaintenanceFailoverComplete` events are raised when a replica has promoted itself to primary. These events do not include a `StartTimeUtc` because the action has already occurred. + +#### NodeMaintenanceEnded event + +`NodeMaintenanceEnded` events are raised to indicate that the maintenance operation has completed and that the replica is once again available. You do *NOT* need to wait for this event to use the load balancer endpoint, as it is available throughout. However, we included this for logging purposes and for customers who use the replica endpoint in clusters for read workloads. \ No newline at end of file diff --git a/docs/Streams.md b/docs/Streams.md new file mode 100644 index 000000000..4378a528d --- /dev/null +++ b/docs/Streams.md @@ -0,0 +1,184 @@ +Overview +=== + +The [Stream](https://redis.io/topics/streams-intro) data type was added in Redis version 5.0 and it represents an append-only log of messages. All of the [stream related commands](https://redis.io/commands#stream) documented on redis.io have been implemented in the StackExchange.Redis client library. Read the ["Introduction to Redis Streams"](https://redis.io/topics/streams-intro) article for further information on the raw Redis commands and how to work with streams. + +Writing to Streams +=== + +Each message or entry in the stream is represented by the `StreamEntry` type. Each stream entry contains a unique ID and an array of name/value pairs. The name/value pairs are represented by the `NameValueEntry` type. + +Use the following to add a simple message with a single name/value pair to a stream: + +```csharp +var db = redis.GetDatabase(); +var messageId = db.StreamAdd("events_stream", "foo_name", "bar_value"); +// messageId = 1518951480106-0 +``` + +The message ID returned by `StreamAdd` is comprised of the millisecond time when the message was added to the stream and a sequence number. The sequence number is used to prevent ID collisions if two or more messages were created at the same millisecond time. + +Multiple name/value pairs can be written to a stream using the following: + +```csharp +var values = new NameValueEntry[] +{ + new NameValueEntry("sensor_id", "1234"), + new NameValueEntry("temp", "19.8") +}; + +var db = redis.GetDatabase(); +var messageId = db.StreamAdd("sensor_stream", values); +``` + +You also have the option to override the auto-generated message ID by passing your own ID to the `StreamAdd` method. Other optional parameters allow you to trim the stream's length. + +```csharp +db.StreamAdd("events_stream", "foo_name", "bar_value", messageId: "0-1", maxLength: 100); +``` + +Reading from Streams +=== + +Reading from a stream is done by using either the `StreamRead` or `StreamRange` methods. + +```csharp +var messages = db.StreamRead("events_stream", "0-0"); +``` + +The code above will read all messages from the ID `"0-0"` to the end of the stream. You have the option to limit the number of messages returned by using the optional `count` parameter. + +The `StreamRead` method also allows you to read from multiple streams at once: + +```csharp +var streams = db.StreamRead(new StreamPosition[] +{ + new StreamPosition("events_stream", "0-0"), + new StreamPosition("score_stream", "0-0") +}); + +Console.WriteLine($"Stream = {streams.First().Key}"); +Console.WriteLine($"Length = {streams.First().Entries.Length}"); +``` + +You can limit the number of messages returned per stream by using the `countPerStream` optional parameter. + +The `StreamRange` method allows you to return a range of entries within a stream. + +```csharp +var messages = db.StreamRange("events_stream", minId: "-", maxId: "+"); +``` + +The `"-"` and `"+"` special characters indicate the smallest and greatest IDs possible. These values are the default values that will be used if no value is passed for the respective parameter. You also have the option to read the stream in reverse by using the `messageOrder` parameter. The `StreamRange` method also provides the ability to limit the number of entries returned by using the `count` parameter. + +```csharp +var messages = db.StreamRange("events_stream", + minId: "0-0", + maxId: "+", + count: 100, + messageOrder: Order.Descending); +``` + +Stream Information +=== + +The `StreamInfo` method provides the ability to read basic information about a stream: its first and last entry, the stream's length, the number of consumer groups, etc. This information can be used to process a stream in a more efficient manner. + +```csharp +var info = db.StreamInfo("events_stream"); + +Console.WriteLine(info.Length); +Console.WriteLine(info.FirstEntry.Id); +Console.WriteLine(info.LastEntry.Id); +``` + +Consumer Groups +=== + +Using Consumer Groups allows you scale the processing of a stream across multiple workers or consumers. Please read the ["Introduction to Redis Streams"](https://redis.io/topics/streams-intro) article for detailed information on consumer groups. + +The following creates a consumer group and tells Redis from which position within the stream to begin reading. If you call the method prior to first creating the stream, the `StreamCreateConsumerGroup` method will create the stream for you by default. You can override this default behavior by passing `false` for the `createStream` optional parameter. + +```csharp +// Returns true if created, otherwise false. +db.StreamCreateConsumerGroup("events_stream", "events_consumer_group", "$"); +// or +db.StreamCreateConsumerGroup("events_stream", "events_consumer_group", StreamPosition.NewMessages); +``` + +The `"$"` special character means that the consumer group will only read messages that are created after the consumer group is created. If you want to read messages that already exist in the stream, you can provide any position within the stream. + +```csharp +// Begin reading from the first position in the stream. +db.StreamCreateConsumerGroup("events_stream", "events_consumer_group", "0-0"); +``` + +Use the `StreamReadGroup` method to read messages into a consumer. This method accepts a message ID as one of the parameters. When an ID is passed to `StreamReadGroup`, Redis will only return pending messages for the given consumer or, in other words, it will only return messages that were ALREADY read by the consumer. + +To read new messages into a consumer, you use the `">"` special character or `StreamPosition.NewMessages`. The `">"` special character means **read messages never delivered to other consumers**. Note that **consumers** within a consumer group are auto-created the first time they are used when calling the `StreamReadGroup` method. + +```csharp +// Read 5 messages into two consumers. +var consumer_1_messages = db.StreamReadGroup("events_stream", "events_cg", "consumer_1", ">", count: 5); +var consumer_2_messages = db.StreamReadGroup("events_stream", "events_cg", "consumer_2", ">", count: 5); +``` + +Once a message has been read by a consumer its state becomes "pending" for the consumer, no other consumer can read that message via the `StreamReadGroup` method. Pending messages for a consumer can be read by using the `StreamReadGroup` method and by supplying an ID within the range of pending messages for the consumer. + +```csharp +// Read the first pending message for the "consumer_1" consumer. +var message = db.StreamReadGroup("events_stream", "events_cg", "consumer_1", "0-0", count: 1); +``` + +Pending message information can also be retrieved by calling the `StreamPending` and `StreamPendingMessages` methods. `StreamPending` returns high level information about the number of pending messages, the pending messages per consumer, and the highest and lowest pending message IDs. + +```csharp +var pendingInfo = db.StreamPending("events_stream", "events_cg"); + +Console.WriteLine(pendingInfo.PendingMessageCount); +Console.WriteLine(pendingInfo.LowestPendingMessageId); +Console.WriteLine(pendingInfo.HighestPendingMessageId); +Console.WriteLine($"Consumer count: {pendingInfo.Consumers.Length}."); +Console.WriteLine(pendingInfo.Consumers.First().Name); +Console.WriteLine(pendingInfo.Consumers.First().PendingMessageCount); +``` + +Use the `StreamPendingMessages` method to retrieve detailed information about the messages that are pending for a given consumer. + +```csharp +// Read the first pending message for the consumer. +var pendingMessages = db.StreamPendingMessages("events_stream", + "events_cg", + count: 1, + consumerName: "consumer_1", + minId: pendingInfo.LowestPendingMessageId); + +Console.WriteLine(pendingMessages.Single().MessageId); +Console.WriteLine(pendingMessages.Single().IdleTimeInMilliseconds); +``` + +Messages are pending for a consumer until they are acknowledged by calling the `StreamAcknowledge` method. A message is no longer accessible by `StreamReadGroup` after it is acknowledged. + +```csharp +// Returns the number of messages acknowledged. +db.StreamAcknowledge("events_stream", "events_cg", pendingMessage.MessageId); +``` + +The `StreamClaim` method can be used to change ownership of messages consumed by a consumer to a different consumer. + +```csharp +// Change ownership to consumer_2 for the first 5 messages pending for consumer_1. +var pendingMessages = db.StreamPendingMessages("events_stream", + "events_cg", + count: 5, + consumerName: "consumer_1", + minId: "0-0"); + +db.StreamClaim("events_stream", + "events_cg", + claimingConsumer: "consumer_2", + minIdleTimeInMs: 0, + messageIds: pendingMessages.Select(m => m.MessageId).ToArray()); +``` + +There are several other methods used to process streams using consumer groups. Please reference the Streams unit tests for those methods and how they are used. diff --git a/docs/Testing.md b/docs/Testing.md new file mode 100644 index 000000000..52776f3b6 --- /dev/null +++ b/docs/Testing.md @@ -0,0 +1,33 @@ +Testing +=== + +Welcome to documentation for the `StackExchange.Redis` test suite! + +Supported platforms: +- Windows (all tests) +- Other .NET-supported platforms (.NET Core tests) + +The unit and integration tests here are fairly straightforward. There are 2 primary steps: +1. Start the servers + +This can be done either by installing Docker and running `docker compose up` in the `tests\RedisConfigs` folder or by running the `start-all` script in the same folder. Docker is the preferred method. + +2. Run the tests + +Tests default to `127.0.0.1` as their server, however you can override any of the test IPs/hostnames and ports by placing a `TestConfig.json` in the `StackExchange.Redis.Tests\` folder. This file is intentionally in `.gitignore` already, as it's for *your* personal overrides. This is useful for testing local or remote servers, different versions, various ports, etc. + +You can find all the JSON properties at [TestConfig.cs](https://github.com/StackExchange/StackExchange.Redis/blob/main/tests/StackExchange.Redis.Tests/Helpers/TestConfig.cs). An example override (everything not specified being a default) would look like this: +```json +{ + "RunLongRunning": true, + "PrimaryServer": "192.168.0.42", + "PrimaryPort": 12345 +} +``` +Note: if a server isn't specified, the related tests should be skipped as inconclusive. + +### Instructions for Windows +The tests are run (by default) as part of the build. You can simply run this in the repository root: +```cmd +.\build.cmd -BuildNumber local +``` \ No newline at end of file diff --git a/docs/ThreadTheft.md b/docs/ThreadTheft.md new file mode 100644 index 000000000..d5b8e717e --- /dev/null +++ b/docs/ThreadTheft.md @@ -0,0 +1,68 @@ +# Thread Theft + +If you're here because you followed a link in an exception and you just want your code to work, +the short version is: try adding the following *early on* in your application startup: + +```csharp +ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", true); +``` + +and see if that fixes things. If you want more context as to what this is about - keep reading! + +## What is thread theft? + +Behind the scenes, for each connection to redis, StackExchange.Redis keeps a queue of the +commands that we've sent to redis that are awaiting a reply. As each reply comes in we look +at the next pending command (order is preserved, which keeps things simple), and we trigger +the "here's your result" API for that reply. For `async`/`await` code, this then leads to +your "continuation" becoming reactivated, which is how your code comes back to life when +an `await`-ed task gets completed. That's the simple version, but reality is a bit more +nuanced. + +By *default*, when you trigger `TrySetResult` (etc) on a `Task`, the continuations are +invoked *synchronously*, i.e. the thread that is setting the result now goes on immediately +to run whatever it is that your continuation wanted. In our case, that would be very bad +as that would mean that the dedicated reader loop (that is meant to be processing results +from redis) is now running your application logic instead; this is **thread theft**, and +would exhibit as lots of timeouts with `rs: CompletePendingMessage` in the information (`rs` +is the **r**eader **s**tate; you shouldn't often observe it in the `CompletePendingMessage*` +step, because it is meant to be very fast; if you are seeing it often it probably means +that the reader is being hijacked when trying to set results). + +To *avoid* this, we use the +`TaskCreationOptions.RunContinuationsAsynchronously` flag. What *this* does depends a little +on whether you have a `SynchronizationContext`. If you *don't* (common for console applications, +services, etc), then the TPL uses the standard thread-pool mechanisms to schedule the +continuation. If you *do* have a `SynchronizationContext` (common in UI applications +and web-servers), then its `Post` method is used instead; the `Post` method is *meant* to +be an asynchronous dispatch API. But... not all implementations are equal. Some +`SynchronizationContext` implementations treat `Post` as a synchronous invoke. This is true +in particular of `LegacyAspNetSynchronizationContext`, which is what you get if you +configure ASP.NET with: + +``` xml + +``` + +or if you do _not_ have a `` of at least 4.5 (which causes the above to default `true`) like this: + +```xml + +``` + +([citation](https://devblogs.microsoft.com/aspnet/all-about-httpruntime-targetframework)) + +In these scenarios, we would once again end up with the reader being stolen and used for +processing your application logic. This can doom any further `await`s to timeouts, +either temporarily (until the application logic chooses to release the thread), or permanently +(essentially deadlocking yourself). + +To avoid this, the library includes an additional layer of mistrust; specifically, if +the `preventthreadtheft` feature flag is enabled, we will *pre-emptively* queue the +completions on the thread-pool. This is a little less efficient in the *default* case, +but *if and only if* you have a misbehaving `SynchronizationContext`, this is +both appropriate and necessary, and does not represent additional overhead. + +The library will attempt to detect `LegacyAspNetSynchronizationContext` in particular, +but this is not always reliable. The flag is also available for manual use with other +similar scenarios. diff --git a/docs/Timeouts.md b/docs/Timeouts.md index 9bd5b340f..ea9830041 100644 --- a/docs/Timeouts.md +++ b/docs/Timeouts.md @@ -3,50 +3,102 @@ Verify what's the maximum bandwidth supported on your client and on the server where redis-server is hosted. If there are requests that are getting bound by bandwidth, it will take longer for them to complete and thereby can cause timeouts. Similarly, verify you are not getting CPU bound on client or on the server box which would cause requests to be waiting for CPU time and thereby have timeouts. -Are there commands taking long time to process on the redis-server? +Are you experiencing "thread theft" of the reader? --------------- -There can be commands that are taking long time to process on the redis-server causing the request to timeout. Few examples of long running commands are mget with large number of keys, keys * or poorly written lua script. You can run the SlowLog command to see if there are requests taking longer than expected. More details regarding the command can be found [here](https://redis.io/commands/slowlog) +The parameter “`rs`” in the error message tells you the state of the reader; if this is frequently reporting `CompletePendingMessage*`, +it is possible that the reader loop has been hijacked; see [Thread Theft](ThreadTheft) for specific guidance. -Was there a big request preceding several small requests to the Redis that timed out? +Are there commands taking a long time to process on the redis-server? --------------- -The parameter “qs” in the error message tells you how many requests were sent from the client to the server, but have not yet processed a response. For some types of load you might see that this value keeps growing, because StackExchange.Redis uses a single TCP connection and can only read one response at a time. Even though the first operation timed out, it does not stop the data being sent to/from the server, and other requests are blocked until this is finished. Thereby, causing timeouts. One solution is to minimize the chance of timeouts by ensuring that your redis-server cache is large enough for your workload and splitting large values into smaller chunks. Another possible solution is to use a pool of ConnectionMultiplexer objects in your client, and choose the "least loaded" ConnectionMultiplexer when sending a new request. This should prevent a single timeout from causing other requests to also timeout. +There can be commands that are taking a long time to process on the redis-server causing the request to timeout. Few examples of long running commands are mget with large number of keys, keys * or poorly written lua script. You can run [the `SLOWLOG` command](https://redis.io/commands/slowlog) to see if there are requests taking longer than expected. More details regarding the command can be found [here](https://redis.io/commands/slowlog). +Was there a big request preceding several small requests to the Redis that timed out? +--------------- +The parameter “`qs`” in the error message tells you how many requests were sent from the client to the server, but have not yet processed a response. For some types of load you might see that this value keeps growing, because StackExchange.Redis uses a single TCP connection and can only read one response at a time. Even though the first operation timed out, it does not stop the data being sent to/from the server, and other requests are blocked until this is finished. Thereby, causing timeouts. One solution is to minimize the chance of timeouts by ensuring that your redis-server cache is large enough for your workload and splitting large values into smaller chunks. Another possible solution is to use a pool of ConnectionMultiplexer objects in your client, and choose the "least loaded" ConnectionMultiplexer when sending a new request. This should prevent a single timeout from causing other requests to also timeout. -Are you seeing high number of busyio or busyworker threads in the timeout exception? +Are you seeing a high number of busyio or busyworker threads in the timeout exception? --------------- -Let's first understand some details on ThreadPool Growth: -The CLR ThreadPool has two types of threads - "Worker" and "I/O Completion Port" (aka IOCP) threads. +Asynchronous operations in StackExchange.Redis can come back in 3 different ways: - - Worker threads are used when for things like processing `Task.Run(…)` or `ThreadPool.QueueUserWorkItem(…)` methods. These threads are also used by various components in the CLR when work needs to happen on a background thread. - - IOCP threads are used when asynchronous IO happens (e.g. reading from the network). +- IOCP threads are used when asynchronous IO happens (e.g. reading from the network). +- From 2.0 onwards, StackExchange.Redis maintains a dedicated thread-pool that it uses for completing most `async` operations; the error message may include an indication of how many of these workers are currently available - if this is zero, it may suggest that your system is particularly busy with asynchronous operations. +- .NET also has a global thread-pool; if the dedicated thread-pool is failing to keep up, additional work will be offered to the global thread-pool, so the message may include details of the global thread-pool. -The thread pool provides new worker threads or I/O completion threads on demand (without any throttling) until it reaches the "Minimum" setting for each type of thread. By default, the minimum number of threads is set to the number of processors on a system. +The StackExchange.Redis dedicated thread-pool has a fixed size suitable for many common scenarios, which is shared between multiple connection instances (this can be customized by explicitly providing a `SocketManager` when creating a `ConnectionMultiplexer`). In many scenarios when using 2.0 and above, the vast majority of asynchronous operations will be serviced by this dedicated pool. This pool exists to avoid contention, as we've frequently seen cases where the global thread-pool becomes jammed with threads that need redis results to unblock them. -Once the number of existing (busy) threads hits the "minimum" number of threads, the ThreadPool will throttle the rate at which is injects new threads to one thread per 500 milliseconds. This means that if your system gets a burst of work needing an IOCP thread, it will process that work very quickly. However, if the burst of work is more than the configured "Minimum" setting, there will be some delay in processing some of the work as the ThreadPool waits for one of two things to happen +.NET itself provides new global thread pool worker threads or I/O completion threads on demand (without any throttling) until it reaches the "Minimum" setting for each type of thread. By default, the minimum number of threads is set to the number of processors on a system. + +For these .NET-provided global thread pools: once the number of existing (busy) threads hits the "minimum" number of threads, the ThreadPool will throttle the rate at which it injects new threads to one thread per 500 milliseconds. This means that if your system gets a burst of work needing an IOCP thread, it will process that work very quickly. However, if the burst of work is more than the configured "Minimum" setting, there will be some delay in processing some of the work as the ThreadPool waits for one of two things to happen: 1. An existing thread becomes free to process the work 2. No existing thread becomes free for 500ms, so a new thread is created. -Basically, it means that when the number of Busy threads is greater than Min threads, you are likely paying a 500ms delay before network traffic is processed by the application. Also, it is important to note that when an existing thread stays idle for longer than 15 seconds (based on what I remember), it will be cleaned up and this cycle of growth and shrinkage can repeat. +Basically, *if* you're hitting the global thread pool (rather than the dedicated StackExchange.Redis thread-pool) it means that when the number of Busy threads is greater than Min threads, you are likely paying a 500ms delay before network traffic is processed by the application. Also, it is important to note that when an existing thread stays idle for longer than 15 seconds (based on what I remember), it will be cleaned up and this cycle of growth and shrinkage can repeat. + +If we look at an example error message from StackExchange.Redis 2.0, you will see that it now prints ThreadPool statistics (see IOCP and WORKER details below). + + Timeout performing GET MyKey (1000ms), inst: 2, qs: 6, in: 0, mgr: 9 of 10 available, + IOCP: (Busy=6,Free=994,Min=4,Max=1000), + WORKER: (Busy=3,Free=997,Min=4,Max=1000) + +In the above example, there are 6 operations currently awaiting replies from redis ("`qs`"), there are 0 bytes waiting to be read from the input stream from redis ("`in`"), and the dedicated thread-pool is almost fully available to service asynchronous completions ("`mgr`"). You can also see that for IOCP thread there are 6 busy threads and the system is configured to allow 4 minimum threads. -If we look at an example error message from StackExchange.Redis (build 1.0.450 or later), you will see that it now prints ThreadPool statistics (see IOCP and WORKER details below). +In 1.*, the information is similar but slightly different: - System.TimeoutException: Timeout performing GET MyKey, inst: 2, mgr: Inactive, - queue: 6, qu: 0, qs: 6, qc: 0, wr: 0, wq: 0, in: 0, ar: 0, - IOCP: (Busy=6,Free=994,Min=4,Max=1000), + System.TimeoutException: Timeout performing GET MyKey, inst: 2, mgr: Inactive, + queue: 6, qu: 0, qs: 6, qc: 0, wr: 0, wq: 0, in: 0, ar: 0, + IOCP: (Busy=6,Free=994,Min=4,Max=1000), WORKER: (Busy=3,Free=997,Min=4,Max=1000) -In the above example, you can see that for IOCP thread there are 6 busy threads and the system is configured to allow 4 minimum threads. In this case, the client would have likely seen two 500 ms delays because 6 > 4. +It may seem contradictory that there are *less* numbers in 2.0 - this is because the 2.0 code has been redesigned not to require some additional steps. + +Note that StackExchange.Redis can hit timeouts if either the IOCP threads or the worker threads (.NET global thread-pool, or the dedicated thread-pool) become saturated without the ability to grow. -Note that StackExchange.Redis can hit timeouts if growth of either IOCP or WORKER threads gets throttled. +Also note that the IOCP and WORKER threads will not be shown on .NET Core if using `netstandard` < 2.0. + +Note that You shouldn't need a much fine-tuning of this from 2.0, since the dedicated thread-pool should be servicing most of the load. Recommendation: -Given the above information, it's recommend to set the minimum configuration value for IOCP and WORKER threads to something larger than the default value. We can't give one-size-fits-all guidance on what this value should be because the right value for one application will be too high/low for another application. This setting can also impact the performance of other parts of complicated applications, so you need to fine-tune this setting to your specific needs. A good starting place is 200 or 300, then test and tweak as needed. +Given the above information, in 1.* it's recommend to set the minimum configuration value for IOCP and WORKER threads to something larger than the default value. We can't give one-size-fits-all guidance on what this value should be because the right value for one application will be too high/low for another application. This setting can also impact the performance of other parts of complicated applications, so you need to fine-tune this setting to your specific needs. A good starting place is 200 or 300, then test and tweak as needed. How to configure this setting: - - In ASP.NET, use the ["minIoThreads" configuration setting](https://msdn.microsoft.com/en-us/library/vstudio/7w2sway1(v=vs.100).aspx) under the `` configuration element in `machine.config`. You could set your server's `machine.config` to allow this overridable per apppool via the method indicated [here](https://stackoverflow.com/questions/1939230/asp-net-processmodel-configuration-optimization#comment2882249_1939245) and set this per Web.Config. Alternativly you can be able to set this programmatically (see below) from your Application_Start method in `Global.asax.cs`. + - In ASP.NET, use the ["minIoThreads" configuration setting](https://msdn.microsoft.com/en-us/library/7w2sway1(v=vs.100).aspx) under the `` configuration element in `machine.config`. According to Microsoft, you can't change this value per site by editing your web.config (even when you could do it in the past), so the value that you choose here is the value that all your .NET sites will use. Please note that you don't need to add every property if you put autoConfig in false, just putting autoConfig="false" and overriding the value is enough: + +```xml + +``` > **Important Note:** the value specified in this configuration element is a *per-core* setting. For example, if you have a 4 core machine and want your minIOThreads setting to be 200 at runtime, you would use ``. - - Outside of ASP.NET, use the [ThreadPool.SetMinThreads(…)](https://msdn.microsoft.com//en-us/library/system.threading.threadpool.setminthreads(v=vs.100).aspx) API. + - Outside of ASP.NET, use one of the methods described in [Run-time configuration options for threading +](https://docs.microsoft.com/dotnet/core/run-time-config/threading#minimum-threads): + - [ThreadPool.SetMinThreads(…)](https://docs.microsoft.com/dotnet/api/system.threading.threadpool.setminthreads) + - The `ThreadPoolMinThreads` MSBuild property + - The `System.Threading.ThreadPool.MinThreads` setting in your `runtimeconfig.json` + +Explanation for abbreviations appearing in exception messages +--- +By default Redis Timeout exception(s) includes useful information, which can help in understanding & diagnosing the timeouts. Some of the abbreviations are as follows: + +| Abbreviation | Long Name | Meaning | +| ------------- | ---------------------- | ---------------------------- | +| inst | OpsSinceLastHeartbeat : {int} | | +|qu | Queue-Awaiting-Write : {int}|There are x operations currently waiting in queue to write to the redis server.| +|qs | Queue-Awaiting-Response : {int}|There are x operations currently awaiting replies from redis server.| +|aw | Active-Writer: {bool}|| +|bw | Backlog-Writer: {enum} | Possible values are Inactive, Started, CheckingForWork, CheckingForTimeout, RecordingTimeout, WritingMessage, Flushing, MarkingInactive, RecordingWriteFailure, RecordingFault, SettingIdle, SpinningDown, Faulted| +|rs | Read-State: {enum}|Possible values are NotStarted, Init, RanToCompletion, Faulted, ReadSync, ReadAsync, UpdateWriteTime, ProcessBuffer, MarkProcessed, TryParseResult, MatchResult, PubSubMessage, PubSubSMessage, PubSubPMessage, Reconfigure, InvokePubSub, DequeueResult, ComputeResult, CompletePendingMessage, NA| +|ws | Write-State: {enum}| Possible values are Initializing, Idle, Writing, Flushing, Flushed, NA| +|in | Inbound-Bytes : {long}|there are x bytes waiting to be read from the input stream from redis| +|in-pipe | Inbound-Pipe-Bytes: {long}|Bytes waiting to be read| +|out-pipe| Outbound-Pipe-Bytes: {long}|Bytes waiting to be sent| +|mgr | 8 of 10 available|Redis Internal Dedicated Thread Pool State| +|IOCP | IOCP: (Busy=0,Free=500,Min=248,Max=500)| Runtime Global Thread Pool IO Threads. | +|WORKER | WORKER: (Busy=170,Free=330,Min=248,Max=500)| Runtime Global Thread Pool Worker Threads.| +|POOL | POOL: (Threads=8,QueuedItems=0,CompletedItems=42,Timers=10)| Thread Pool Work Item Stats.| +|v | Redis Version: version |The `StackExchange.Redis` version you are currently using in your application.| +|active | Message-Current: {string} |Included in exception message when `IncludeDetailInExceptions=True` on multiplexer| +|next | Message-Next: {string} |When `IncludeDetailInExceptions=True` on multiplexer, it might include command and key, otherwise only command.| +|Local-CPU | %CPU or Not Available |When `IncludePerformanceCountersInExceptions=True` on multiplexer, Local CPU %age will be included in exception message. It might not work in all environments where application is hosted. | + diff --git a/docs/Transactions.md b/docs/Transactions.md index 7ff22a0e4..4d8deca27 100644 --- a/docs/Transactions.md +++ b/docs/Transactions.md @@ -1,7 +1,7 @@ Transactions in Redis ===================== -Transactions in Redis are not like transactions in, say a SQL database. The [full documentation is here](http://redis.io/topics/transactions), +Transactions in Redis are not like transactions in, say a SQL database. The [full documentation is here](https://redis.io/topics/transactions), but to paraphrase: A transaction in redis consists of a block of commands placed between `MULTI` and `EXEC` (or `DISCARD` for rollback). Once a `MULTI` @@ -11,7 +11,7 @@ all applied in a single unit (i.e. without other connections getting time betwee a `EXEC`, everything is thrown away. Because the commands inside the transaction are queued, you can't make decisions *inside* the transaction. For example, in a SQL database you might do the following (pseudo-code - illustrative only): -```C# +```csharp // assign a new unique id only if they don't already // have one, in a transaction to ensure no thread-races var newId = CreateNewUniqueID(); // optimistic @@ -41,14 +41,14 @@ you *can* do is: `WATCH` a key, check data from that key in the normal way, then If, when you check the data, you discover that you don't actually need the transaction, you can use `UNWATCH` to forget all the watched keys. Note that watched keys are also reset during `EXEC` and `DISCARD`. So *at the Redis layer*, this is conceptually: -``` +```lua WATCH {custKey} HEXISTS {custKey} "UniqueId" -(check the reply, then either:) +-- (check the reply, then either:) MULTI HSET {custKey} "UniqueId" {newId} EXEC -(or, if we find there was already an unique-id:) +-- (or, if we find there was already an unique-id:) UNWATCH ``` @@ -66,7 +66,7 @@ basically pre-canned tests involving `WATCH`, some kind of test, and a check on pass, the `MULTI`/`EXEC` is issued; otherwise `UNWATCH` is issued. This is all done in a way that prevents the commands being mixed together with other callers. So our example becomes: -```C# +```csharp var newId = CreateNewId(); var tran = db.CreateTransaction(); tran.AddCondition(Condition.HashNotExists(custKey, "UniqueID")); @@ -88,7 +88,7 @@ Inbuilt operations via `When` It should also be noted that many common scenarios (in particular: key/hash existence, like in the above) have been anticipated by Redis, and single-operation atomic commands exist. These are accessed via the `When` parameter - so our previous example can *also* be written as: -```C# +```csharp var newId = CreateNewId(); bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists); ``` @@ -98,19 +98,19 @@ bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists); Lua --- -You should also keep in mind that Redis 2.6 and above [support Lua scripting](http://redis.io/commands/EVAL), a versatile tool for performing multiple operations as a single atomic unit at the server. +You should also keep in mind that Redis 2.6 and above [support Lua scripting](https://redis.io/commands/EVAL), a versatile tool for performing multiple operations as a single atomic unit at the server. Since no other connections are serviced during a Lua script it behaves much like a transaction, but without the complexity of `MULTI` / `EXEC` etc. This also avoids issues such as bandwidth and latency between the caller and the server, but the trade-off is that it monopolises the server for the duration of the script. At the Redis layer (and assuming `HSETNX` did not exist) this could be implemented as: -``` +```lua EVAL "if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end" 1 {custKey} {newId} ``` This can be used in StackExchange.Redis via: -```C# +```csharp var wasSet = (bool) db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end", new RedisKey[] { custKey }, new RedisValue[] { newId }); ``` diff --git a/docs/docs.csproj b/docs/docs.csproj new file mode 100644 index 000000000..977e065bc --- /dev/null +++ b/docs/docs.csproj @@ -0,0 +1,6 @@ + + + + netstandard2.0 + + diff --git a/docs/exp/SER001.md b/docs/exp/SER001.md new file mode 100644 index 000000000..2def8be6e --- /dev/null +++ b/docs/exp/SER001.md @@ -0,0 +1,22 @@ +At the current time, [Redis documents that](https://redis.io/docs/latest/commands/vadd/): + +> Vector set is a new data type that is currently in preview and may be subject to change. + +As such, the corresponding library feature must also be considered subject to change: + +1. Existing bindings may cease working correctly if the underlying server API changes. +2. Changes to the server API may require changes to the library API, manifesting in either/both of build-time + or run-time breaks. + +While this seems *unlikely*, it must be considered a possibility. If you acknowledge this, you can suppress +this warning by adding the following to your `csproj` file: + +```xml +$(NoWarn);SER001 +``` + +or more granularly / locally in C#: + +``` c# +#pragma warning disable SER001 +``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index c8ded8538..0b4d9bb2e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,13 +1,13 @@ StackExchange.Redis =================== -[Release Notes](ReleaseNotes) +- [Release Notes](ReleaseNotes) ## Overview -StackExchange.Redis is a high performance general purpose redis client for .NET languages (C# etc). It is the logical successor to [BookSleeve](https://code.google.com/archive/p/booksleeve/), -and is the client developed-by (and used-by) [Stack Exchange](http://stackexchange.com/) for busy sites like [Stack Overflow](http://stackoverflow.com/). For the full reasons -why this library was created (i.e. "What about BookSleeve?") [please see here](http://marcgravell.blogspot.com/2014/03/so-i-went-and-wrote-another-redis-client.html). +StackExchange.Redis is a high performance general purpose redis client for .NET languages (C#, etc.). It is the logical successor to [BookSleeve](https://code.google.com/archive/p/booksleeve/), +and is the client developed-by (and used-by) [Stack Exchange](https://stackexchange.com/) for busy sites like [Stack Overflow](https://stackoverflow.com/). For the full reasons +why this library was created (i.e. "What about BookSleeve?") [please see here](https://marcgravell.blogspot.com/2014/03/so-i-went-and-wrote-another-redis-client.html). Features -- @@ -27,32 +27,33 @@ StackExchange.Redis can be installed via the nuget UI (as [StackExchange.Redis]( PM> Install-Package StackExchange.Redis ``` -If you require a strong-named package (because your project is strong-named), then you may wish to use instead: - -```PowerShell -PM> Install-Package StackExchange.Redis.StrongName -``` - -([for further reading, see here](http://blog.marcgravell.com/2014/06/snk-we-need-to-talk.html)) - Documentation --- +- [Server](Server) - running a redis server - [Basic Usage](Basics) - getting started and basic usage +- [Async Timeouts](AsyncTimeouts) - async timeouts and cancellation - [Configuration](Configuration) - options available when connecting to redis - [Pipelines and Multiplexers](PipelinesMultiplexers) - what is a multiplexer? - [Keys, Values and Channels](KeysValues) - discusses the data-types used on the API - [Transactions](Transactions) - how atomic transactions work in redis - [Events](Events) - the events available for logging / information purposes - [Pub/Sub Message Order](PubSubOrder) - advice on sequential and concurrent processing +- [Using RESP3](Resp3) - information on using RESP3 +- [ServerMaintenanceEvent](ServerMaintenanceEvent) - how to listen and prepare for hosted server maintenance (e.g. Azure Cache for Redis) +- [Streams](Streams) - how to use the Stream data type - [Where are `KEYS` / `SCAN` / `FLUSH*`?](KeysScan) - how to use server-based commands - [Profiling](Profiling) - profiling interfaces, as well as how to profile in an `async` world -- [Scripting](Scripting) - running Lua scripts with convenient named parameter replacement +- [Scripting](Scripting) - running Lua scripts with convenient named parameter replacement +- [Testing](Testing) - running the `StackExchange.Redis.Tests` suite to validate changes +- [Timeouts](Timeouts) - guidance on dealing with timeout problems +- [Thread Theft](ThreadTheft) - guidance on avoiding TPL threading problems +- [RESP Logging](RespLogging) - capturing and validating RESP streams Questions and Contributions --- If you think you have found a bug or have a feature request, please [report an issue][2], or if appropriate: submit a pull request. If you have a question, feel free to [contact me](https://github.com/mgravell). - [1]: http://msdn.microsoft.com/en-us/library/dd460717%28v=vs.110%29.aspx + [1]: https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl [2]: https://github.com/StackExchange/StackExchange.Redis/issues?state=open diff --git a/eng/StackExchange.Redis.Build/FastHashGenerator.cs b/eng/StackExchange.Redis.Build/FastHashGenerator.cs new file mode 100644 index 000000000..cdbc94ebe --- /dev/null +++ b/eng/StackExchange.Redis.Build/FastHashGenerator.cs @@ -0,0 +1,215 @@ +using System.Buffers; +using System.Collections.Immutable; +using System.Reflection; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace StackExchange.Redis.Build; + +[Generator(LanguageNames.CSharp)] +public class FastHashGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var literals = context.SyntaxProvider + .CreateSyntaxProvider(Predicate, Transform) + .Where(pair => pair.Name is { Length: > 0 }) + .Collect(); + + context.RegisterSourceOutput(literals, Generate); + } + + private bool Predicate(SyntaxNode node, CancellationToken cancellationToken) + { + // looking for [FastHash] partial static class Foo { } + if (node is ClassDeclarationSyntax decl + && decl.Modifiers.Any(SyntaxKind.StaticKeyword) + && decl.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + foreach (var attribList in decl.AttributeLists) + { + foreach (var attrib in attribList.Attributes) + { + if (attrib.Name.ToString() is "FastHashAttribute" or "FastHash") return true; + } + } + } + + return false; + } + + private static string GetName(INamedTypeSymbol type) + { + if (type.ContainingType is null) return type.Name; + var stack = new Stack(); + while (true) + { + stack.Push(type.Name); + if (type.ContainingType is null) break; + type = type.ContainingType; + } + var sb = new StringBuilder(stack.Pop()); + while (stack.Count != 0) + { + sb.Append('.').Append(stack.Pop()); + } + return sb.ToString(); + } + + private (string Namespace, string ParentType, string Name, string Value) Transform( + GeneratorSyntaxContext ctx, + CancellationToken cancellationToken) + { + // extract the name and value (defaults to name, but can be overridden via attribute) and the location + if (ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) is not INamedTypeSymbol named) return default; + string ns = "", parentType = ""; + if (named.ContainingType is { } containingType) + { + parentType = GetName(containingType); + ns = containingType.ContainingNamespace.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + } + else if (named.ContainingNamespace is { } containingNamespace) + { + ns = containingNamespace.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + } + + string name = named.Name, value = ""; + foreach (var attrib in named.GetAttributes()) + { + if (attrib.AttributeClass?.Name == "FastHashAttribute") + { + if (attrib.ConstructorArguments.Length == 1) + { + if (attrib.ConstructorArguments[0].Value?.ToString() is { Length: > 0 } val) + { + value = val; + break; + } + } + } + } + + if (string.IsNullOrWhiteSpace(value)) + { + value = name.Replace("_", "-"); // if nothing explicit: infer from name + } + + return (ns, parentType, name, value); + } + + private string GetVersion() + { + var asm = GetType().Assembly; + if (asm.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false).FirstOrDefault() is + AssemblyFileVersionAttribute { Version: { Length: > 0 } } version) + { + return version.Version; + } + + return asm.GetName().Version?.ToString() ?? "??"; + } + + private void Generate( + SourceProductionContext ctx, + ImmutableArray<(string Namespace, string ParentType, string Name, string Value)> literals) + { + if (literals.IsDefaultOrEmpty) return; + + var sb = new StringBuilder("// ") + .AppendLine().Append("// ").Append(GetType().Name).Append(" v").Append(GetVersion()).AppendLine(); + + // lease a buffer that is big enough for the longest string + var buffer = ArrayPool.Shared.Rent( + Encoding.UTF8.GetMaxByteCount(literals.Max(l => l.Value.Length))); + int indent = 0; + + StringBuilder NewLine() => sb.AppendLine().Append(' ', indent * 4); + NewLine().Append("using System;"); + NewLine().Append("using StackExchange.Redis;"); + NewLine().Append("#pragma warning disable CS8981"); + foreach (var grp in literals.GroupBy(l => (l.Namespace, l.ParentType))) + { + NewLine(); + int braces = 0; + if (!string.IsNullOrWhiteSpace(grp.Key.Namespace)) + { + NewLine().Append("namespace ").Append(grp.Key.Namespace); + NewLine().Append("{"); + indent++; + braces++; + } + if (!string.IsNullOrWhiteSpace(grp.Key.ParentType)) + { + if (grp.Key.ParentType.Contains('.')) // nested types + { + foreach (var part in grp.Key.ParentType.Split('.')) + { + NewLine().Append("partial class ").Append(part); + NewLine().Append("{"); + indent++; + braces++; + } + } + else + { + NewLine().Append("partial class ").Append(grp.Key.ParentType); + NewLine().Append("{"); + indent++; + braces++; + } + } + + foreach (var literal in grp) + { + int len; + unsafe + { + fixed (byte* bPtr = buffer) // netstandard2.0 forces fallback API + { + fixed (char* cPtr = literal.Value) + { + len = Encoding.UTF8.GetBytes(cPtr, literal.Value.Length, bPtr, buffer.Length); + } + } + } + + // perform string escaping on the generated value (this includes the quotes, note) + var csValue = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(literal.Value)).ToFullString(); + + var hash = FastHash.Hash64(buffer.AsSpan(0, len)); + NewLine().Append("static partial class ").Append(literal.Name); + NewLine().Append("{"); + indent++; + NewLine().Append("public const int Length = ").Append(len).Append(';'); + NewLine().Append("public const long Hash = ").Append(hash).Append(';'); + NewLine().Append("public static ReadOnlySpan U8 => ").Append(csValue).Append("u8;"); + NewLine().Append("public const string Text = ").Append(csValue).Append(';'); + if (len <= 8) + { + // the hash enforces all the values + NewLine().Append("public static bool Is(long hash, in RawResult value) => hash == Hash && value.Payload.Length == Length;"); + NewLine().Append("public static bool Is(long hash, ReadOnlySpan value) => hash == Hash & value.Length == Length;"); + } + else + { + NewLine().Append("public static bool Is(long hash, in RawResult value) => hash == Hash && value.IsEqual(U8);"); + NewLine().Append("public static bool Is(long hash, ReadOnlySpan value) => hash == Hash && value.SequenceEqual(U8);"); + } + indent--; + NewLine().Append("}"); + } + + // handle any closing braces + while (braces-- > 0) + { + indent--; + NewLine().Append("}"); + } + } + + ArrayPool.Shared.Return(buffer); + ctx.AddSource("FastHash.generated.cs", sb.ToString()); + } +} diff --git a/eng/StackExchange.Redis.Build/FastHashGenerator.md b/eng/StackExchange.Redis.Build/FastHashGenerator.md new file mode 100644 index 000000000..7fc5103ae --- /dev/null +++ b/eng/StackExchange.Redis.Build/FastHashGenerator.md @@ -0,0 +1,64 @@ +# FastHashGenerator + +Efficient matching of well-known short string tokens is a high-volume scenario, for example when matching RESP literals. + +The purpose of this generator is to interpret inputs like: + +``` c# +[FastHash] public static partial class bin { } +[FastHash] public static partial class f32 { } +``` + +Usually the token is inferred from the name; `[FastHash("real value")]` can be used if the token is not a valid identifier. +Underscore is replaced with hyphen, so a field called `my_token` has the default value `"my-token"`. +The generator demands *all* of `[FastHash] public static partial class`, and note that any *containing* types must +*also* be declared `partial`. + +The output is of the form: + +``` c# +static partial class bin +{ + public const int Length = 3; + public const long Hash = 7235938; + public static ReadOnlySpan U8 => @"bin"u8; + public static string Text => @"bin"; + public static bool Is(long hash, in RawResult value) => ... + public static bool Is(long hash, in ReadOnlySpan value) => ... +} +static partial class f32 +{ + public const int Length = 3; + public const long Hash = 3289958; + public static ReadOnlySpan U8 => @"f32"u8; + public const string Text = @"f32"; + public static bool Is(long hash, in RawResult value) => ... + public static bool Is(long hash, in ReadOnlySpan value) => ... +} +``` + +(this API is strictly an internal implementation detail, and can change at any time) + +This generated code allows for fast, efficient, and safe matching of well-known tokens, for example: + +``` c# +var key = ... +var hash = key.Hash64(); +switch (key.Length) +{ + case bin.Length when bin.Is(hash, key): + // handle bin + break; + case f32.Length when f32.Is(hash, key): + // handle f32 + break; +} +``` + +The switch on the `Length` is optional, but recommended - these low values can often be implemented (by the compiler) +as a simple jump-table, which is very fast. However, switching on the hash itself is also valid. All hash matches +must also perform a sequence equality check - the `Is(hash, value)` convenience method validates both hash and equality. + +Note that `switch` requires `const` values, hence why we use generated *types* rather than partial-properties +that emit an instance with the known values. Also, the `"..."u8` syntax emits a span which is awkward to store, but +easy to return via a property. diff --git a/eng/StackExchange.Redis.Build/StackExchange.Redis.Build.csproj b/eng/StackExchange.Redis.Build/StackExchange.Redis.Build.csproj new file mode 100644 index 000000000..f875133ba --- /dev/null +++ b/eng/StackExchange.Redis.Build/StackExchange.Redis.Build.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + enable + enable + true + + + + + + + + + FastHash.cs + + + + diff --git a/global.json b/global.json new file mode 100644 index 000000000..35e954767 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ + +{ + "sdk": { + "allowPrerelease": false + } +} \ No newline at end of file diff --git a/monobuild.bash b/monobuild.bash deleted file mode 100755 index d0154c0dc..000000000 --- a/monobuild.bash +++ /dev/null @@ -1,11 +0,0 @@ -rm -rf StackExchange.Redis/bin/mono -rm -rf BasicTest/bin/mono -mkdir -p StackExchange.Redis/bin/mono -mkdir -p BasicTest/bin/mono -echo -e "Building StackExchange.Redis.dll ..." -mcs -recurse:StackExchange.Redis/*.cs -out:StackExchange.Redis/bin/mono/StackExchange.Redis.dll -target:library -unsafe+ -o+ -r:System.IO.Compression.dll -echo -e "Building BasicTest.exe ..." -mcs BasicTest/Program.cs -out:BasicTest/bin/mono/BasicTest.exe -target:exe -o+ -r:StackExchange.Redis/bin/mono/StackExchange.Redis.dll -cp StackExchange.Redis/bin/mono/*.* BasicTest/bin/mono/ -echo -e "Running basic test ..." -mono BasicTest/bin/mono/BasicTest.exe 10000 diff --git a/monobuild.cmd b/monobuild.cmd deleted file mode 100644 index d31dc08bc..000000000 --- a/monobuild.cmd +++ /dev/null @@ -1,15 +0,0 @@ -@rd /s /q StackExchange.Redis\bin\mono 1>nul 2>nul -@rd /s /q BasicTest\bin\mono 1>nul 2>nul -@md StackExchange.Redis\bin\mono 1>nul 2>nul -@md BasicTest\bin\mono 1>nul 2>nul -@echo Building StackExchange.Redis.dll ... -@call mcs -recurse:StackExchange.Redis\*.cs -out:StackExchange.Redis\bin\mono\StackExchange.Redis.dll -target:library -unsafe+ -o+ -r:System.IO.Compression.dll -@echo Building BasicTest.exe ... -@call mcs BasicTest\Program.cs -out:BasicTest\bin\mono\BasicTest.exe -target:exe -o+ -r:StackExchange.Redis\bin\mono\StackExchange.Redis.dll -@copy StackExchange.Redis\bin\mono\*.* BasicTest\bin\mono > nul -@echo . -@echo Running basic test (Mono) ... -@call mono BasicTest\bin\mono\BasicTest.exe 100000 -@echo . -@echo Running basic test (.NET) ... -@call BasicTest\bin\mono\BasicTest.exe 100000 diff --git a/netbuild.cmd b/netbuild.cmd deleted file mode 100644 index 698eb2726..000000000 --- a/netbuild.cmd +++ /dev/null @@ -1,12 +0,0 @@ -@rd /s /q StackExchange.Redis\bin\Release 1>nul 2>nul -@rd /s /q BasicTest\bin\Release 1>nul 2>nul -@md StackExchange.Redis\bin\Release 1>nul 2>nul -@md BasicTest\bin\Release 1>nul 2>nul -@echo Building StackExchange.Redis.dll ... -@call csc /out:StackExchange.Redis\bin\Release\StackExchange.Redis.dll /target:library /unsafe+ /o+ /r:System.IO.Compression.dll /recurse:StackExchange.Redis\*.cs -@echo Building BasicTest.exe ... -@call csc /out:BasicTest\bin\Release\BasicTest.exe /target:exe -o+ /r:StackExchange.Redis\bin\Release\StackExchange.Redis.dll BasicTest\Program.cs -@copy StackExchange.Redis\bin\Release\*.* BasicTest\bin\Release > nul -@echo . -@echo Running basic test (.NET) ... -@call BasicTest\bin\Release\BasicTest.exe 100000 \ No newline at end of file diff --git a/packages/BookSleeve.1.3.41/lib/BookSleeve.dll b/packages/BookSleeve.1.3.41/lib/BookSleeve.dll deleted file mode 100644 index 0ae493fbe..000000000 Binary files a/packages/BookSleeve.1.3.41/lib/BookSleeve.dll and /dev/null differ diff --git a/packages/BookSleeve.1.3.41/lib/BookSleeve.pdb b/packages/BookSleeve.1.3.41/lib/BookSleeve.pdb deleted file mode 100644 index fce158362..000000000 Binary files a/packages/BookSleeve.1.3.41/lib/BookSleeve.pdb and /dev/null differ diff --git a/packages/BookSleeve.1.3.41/lib/BookSleeve.xml b/packages/BookSleeve.1.3.41/lib/BookSleeve.xml deleted file mode 100644 index aa10b75d3..000000000 --- a/packages/BookSleeve.1.3.41/lib/BookSleeve.xml +++ /dev/null @@ -1,3300 +0,0 @@ - - - - BookSleeve - - - - - Represents the state of an individual client connection to redis - - - - - Format the object as a string - - - - - address/port of the client - - - - - total duration of the connection in seconds - - - - - idle time of the connection in seconds - - - - - current database ID - - - - - number of channel subscriptions - - - - - number of pattern matching subscriptions - - - - - number of commands in a MULTI/EXEC context - - - - - The client flags can be a combination of: - O: the client is a slave in MONITOR mode - S: the client is a normal slave server - M: the client is a master - x: the client is in a MULTI/EXEC context - b: the client is waiting in a blocking operation - i: the client is waiting for a VM I/O (deprecated) - d: a watched keys has been modified - EXEC will fail - c: connection to be closed after writing entire reply - u: the client is unblocked - A: connection to be closed ASAP - N: no specific flag set - - - - - The flags associated with this connection - - - - - last command played - - - - - The name allocated to this connection, if any - - - - - The client flags can be a combination of: - O: the client is a slave in MONITOR mode - S: the client is a normal slave server - M: the client is a master - x: the client is in a MULTI/EXEC context - b: the client is waiting in a blocking operation - i: the client is waiting for a VM I/O (deprecated) - d: a watched keys has been modified - EXEC will fail - c: connection to be closed after writing entire reply - u: the client is unblocked - A: connection to be closed ASAP - N: no specific flag set - - - - - no specific flag set - - - - - the client is a slave in MONITOR mode - - - - - the client is a normal slave server - - - - - the client is a master - - - - - the client is in a MULTI/EXEC context - - - - - the client is waiting in a blocking operation - - - - - a watched keys has been modified - EXEC will fail - - - - - connection to be closed after writing entire reply - - - - - the client is unblocked - - - - - connection to be closed ASAP - - - - - Represents the information known about long-running commands - - - - - Deduces a link to the redis documentation about the specified command - - - - - A unique progressive identifier for every slow log entry. - - The entry's unique ID can be used in order to avoid processing slow log entries multiple times (for instance you may have a script sending you an email alert for every new slow log entry). The ID is never reset in the course of the Redis server execution, only a server restart will reset it. - - - - The time at which the logged command was processed. - - - - - The amount of time needed for its execution - - - - - The array composing the arguments of the command. - - - - - Describes a pre-condition used in a redis transaction - - - - - Enforces that the given key must exist - - - - - Enforces that the given key must not exist - - - - - Enforces that the given hash-field must exist - - - - - Enforces that the given hash-field must not exist - - - - - Enforces that the given key must have the specified value - - - - - Enforces that the given key must have the specified value - - - - - Enforces that the given key must not have the specified value - - - - - Enforces that the given key must not have the specified value - - - - - Enforces that the given hash-field must have the specified value - - - - - Enforces that the given hash-field must have the specified value - - - - - Enforces that the given hash-field must not have the specified value - - - - - Enforces that the given hash-field must not have the specified value - - - - - Provides utility methods for managing connections to multiple (master/slave) redis servers (with the same - information - not sharding). - - - - - Inspect the provided configration, and connect to the available servers to report which server is the preferred/active node. - - - - - Inspect the provided configration, and connect to the available servers to report which server is the preferred/active node. - - - - - Inspect the provided configration, and connect to the preferred/active node after checking what nodes are available. - - - - - Inspect the provided configration, and connect to the preferred/active node after checking what nodes are available. - - - - - Subscribe to perform some operation when a change to the preferred/active node is broadcast. - - - - - Using the configuration available, and after checking which nodes are available, switch the master node and broadcast this change. - - - - - Using the configuration available, and after checking which nodes are available, switch the master node and broadcast this change. - - - - - Prompt all clients to reconnect. - - - - - A thread-safe, multiplexed connection to a Redis server; each connection - should be cached and re-used (without synchronization) from multiple - callers for maximum efficiency. Usually only a single RedisConnection - is required - - - - - Base class for a redis-connection; provides core redis services - - - - - The default time to wait for individual commands to complete when using Wait - - - - - Explicitly specify the server version; this is useful when INFO is not available - - - - - Obtains fresh statistics on the usage of the connection - - - - - Issues a basic ping/pong pair against the server, returning the latency - - - - - Releases any resources associated with the connection - - - - - Releases any resources associated with the connection - - - - - Called after opening a connection - - - - - Called before opening a connection - - - - - Called during connection init, but after the AUTH is sent (if needed) - - Whether to release any queued messages - - - - An already-completed task that indicates success - - - - - Attempts to open the connection to the remote server - - - - - Releases the queue of any messages "sent" before the connection was open - - - - - Invoked when we have completed the handshake - - - - - Specify a name for the current connection - - - - - The INFO command returns information and statistics about the server in format that is simple to parse by computers and easy to red by humans. - - http://redis.io/commands/info - - - - The INFO command returns information and statistics about the server in format that is simple to parse by computers and easy to red by humans. - - http://redis.io/commands/info - - - - Closes the connection; either draining the unsent queue (to completion), or abandoning the unsent queue. - - - - - Closes the connection; either draining the unsent queue (to completion), or abandoning the unsent queue. - - - - - Peek at the next item in the sent-queue - - - - - Invoked when the server is terminating - - - - - Indicates the number of commands executed on a per-database basis - - - - - Raises an error event - - - - - Raises an error event - - - - - Return the number of items in the sent-queue - - - - - Temporarily suspends eager-flushing (flushing if the write-queue becomes empty briefly). Buffer-based flushing - will still occur when the data is full. This is useful if you are performing a large number of - operations in close duration, and want to avoid packet fragmentation. Note that you MUST call - ResumeFlush at the end of the operation - preferably using Try/Finally so that flushing is resumed - even upon error. This method is thread-safe; any number of callers can suspend/resume flushing - concurrently - eager flushing will resume fully when all callers have called ResumeFlush. - - Note that some operations (transaction conditions, etc) require flushing - this will still - occur even if the buffer is only part full. - - - - Resume eager-flushing (flushing if the write-queue becomes empty briefly). See SuspendFlush for - full usage. - - - - - Writes a group of messages, but allowing other threads to inject messages between them; - this method minimises the numbers of packets by preventing flush until all are written - - - - - The message to supply to callers when rejecting messages - - - - - If the task is not yet completed, blocks the caller until completion up to a maximum of SyncTimeout milliseconds. - Once a task is completed, the result is returned. - - The task to wait on - The return value of the task. - If SyncTimeout milliseconds is exceeded. - - - - If the task is not yet completed, blocks the caller until completion up to a maximum of SyncTimeout milliseconds. - - The task to wait on - If SyncTimeout milliseconds is exceeded. - If an exception is throw, it is extracted from the AggregateException (unless multiple exceptions are found) - - - - Give some information about the oldest incomplete (but sent) message on the server - - - - - Waits for all of a set of tasks to complete, up to a maximum of SyncTimeout milliseconds. - - The tasks to wait on - If SyncTimeout milliseconds is exceeded. - - - - Waits for any of a set of tasks to complete, up to a maximum of SyncTimeout milliseconds. - - The tasks to wait on - The index of a completed task - If SyncTimeout milliseconds is exceeded. - - - - Add a continuation (a callback), to be executed once a task has completed - - The task to add a continuation to - The continuation to perform once completed - A new task representing the composed operation - - - - Add a continuation (a callback), to be executed once a task has completed - - The task to add a continuation to - The continuation to perform once completed - A new task representing the composed operation - - - - Attempt to reduce Task overhead by completing tasks without continuations synchronously (default is asynchronously) - - - - - The amount of time to wait for any individual command to return a result when using Wait - - - - - The host for the redis server - - - - - The password used to authenticate with the redis server - - - - - The port for the redis server - - - - - The IO timeout to use when communicating with the redis server - - - - - Features available to the redis server - - - - - Specify a name for this connection (displayed via Server.ListClients / CLIENT LIST) - - - - - The version of the connected redis server - - - - - The current state of the connection - - - - - Indicate the number of messages that have not yet been set. - - - - - Raised when a connection becomes closed. - - - - - If the connection has been shut down, what was the reason? - - - - - Should a QUIT be sent when closing the connection? - - - - - How frequently should keep-alives be sent? - - - - - Invoked when the server is shutting down; includes any error information - - - - - Invoked when any error message is received on the connection. - - - - - If true, then when using the Wait methods, information about the oldest outstanding message - is included in the exception; this often points to a particular operation that was monopolising - the connection - - - - - What type of connection is this - - - - - Gets or sets the behavior for processing incoming messages. - - - - - Gets or sets the default CompletionMode value for all new connections. - - - - - Indicates the current state of the connection to the server - - - - - A connection that has not yet been innitialized - - - - - A connection that has not yet been innitialized - - - - - A connection that is in the process of opening - - - - - An open connection - - - - - A connection that is in the process of closing - - - - - A connection that is now closed and cannot be used - - - - - Commands that apply to Redis scripting (Lua). - - http://redis.io/commands#scripting - - - - Execute a Lua 5.1 script. The script does not need to define a Lua function (and should not). It is just a Lua program that will run in the context of the Redis server. - All key-names should be passed via the keyArgs parameter, which allows necessary checks by Redis. Note that Lua scripts in Redis have a range of features and - limitations - be sure to review the documentation. - - http://redis.io/commands/eval - - - - Ensures that the given script exists - - http://redis.io/commands/script-exists and http://redis.io/commands/script-load - - - - Commands related to server operation and configuration, rather than data. - - http://redis.io/commands#server - - - - Delete all the keys of the currently selected DB. - - http://redis.io/commands/flushdb - - - - Delete all the keys of all the existing databases, not just the currently selected one. - - http://redis.io/commands/flushall - - - - This command is often used to test if a connection is still alive, or to measure latency. - - The latency in milliseconds. - http://redis.io/commands/ping - - - - The TIME command returns the current server time. - - The server's current time. - http://redis.io/commands/time - - - - Get all configuration parameters matching the specified pattern. - - All the configuration parameters matching this parameter are reported as a list of key-value pairs. - All matching configuration parameters. - http://redis.io/commands/config-get - - - - The CONFIG SET command is used in order to reconfigure the server at runtime without the need to restart Redis. You can change both trivial parameters or switch from one to another persistence option using this command. - - http://redis.io/commands/config-set - - - - The SLAVEOF command can change the replication settings of a slave on the fly. In the proper form SLAVEOF hostname port will make the server a slave of another server listening at the specified hostname and port. - If a server is already a slave of some master, SLAVEOF hostname port will stop the replication against the old server and start the synchronization against the new one, discarding the old dataset. - - http://redis.io/commands/slaveof - - - - The SLAVEOF command can change the replication settings of a slave on the fly. - If a Redis server is already acting as slave, the command SLAVEOF NO ONE will turn off the replication, turning the Redis server into a MASTER. - The form SLAVEOF NO ONE will stop replication, turning the server into a MASTER, but will not discard the replication. So, if the old master stops working, it is possible to turn the slave into a master and set the application to use this new master in read/write. Later when the other Redis server is fixed, it can be reconfigured to work as a slave. - - - - - Flush the Lua scripts cache; this can damage existing connections that expect the flush to behave normally, and should be used with caution. - - http://redis.io/commands/script-flush - - - - The CLIENT LIST command returns information and statistics about the client connections server in a mostly human readable format. - - http://redis.io/commands/client-list - - - - The CLIENT KILL command closes a given client connection identified by ip:port. - - http://redis.io/commands/client-kill - - - - The INFO command returns information and statistics about the server in a format that is simple to parse by computers and easy to read by humans. - - http://redis.io/commands/info - - - - Serialize the value stored at key in a Redis-specific format and return it to the user. The returned value can be synthesized back into a Redis key using the RESTORE command. - The serialization format is opaque and non-standard. The serialized value does NOT contain expire information. In order to capture the time to live of the current value the PTTL command should be used. - - http://redis.io/commands/dump - - - - Create a key associated with a value that is obtained by deserializing the provided serialized value (obtained via Export). - - http://redis.io/commands/restore - - - - This command is used in order to read the slow queries log. - - The number of records to return (or the entire log if not specified) - http://redis.io/commands/slowlog - - - - This command is used in order to reset the slow queries log. - - http://redis.io/commands/slowlog - - - - By default, save the DB in background. Redis forks, the parent continues to serve the clients, the child saves the DB on disk then exits. - By specifying foreground = true, The SAVE commands performs a synchronous save of the dataset producing a point in time snapshot of all the data inside the Redis instance, in the form of an RDB file. - A client my be able to check if the operation succeeded using the LASTSAVE command. - - http://redis.io/commands/bgsave http://redis.io/commands/save - - - - Return the time of the last DB save executed with success. A client may check if a BGSAVE command succeeded reading the LASTSAVE value, then issuing a BGSAVE command and checking at regular intervals every N seconds if LASTSAVE changed. - - http://redis.io/commands/lastsave - - - - Commands that apply to sorted sets per key. A sorted set keeps a "score" - per element, and this score is used to order the elements. Duplicates - are not allowed (typically, the score of the duplicate is added to the - pre-existing element instead). - - http://redis.io/commands#sorted_set - - - - Adds all the specified members with the specified scores to the sorted set stored at key. It is possible to specify multiple score/member pairs. If a specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. If key does not exist, a new sorted set with the specified members as sole members is created, like if the sorted set was empty. If the key exists but does not hold a sorted set, an error is returned. - The score values should be the string representation of a numeric value, and accepts double precision floating point numbers. - - The number of elements added to the sorted sets, not including elements already existing for which the score was updated. - http://redis.io/commands/zadd - - - - Adds all the specified members with the specified scores to the sorted set stored at key. It is possible to specify multiple score/member pairs. If a specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. If key does not exist, a new sorted set with the specified members as sole members is created, like if the sorted set was empty. If the key exists but does not hold a sorted set, an error is returned. - The score values should be the string representation of a numeric value, and accepts double precision floating point numbers. - - The number of elements added to the sorted sets, not including elements already existing for which the score was updated. - http://redis.io/commands/zadd - - - - Returns the sorted set cardinality (number of elements) of the sorted set stored at key. - - the cardinality (number of elements) of the sorted set, or 0 if key does not exist. - http://redis.io/commands/zcard - - - - Returns the number of elements in the sorted set at key with a score between min and max. - The min and max arguments have the same semantic as described for ZRANGEBYSCORE. - - the number of elements in the specified score range. - http://redis.io/commands/zcount - - - - Increments the score of member in the sorted set stored at key by increment. If member does not exist in the sorted set, it is added with increment as its score (as if its previous score was 0.0). If key does not exist, a new sorted set with the specified member as its sole member is created. - An error is returned when key exists but does not hold a sorted set. - The score value should be the string representation of a numeric value, and accepts double precision floating point numbers. It is possible to provide a negative value to decrement the score. - - http://redis.io/commands/zincrby - the new score of member (a double precision floating point number), represented as string. - - - - Increments the score of member in the sorted set stored at key by increment. If member does not exist in the sorted set, it is added with increment as its score (as if its previous score was 0.0). If key does not exist, a new sorted set with the specified member as its sole member is created. - An error is returned when key exists but does not hold a sorted set. - The score value should be the string representation of a numeric value, and accepts double precision floating point numbers. It is possible to provide a negative value to decrement the score. - - http://redis.io/commands/zincrby - the new score of member (a double precision floating point number), represented as string. - - - - Increments the score of member in the sorted set stored at key by increment. If member does not exist in the sorted set, it is added with increment as its score (as if its previous score was 0.0). If key does not exist, a new sorted set with the specified member as its sole member is created. - An error is returned when key exists but does not hold a sorted set. - The score value should be the string representation of a numeric value, and accepts double precision floating point numbers. It is possible to provide a negative value to decrement the score. - - http://redis.io/commands/zincrby - the new score of member (a double precision floating point number), represented as string. - - - - Increments the score of member in the sorted set stored at key by increment. If member does not exist in the sorted set, it is added with increment as its score (as if its previous score was 0.0). If key does not exist, a new sorted set with the specified member as its sole member is created. - An error is returned when key exists but does not hold a sorted set. - The score value should be the string representation of a numeric value, and accepts double precision floating point numbers. It is possible to provide a negative value to decrement the score. - - http://redis.io/commands/zincrby - the new score of member (a double precision floating point number), represented as string. - - - - Returns the specified range of elements in the sorted set stored at key. The elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. - See ZREVRANGE when you need the elements ordered from highest to lowest score (and descending lexicographical order for elements with equal score). - Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on. - - http://redis.io/commands/zrange - http://redis.io/commands/zrevrange - list of elements in the specified range (optionally with their scores). - - - - Returns the specified range of elements in the sorted set stored at key. The elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. - See ZREVRANGE when you need the elements ordered from highest to lowest score (and descending lexicographical order for elements with equal score). - Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on. - - http://redis.io/commands/zrange - http://redis.io/commands/zrevrange - list of elements in the specified range (optionally with their scores). - - - - Returns the specified range of elements in the sorted set stored at key. The elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. - See ZREVRANGE when you need the elements ordered from highest to lowest score (and descending lexicographical order for elements with equal score). - Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on. - - http://redis.io/commands/zrange - http://redis.io/commands/zrevrange - list of elements in the specified range (optionally with their scores). - - - - Returns all the elements in the sorted set at key with a score between min and max (including elements with score equal to min or max). The elements are considered to be ordered from low to high scores. - The elements having the same score are returned in lexicographical order (this follows from a property of the sorted set implementation in Redis and does not involve further computation). - The optional LIMIT argument can be used to only get a range of the matching elements (similar to SELECT LIMIT offset, count in SQL). Keep in mind that if offset is large, the sorted set needs to be traversed for offset elements before getting to the elements to return, which can add up to O(N) time complexity. - - http://redis.io/commands/zrangebyscore - http://redis.io/commands/zrevrangebyscore - list of elements in the specified score range (optionally with their scores). - - - - Returns the rank of member in the sorted set stored at key, with the scores ordered from low to high. The rank (or index) is 0-based, which means that the member with the lowest score has rank 0. - - http://redis.io/commands/zrank - http://redis.io/commands/zrevrank - If member exists in the sorted set, Integer reply: the rank of member. If member does not exist in the sorted set or key does not exist, Bulk reply: nil. - - - - Returns the rank of member in the sorted set stored at key, with the scores ordered from low to high. The rank (or index) is 0-based, which means that the member with the lowest score has rank 0. - - http://redis.io/commands/zrank - http://redis.io/commands/zrevrank - If member exists in the sorted set, Integer reply: the rank of member. If member does not exist in the sorted set or key does not exist, Bulk reply: nil. - - - - Returns the score of member in the sorted set at key. If member does not exist in the sorted set, or key does not exist, nil is returned. - - http://redis.io/commands/zscore - the score of member (a double precision floating point number), represented as string. - - - - Returns the score of member in the sorted set at key. If member does not exist in the sorted set, or key does not exist, nil is returned. - - http://redis.io/commands/zscore - the score of member (a double precision floating point number), represented as string. - - - - Removes the specified members from the sorted set stored at key. Non existing members are ignored. - An error is returned when key exists and does not hold a sorted set. - - http://redis.io/commands/zrem - The number of members removed from the sorted set, not including non existing members. - - - - Removes the specified members from the sorted set stored at key. Non existing members are ignored. - An error is returned when key exists and does not hold a sorted set. - - http://redis.io/commands/zrem - The number of members removed from the sorted set, not including non existing members. - - - - Removes the specified members from the sorted set stored at key. Non existing members are ignored. - An error is returned when key exists and does not hold a sorted set. - - http://redis.io/commands/zrem - The number of members removed from the sorted set, not including non existing members. - - - - Removes the specified members from the sorted set stored at key. Non existing members are ignored. - An error is returned when key exists and does not hold a sorted set. - - http://redis.io/commands/zrem - The number of members removed from the sorted set, not including non existing members. - - - - Removes all elements in the sorted set stored at key with rank between start and stop. Both start and stop are 0-based indexes with 0 being the element with the lowest score. These indexes can be negative numbers, where they indicate offsets starting at the element with the highest score. For example: -1 is the element with the highest score, -2 the element with the second highest score and so forth. - - http://redis.io/commands/zremrangebyrank - the number of elements removed. - - - - Removes all elements in the sorted set stored at key with a score between min and max (inclusive). - - http://redis.io/commands/zremrangebyscore - the number of elements removed. - Since version 2.1.6, min and max can be exclusive, following the syntax of ZRANGEBYSCORE. - - - - Computes the intersection of numkeys sorted sets given by the specified keys, and stores the result in destination. - - http://redis.io/commands/zinterstore - the number of elements in the resulting set. - - - - Computes the union of numkeys sorted sets given by the specified keys, and stores the result in destination. It is mandatory to provide the number of input keys (numkeys) before passing the input keys and the other (optional) arguments. - - http://redis.io/commands/zunionstore - the number of elements in the resulting set. - - - - The ZSCAN command is used in order to incrementally iterate over a collection of elements. - - http://redis.io/commands/zscan - - - - The ZSCAN command is used in order to incrementally iterate over a collection of elements. - - http://redis.io/commands/zscan - - - - Commands that apply to sets of items per key; sets - have no defined order and are strictly unique. Duplicates - are not allowed (typically, duplicates are silently discarded). - - http://redis.io/commands#set - - - - Add member to the set stored at key. If member is already a member of this set, no operation is performed. If key does not exist, a new set is created with member as its sole member. - - true if added - http://redis.io/commands/sadd - - - - Add member to the set stored at key. If member is already a member of this set, no operation is performed. If key does not exist, a new set is created with member as its sole member. - - true if added - http://redis.io/commands/sadd - - - - Add member to the set stored at key. If member is already a member of this set, no operation is performed. If key does not exist, a new set is created with member as its sole member. - - the number of elements actually added to the set. - http://redis.io/commands/sadd - - - - Add member to the set stored at key. If member is already a member of this set, no operation is performed. If key does not exist, a new set is created with member as its sole member. - - the number of elements actually added to the set. - http://redis.io/commands/sadd - - - - Returns the set cardinality (number of elements) of the set stored at key. - - the cardinality (number of elements) of the set, or 0 if key does not exist. - http://redis.io/commands/scard - - - - Returns the members of the set resulting from the difference between the first set and all the successive sets. - - list with members of the resulting set. - http://redis.io/commands/sdiff - - - - Returns the members of the set resulting from the difference between the first set and all the successive sets. - - list with members of the resulting set. - http://redis.io/commands/sdiff - - - - This command is equal to SDIFF, but instead of returning the resulting set, it is stored in destination. - - If destination already exists, it is overwritten. - the number of elements in the resulting set. - http://redis.io/commands/sdiffstore - - - - Returns the members of the set resulting from the intersection of all the given sets. - - list with members of the resulting set. - http://redis.io/commands/sinter - - - - Returns the members of the set resulting from the intersection of all the given sets. - - list with members of the resulting set. - http://redis.io/commands/sinter - - - - This command is equal to SINTER, but instead of returning the resulting set, it is stored in destination. - - If destination already exists, it is overwritten. - the number of elements in the resulting set. - http://redis.io/commands/sinterstore - - - - Returns the members of the set resulting from the union of all the given sets. - - list with members of the resulting set. - http://redis.io/commands/sunion - - - - Returns the members of the set resulting from the union of all the given sets. - - list with members of the resulting set. - http://redis.io/commands/sunion - - - - This command is equal to SUNION, but instead of returning the resulting set, it is stored in destination. - - If destination already exists, it is overwritten. - the number of elements in the resulting set. - http://redis.io/commands/sunionstore - - - - Returns if member is a member of the set stored at key. - - 1 if the element is a member of the set. 0 if the element is not a member of the set, or if key does not exist. - http://redis.io/commands/sismember - - - - Returns if member is a member of the set stored at key. - - 1 if the element is a member of the set. 0 if the element is not a member of the set, or if key does not exist. - http://redis.io/commands/sismember - - - - Returns all the members of the set value stored at key. - - all elements of the set. - http://redis.io/commands/smembers - - - - Returns all the members of the set value stored at key. - - all elements of the set. - http://redis.io/commands/smembers - - - - Move member from the set at source to the set at destination. This operation is atomic. In every given moment the element will appear to be a member of source or destination for other clients. - - If the source set does not exist or does not contain the specified element, no operation is performed and 0 is returned. Otherwise, the element is removed from the source set and added to the destination set. When the specified element already exists in the destination set, it is only removed from the source set. - 1 if the element is moved. 0 if the element is not a member of source and no operation was performed. - http://redis.io/commands/smove - - - - Move member from the set at source to the set at destination. This operation is atomic. In every given moment the element will appear to be a member of source or destination for other clients. - - If the source set does not exist or does not contain the specified element, no operation is performed and 0 is returned. Otherwise, the element is removed from the source set and added to the destination set. When the specified element already exists in the destination set, it is only removed from the source set. - 1 if the element is moved. 0 if the element is not a member of source and no operation was performed. - http://redis.io/commands/smove - - - - Removes and returns a random element from the set value stored at key. - - the removed element, or nil when key does not exist. - http://redis.io/commands/spop - - - - Removes and returns a random element from the set value stored at key. - - the removed element, or nil when key does not exist. - http://redis.io/commands/spop - - - - Return a random element from the set value stored at key. - - the randomly selected element, or nil when key does not exist. - http://redis.io/commands/srandmember - - - - Return a random element from the set value stored at key. - - the randomly selected element, or nil when key does not exist. - http://redis.io/commands/srandmember - - - - Return an array of count distinct elements if count is positive. If called with a negative count the behavior changes and the command is allowed to return the same element multiple times. In this case the numer of returned elements is the absolute value of the specified count. - - the randomly selected element, or nil when key does not exist. - http://redis.io/commands/srandmember - - - - Return an array of count distinct elements if count is positive. If called with a negative count the behavior changes and the command is allowed to return the same element multiple times. In this case the numer of returned elements is the absolute value of the specified count. - - the randomly selected element, or nil when key does not exist. - http://redis.io/commands/srandmember - - - - Remove member from the set stored at key. If member is not a member of this set, no operation is performed. - - 1 if the element was removed. 0 if the element was not a member of the set. - http://redis.io/commands/srem - - - - Remove member from the set stored at key. If member is not a member of this set, no operation is performed. - - 1 if the element was removed. 0 if the element was not a member of the set. - http://redis.io/commands/srem - - - - Remove member from the set stored at key. If member is not a member of this set, no operation is performed. - - 1 if the element was removed. 0 if the element was not a member of the set. - http://redis.io/commands/srem - - - - Remove member from the set stored at key. If member is not a member of this set, no operation is performed. - - 1 if the element was removed. 0 if the element was not a member of the set. - http://redis.io/commands/srem - - - - The SSCAN command is used in order to incrementally iterate over a collection of elements. - - http://redis.io/commands/sscan - - - - The SSCAN command is used in order to incrementally iterate over a collection of elements. - - http://redis.io/commands/sscan - - - - Generic commands that apply to all/most data structures - - http://redis.io/commands#generic - - - - Removes the specified key. A key is ignored if it does not exist. - - True if the key was removed. - http://redis.io/commands/del - - - - Removes the specified keys. A key is ignored if it does not exist. - - The number of keys that were removed. - http://redis.io/commands/del - - - - Returns if key exists. - - 1 if the key exists. 0 if the key does not exist. - http://redis.io/commands/exists - - - - Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is said to be volatile in Redis terminology. - - If key is updated before the timeout has expired, then the timeout is removed as if the PERSIST command was invoked on key. - For Redis versions < 2.1.3, existing timeouts cannot be overwritten. So, if key already has an associated timeout, it will do nothing and return 0. Since Redis 2.1.3, you can update the timeout of a key. It is also possible to remove the timeout using the PERSIST command. See the page on key expiry for more information. - 1 if the timeout was set. 0 if key does not exist or the timeout could not be set. - http://redis.io/commands/expire - - - - Remove the existing timeout on key. - - 1 if the timeout was removed. 0 if key does not exist or does not have an associated timeout. - Available with 2.1.2 and above only - http://redis.io/commands/persist - - - - Returns all keys matching pattern - - Warning: consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases. This command is intended for debugging and special operations, such as changing your keyspace layout. Don't use KEYS in your regular application code. If you're looking for a way to find keys in a subset of your keyspace, consider using sets. - http://redis.io/commands/keys - - - - The SCAN command is used in order to incrementally iterate over a collection of elements. - - http://redis.io/commands/scan - - - - Move key from the currently selected database (see SELECT) to the specified destination database. When key already exists in the destination database, or it does not exist in the source database, it does nothing. It is possible to use MOVE as a locking primitive because of this. - - 1 if key was moved. 0 if key was not moved. - http://redis.io/commands/move - - - - Return a random key from the currently selected database. - - the random key, or nil when the database is empty. - http://redis.io/commands/randomkey - - - - Renames key to newkey. It returns an error when the source and destination names are the same, or when key does not exist. If newkey already exists it is overwritten. - - http://redis.io/commands/rename - - - - Renames key to newkey if newkey does not yet exist. It returns an error under the same conditions as RENAME. - - 1 if key was renamed to newkey. 0 if newkey already exists. - http://redis.io/commands/renamenx - - - - Returns the remaining time to live (seconds) of a key that has a timeout. This introspection capability allows a Redis client to check how many seconds a given key will continue to be part of the dataset. - - TTL in seconds or -1 when key does not exist or does not have a timeout. - http://redis.io/commands/ttl - - - - Returns the string representation of the type of the value stored at key. The different types that can be returned are: string, list, set, zset and hash. - - type of key, or none when key does not exist. - http://redis.io/commands/type - - - - Return the number of keys in the currently selected database. - - http://redis.io/commands/dbsize - - - - Returns or stores the elements contained in the list, set or sorted set at key. By default, sorting is numeric and elements are compared by their value interpreted as double precision floating point number. - - http://redis.io/commands/sort - - - - Returns or stores the elements contained in the list, set or sorted set at key. By default, sorting is numeric and elements are compared by their value interpreted as double precision floating point number. - - http://redis.io/commands/sort - - - - Returns the raw DEBUG OBJECT output for a key; this command is not fully documented and should be avoided unless you have good reason, and then avoided anyway. - - http://redis.io/commands/debug-object - - - - Commands that apply to key/sub-key/value tuples, i.e. where - the item is a dictionary of inner values. This can be useful for - modelling members of an entity, for example. - - http://redis.io/commands#hash - - - - Removes the specified fields from the hash stored at key. Non-existing fields are ignored. Non-existing keys are treated as empty hashes and this command returns 0. - - http://redis.io/commands/hdel - The number of fields that were removed. - - - - Removes the specified fields from the hash stored at key. Non-existing fields are ignored. Non-existing keys are treated as empty hashes and this command returns 0. - - http://redis.io/commands/hdel - The number of fields that were removed. - - - - Returns if field is an existing field in the hash stored at key. - - 1 if the hash contains field. 0 if the hash does not contain field, or key does not exist. - http://redis.io/commands/hexists - - - - Returns the value associated with field in the hash stored at key. - - the value associated with field, or nil when field is not present in the hash or key does not exist. - http://redis.io/commands/hget - - - - Returns the value associated with field in the hash stored at key. - - the value associated with field, or nil when field is not present in the hash or key does not exist. - http://redis.io/commands/hget - - - - Returns the value associated with field in the hash stored at key. - - the value associated with field, or nil when field is not present in the hash or key does not exist. - http://redis.io/commands/hget - - - - Returns the values associated with the specified fields in the hash stored at key. For every field that does not exist in the hash, a nil value is returned. - - list of values associated with the given fields, in the same order as they are requested. - http://redis.io/commands/hmget - - - - Returns the values associated with the specified fields in the hash stored at key. For every field that does not exist in the hash, a nil value is returned. - - list of values associated with the given fields, in the same order as they are requested. - http://redis.io/commands/hmget - - - - Returns all fields and values of the hash stored at key. - - list of fields and their values stored in the hash, or an empty list when key does not exist. - http://redis.io/commands/hgetall - - - - Increments the number stored at field in the hash stored at key by increment. If key does not exist, a new key holding a hash is created. If field does not exist or holds a string that cannot be interpreted as integer, the value is set to 0 before the operation is performed. - - The range of values supported by HINCRBY is limited to 64 bit signed integers. - the value at field after the increment operation. - http://redis.io/commands/hincrby - - - - Increments the number stored at field in the hash stored at key by increment. If key does not exist, a new key holding a hash is created. If field does not exist or holds a string that cannot be interpreted as integer, the value is set to 0 before the operation is performed. - - The range of values supported by HINCRBY is limited to 64 bit signed integers. - the value at field after the increment operation. - http://redis.io/commands/hincrby - - - - Returns all field names in the hash stored at key. - - list of fields in the hash, or an empty list when key does not exist. - http://redis.io/commands/hkeys - - - - Returns all values in the hash stored at key. - - list of values in the hash, or an empty list when key does not exist. - http://redis.io/commands/hvals - - - - Returns the number of fields contained in the hash stored at key. - - number of fields in the hash, or 0 when key does not exist. - http://redis.io/commands/hlen - - - - Sets field in the hash stored at key to value. If key does not exist, a new key holding a hash is created. If field already exists in the hash, it is overwritten. - - 1 if field is a new field in the hash and value was set. 0 if field already exists in the hash and the value was updated. - http://redis.io/commands/hset - - - - Sets field in the hash stored at key to value. If key does not exist, a new key holding a hash is created. If field already exists in the hash, it is overwritten. - - 1 if field is a new field in the hash and value was set. 0 if field already exists in the hash and the value was updated. - http://redis.io/commands/hset - - - - Sets the specified fields to their respective values in the hash stored at key. This command overwrites any existing fields in the hash. If key does not exist, a new key holding a hash is created. - - 1 if field is a new field in the hash and value was set. 0 if field already exists in the hash and the value was updated. - http://redis.io/commands/hmset - - - - Sets field in the hash stored at key to value, only if field does not yet exist. If key does not exist, a new key holding a hash is created. If field already exists, this operation has no effect. - - 1 if field is a new field in the hash and value was set. 0 if field already exists in the hash and no operation was performed. - http://redis.io/commands/hsetnx - - - - Sets field in the hash stored at key to value, only if field does not yet exist. If key does not exist, a new key holding a hash is created. If field already exists, this operation has no effect. - - 1 if field is a new field in the hash and value was set. 0 if field already exists in the hash and no operation was performed. - http://redis.io/commands/hsetnx - - - - The HSCAN command is used in order to incrementally iterate over a collection of elements. - - http://redis.io/commands/hscan - - - - The HSCAN command is used in order to incrementally iterate over a collection of elements. - - http://redis.io/commands/hscan - - - - Commands that apply to key/value pairs, where the value - can be a string, a BLOB, or interpreted as a number - - http://redis.io/commands#string - - - - If key already exists and is a string, this command appends the value at the end of the string. If key does not exist it is created and set as an empty string, so APPEND will be similar to SET in this special case. - - the length of the string after the append operation. - http://redis.io/commands/append - - - - If key already exists and is a string, this command appends the value at the end of the string. If key does not exist it is created and set as an empty string, so APPEND will be similar to SET in this special case. - - the length of the string after the append operation. - http://redis.io/commands/append - - - - Decrements the number stored at key by decrement. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that is not representable as integer. This operation is limited to 64 bit signed integers. - - the value of key after the increment - http://redis.io/commands/decrby - http://redis.io/commands/decr - - - - Increments the number stored at key by increment. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that is not representable as integer. This operation is limited to 64 bit signed integers. - - the value of key after the increment - http://redis.io/commands/incrby - http://redis.io/commands/incr - - - - Increments the number stored at key by increment. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that is not representable as integer. This operation is limited to 64 bit signed integers. - - the value of key after the increment - http://redis.io/commands/incrbyfloat - - - - Get the value of key. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values. - - the value of key, or nil when key does not exist. - http://redis.io/commands/get - - - - Get the value of key. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values. - - the value of key, or nil when key does not exist. - http://redis.io/commands/get - - - - Get the value of key. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values. - - the value of key, or nil when key does not exist. - http://redis.io/commands/get - - - - Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive). - - Negative offsets can be used in order to provide an offset starting from the end of the string. So -1 means the last character, -2 the penultimate and so forth. The function handles out of range requests by limiting the resulting range to the actual length of the string. - the value of key, or nil when key does not exist. - http://redis.io/commands/getrange - - - - Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive). - - Negative offsets can be used in order to provide an offset starting from the end of the string. So -1 means the last character, -2 the penultimate and so forth. The function handles out of range requests by limiting the resulting range to the actual length of the string. - the value of key, or nil when key does not exist. - http://redis.io/commands/getrange - - - - Returns the values of all specified keys. For every key that does not hold a string value or does not exist, the special value nil is returned. Because of this, the operation never fails. - - list of values at the specified keys. - http://redis.io/commands/mget - - - - Returns the values of all specified keys. For every key that does not hold a string value or does not exist, the special value nil is returned. Because of this, the operation never fails. - - list of values at the specified keys. - http://redis.io/commands/mget - - - - Atomically sets key to value and returns the old value stored at key. Returns an error when key exists but does not hold a string value. - - the old value stored at key, or nil when key did not exist. - http://redis.io/commands/getset - - - - Atomically sets key to value and returns the old value stored at key. Returns an error when key exists but does not hold a string value. - - the old value stored at key, or nil when key did not exist. - http://redis.io/commands/getset - - - - Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type. - - http://redis.io/commands/set - - - - Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type. - - http://redis.io/commands/set - - - - Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type. - - http://redis.io/commands/set - - - - Set key to hold the string value and set key to timeout after a given number of seconds. - - http://redis.io/commands/setex - - - - Set key to hold the string value and set key to timeout after a given number of seconds. - - http://redis.io/commands/setex - - - - Overwrites part of the string stored at key, starting at the specified offset, for the entire length of value. If the offset is larger than the current length of the string at key, the string is padded with zero-bytes to make offset fit. Non-existing keys are considered as empty strings, so this command will make sure it holds a string large enough to be able to set value at offset. - - Note that the maximum offset that you can set is 229 -1 (536870911), as Redis Strings are limited to 512 megabytes. If you need to grow beyond this size, you can use multiple keys. - Warning: When setting the last possible byte and the string value stored at key does not yet hold a string value, or holds a small string value, Redis needs to allocate all intermediate memory which can block the server for some time. On a 2010 MacBook Pro, setting byte number 536870911 (512MB allocation) takes ~300ms, setting byte number 134217728 (128MB allocation) takes ~80ms, setting bit number 33554432 (32MB allocation) takes ~30ms and setting bit number 8388608 (8MB allocation) takes ~8ms. Note that once this first allocation is done, subsequent calls to SETRANGE for the same key will not have the allocation overhead. - http://redis.io/commands/setrange - the length of the string after it was modified by the command. - - - - Overwrites part of the string stored at key, starting at the specified offset, for the entire length of value. If the offset is larger than the current length of the string at key, the string is padded with zero-bytes to make offset fit. Non-existing keys are considered as empty strings, so this command will make sure it holds a string large enough to be able to set value at offset. - - Note that the maximum offset that you can set is 229 -1 (536870911), as Redis Strings are limited to 512 megabytes. If you need to grow beyond this size, you can use multiple keys. - Warning: When setting the last possible byte and the string value stored at key does not yet hold a string value, or holds a small string value, Redis needs to allocate all intermediate memory which can block the server for some time. On a 2010 MacBook Pro, setting byte number 536870911 (512MB allocation) takes ~300ms, setting byte number 134217728 (128MB allocation) takes ~80ms, setting bit number 33554432 (32MB allocation) takes ~30ms and setting bit number 8388608 (8MB allocation) takes ~8ms. Note that once this first allocation is done, subsequent calls to SETRANGE for the same key will not have the allocation overhead. - http://redis.io/commands/setrange - the length of the string after it was modified by the command. - - - - Sets the given keys to their respective values. MSET replaces existing values with new values, just as regular SET. See MSETNX if you don't want to overwrite existing values. - - MSET is atomic, so all given keys are set at once. It is not possible for clients to see that some of the keys were updated while others are unchanged. - http://redis.io/commands/mset - - - - Sets the given keys to their respective values. MSET replaces existing values with new values, just as regular SET. See MSETNX if you don't want to overwrite existing values. - - MSET is atomic, so all given keys are set at once. It is not possible for clients to see that some of the keys were updated while others are unchanged. - http://redis.io/commands/mset - - - - Sets the given keys to their respective values. MSETNX will not perform any operation at all even if just a single key already exists. - - Because of this semantic MSETNX can be used in order to set different keys representing different fields of an unique logic object in a way that ensures that either all the fields or none at all are set. - MSETNX is atomic, so all given keys are set at once. It is not possible for clients to see that some of the keys were updated while others are unchanged. - 1 if the all the keys were set, 0 if no key was set (at least one key already existed). - http://redis.io/commands/msetnx - - - - Sets the given keys to their respective values. MSETNX will not perform any operation at all even if just a single key already exists. - - Because of this semantic MSETNX can be used in order to set different keys representing different fields of an unique logic object in a way that ensures that either all the fields or none at all are set. - MSETNX is atomic, so all given keys are set at once. It is not possible for clients to see that some of the keys were updated while others are unchanged. - 1 if the all the keys were set, 0 if no key was set (at least one key already existed). - http://redis.io/commands/msetnx - - - - Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. - - 1 if the key was set, 0 if the key was not set - http://redis.io/commands/setnx - - - - Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. - - 1 if the key was set, 0 if the key was not set - http://redis.io/commands/setnx - - - - Returns the bit value at offset in the string value stored at key. - - When offset is beyond the string length, the string is assumed to be a contiguous space with 0 bits. When key does not exist it is assumed to be an empty string, so offset is always out of range and the value is also assumed to be a contiguous space with 0 bits. - the bit value stored at offset. - http://redis.io/commands/getbit - - - - Returns the length of the string value stored at key. An error is returned when key holds a non-string value. - - the length of the string at key, or 0 when key does not exist. - http://redis.io/commands/strlen - - - - Sets or clears the bit at offset in the string value stored at key. - - The bit is either set or cleared depending on value, which can be either 0 or 1. When key does not exist, a new string value is created. The string is grown to make sure it can hold a bit at offset. The offset argument is required to be greater than or equal to 0, and smaller than 232 (this limits bitmaps to 512MB). When the string at key is grown, added bits are set to 0. - - Warning: When setting the last possible bit (offset equal to 232 -1) and the string value stored at key does not yet hold a string value, or holds a small string value, Redis needs to allocate all intermediate memory which can block the server for some time. On a 2010 MacBook Pro, setting bit number 232 -1 (512MB allocation) takes ~300ms, setting bit number 230 -1 (128MB allocation) takes ~80ms, setting bit number 228 -1 (32MB allocation) takes ~30ms and setting bit number 226 -1 (8MB allocation) takes ~8ms. Note that once this first allocation is done, subsequent calls to SETBIT for the same key will not have the allocation overhead. - - the original bit value stored at offset. - http://redis.io/commands/setbit - - - - Count the number of set bits (population counting) in a string. - By default all the bytes contained in the string are examined. It is possible to specify the counting operation only in an interval passing the additional arguments start and end. - Like for the GETRANGE command start and end can contain negative values in order to index bytes starting from the end of the string, where -1 is the last byte, -2 is the penultimate, and so forth. - - http://redis.io/commands/bitcount - - - - Perform a bitwise AND operation between multiple keys (containing string values) and store the result in the destination key. - - The size of the string stored in the destination key, that is equal to the size of the longest input string. - http://redis.io/commands/bitop - - - - Perform a bitwise OR operation between multiple keys (containing string values) and store the result in the destination key. - - The size of the string stored in the destination key, that is equal to the size of the longest input string. - http://redis.io/commands/bitop - - - - Perform a bitwise XOR operation between multiple keys (containing string values) and store the result in the destination key. - - The size of the string stored in the destination key, that is equal to the size of the longest input string. - http://redis.io/commands/bitop - - - - Perform a bitwise NOT operation on a key (containing a string value) and store the result in the destination key. - - The size of the string stored in the destination key, that is equal to the size of the longest input string. - http://redis.io/commands/bitop - - - - This is a composite helper command, to help with using redis as a lock provider. This is achieved - as a string key/value pair with timeout. If the lock does not exist (or has expired), then a new string key is - created (with the supplied duration), and true is returned to indicate success. If the lock - already exists, then no lock is taken, and false is returned. The value may be fetched separately, but the meaning is - implementation-defined). No change is made if the - lock was not successfully taken. In this case, the client should delay and retry. - - It is expected that a well-behaved client will also release the lock in a timely fashion via ReleaseLock. - - null if the lock was successfully taken; the competing value otherwise - It transpires that robust locking in redis is actually remarkably hard, and most - implementations are broken in one way or another (most commonly: thread-race, or extending the - lock duration when failing to take the lock). - - - - Releases a lock that was taken successfully via TakeLock. You should not release a lock that you did - not take, as this will cause problems. - - - - - Releases a lock that was taken successfully via TakeLock. The value is checked for correctness. - - - - - Extends a lock that was taken successfully via TakeLock. The value is checked for correctness. - - - - - Commands that apply to basic lists of items per key; lists - preserve insertion order and have no enforced uniqueness (duplicates - are allowed) - - http://redis.io/commands#list - - - - Inserts value in the list stored at key either before or after the reference value pivot. - - When key does not exist, it is considered an empty list and no operation is performed. - the length of the list after the insert operation, or -1 when the value pivot was not found. - http://redis.io/commands/linsert - - - - Inserts value in the list stored at key either before or after the reference value pivot. - - When key does not exist, it is considered an empty list and no operation is performed. - the length of the list after the insert operation, or -1 when the value pivot was not found. - http://redis.io/commands/linsert - - - - Inserts value in the list stored at key either before or after the reference value pivot. - - When key does not exist, it is considered an empty list and no operation is performed. - the length of the list after the insert operation, or -1 when the value pivot was not found. - http://redis.io/commands/linsert - - - - Inserts value in the list stored at key either before or after the reference value pivot. - - When key does not exist, it is considered an empty list and no operation is performed. - the length of the list after the insert operation, or -1 when the value pivot was not found. - http://redis.io/commands/linsert - - - - Returns the element at index index in the list stored at key. The index is zero-based, so 0 means the first element, 1 the second element and so on. Negative indices can be used to designate elements starting at the tail of the list. Here, -1 means the last element, -2 means the penultimate and so forth. - - the requested element, or nil when index is out of range. - http://redis.io/commands/lindex - - - - Returns the element at index index in the list stored at key. The index is zero-based, so 0 means the first element, 1 the second element and so on. Negative indices can be used to designate elements starting at the tail of the list. Here, -1 means the last element, -2 means the penultimate and so forth. - - the requested element, or nil when index is out of range. - http://redis.io/commands/lindex - - - - Sets the list element at index to value. For more information on the index argument, see LINDEX. - - An error is returned for out of range indexes. - http://redis.io/commands/lset - - - - Sets the list element at index to value. For more information on the index argument, see LINDEX. - - An error is returned for out of range indexes. - http://redis.io/commands/lset - - - - Returns the length of the list stored at key. If key does not exist, it is interpreted as an empty list and 0 is returned. - - the length of the list at key. - http://redis.io/commands/llen - - - - Removes and returns the first element of the list stored at key. - - the value of the first element, or nil when key does not exist. - http://redis.io/commands/lpop - - - - Removes and returns the first element of the list stored at key. - - the value of the first element, or nil when key does not exist. - http://redis.io/commands/lpop - - - - Removes and returns the last element of the list stored at key. - - the value of the first element, or nil when key does not exist. - http://redis.io/commands/rpop - - - - Removes and returns the last element of the list stored at key. - - the value of the first element, or nil when key does not exist. - http://redis.io/commands/rpop - - - IMPORTANT: blocking commands will interrupt multiplexing, and should not be used on a connection being used by parallel consumers. - BLPOP is a blocking list pop primitive. It is the blocking version of LPOP because it blocks the connection when there are no elements to pop from any of the given lists. An element is popped from the head of the first list that is non-empty, with the given keys being checked in the order that they are given. A timeout of zero can be used to block indefinitely. - - A null when no element could be popped and the timeout expired, otherwise the popped element. - http://redis.io/commands/blpop - - - IMPORTANT: blocking commands will interrupt multiplexing, and should not be used on a connection being used by parallel consumers. - BLPOP is a blocking list pop primitive. It is the blocking version of LPOP because it blocks the connection when there are no elements to pop from any of the given lists. An element is popped from the head of the first list that is non-empty, with the given keys being checked in the order that they are given. A timeout of zero can be used to block indefinitely. - - A null when no element could be popped and the timeout expired, otherwise the popped element. - http://redis.io/commands/blpop - - - IMPORTANT: blocking commands will interrupt multiplexing, and should not be used on a connection being used by parallel consumers. - BRPOP is a blocking list pop primitive. It is the blocking version of RPOP because it blocks the connection when there are no elements to pop from any of the given lists. An element is popped from the tail of the first list that is non-empty, with the given keys being checked in the order that they are given. A timeout of zero can be used to block indefinitely. - - A null when no element could be popped and the timeout expired, otherwise the popped element. - http://redis.io/commands/brpop - - - IMPORTANT: blocking commands will interrupt multiplexing, and should not be used on a connection being used by parallel consumers. - BRPOP is a blocking list pop primitive. It is the blocking version of RPOP because it blocks the connection when there are no elements to pop from any of the given lists. An element is popped from the tail of the first list that is non-empty, with the given keys being checked in the order that they are given. A timeout of zero can be used to block indefinitely. - - A null when no element could be popped and the timeout expired, otherwise the popped element. - http://redis.io/commands/brpop - - - IMPORTANT: blocking commands will interrupt multiplexing, and should not be used on a connection being used by parallel consumers. - BRPOPLPUSH is the blocking variant of RPOPLPUSH. When source contains elements, this command behaves exactly like RPOPLPUSH. When source is empty, Redis will block the connection until another client pushes to it or until timeout is reached. A timeout of zero can be used to block indefinitely. - - For example: consider source holding the list a,b,c, and destination holding the list x,y,z. Executing RPOPLPUSH results in source holding a,b and destination holding c,x,y,z. - If source does not exist, the value nil is returned and no operation is performed. If source and destination are the same, the operation is equivalent to removing the last element from the list and pushing it as first element of the list, so it can be considered as a list rotation command. - the element being popped and pushed. - http://redis.io/commands/brpoplpush - - - IMPORTANT: blocking commands will interrupt multiplexing, and should not be used on a connection being used by parallel consumers. - BRPOPLPUSH is the blocking variant of RPOPLPUSH. When source contains elements, this command behaves exactly like RPOPLPUSH. When source is empty, Redis will block the connection until another client pushes to it or until timeout is reached. A timeout of zero can be used to block indefinitely. - - For example: consider source holding the list a,b,c, and destination holding the list x,y,z. Executing RPOPLPUSH results in source holding a,b and destination holding c,x,y,z. - If source does not exist, the value nil is returned and no operation is performed. If source and destination are the same, the operation is equivalent to removing the last element from the list and pushing it as first element of the list, so it can be considered as a list rotation command. - the element being popped and pushed. - http://redis.io/commands/brpoplpush - - - - Inserts value at the head of the list stored at key. If key does not exist and createIfMissing is true, it is created as empty list before performing the push operation. - - the length of the list after the push operation. - http://redis.io/commands/lpush - http://redis.io/commands/lpushx - - - - Inserts value at the head of the list stored at key. If key does not exist and createIfMissing is true, it is created as empty list before performing the push operation. - - the length of the list after the push operation. - http://redis.io/commands/lpush - http://redis.io/commands/lpushx - - - - Inserts value at the tail of the list stored at key. If key does not exist and createIfMissing is true, it is created as empty list before performing the push operation. - - the length of the list after the push operation. - http://redis.io/commands/rpush - http://redis.io/commands/rpushx - - - - Inserts value at the tail of the list stored at key. If key does not exist and createIfMissing is true, it is created as empty list before performing the push operation. - - the length of the list after the push operation. - http://redis.io/commands/rpush - http://redis.io/commands/rpushx - - - - Removes the first count occurrences of elements equal to value from the list stored at key. - - The count argument influences the operation in the following ways: - count > 0: Remove elements equal to value moving from head to tail. - count < 0: Remove elements equal to value moving from tail to head. - count = 0: Remove all elements equal to value. - For example, LREM list -2 "hello" will remove the last two occurrences of "hello" in the list stored at list. - the number of removed elements. - http://redis.io/commands/lrem - - - - Removes the first count occurrences of elements equal to value from the list stored at key. - - The count argument influences the operation in the following ways: - count > 0: Remove elements equal to value moving from head to tail. - count < 0: Remove elements equal to value moving from tail to head. - count = 0: Remove all elements equal to value. - For example, LREM list -2 "hello" will remove the last two occurrences of "hello" in the list stored at list. - the number of removed elements. - http://redis.io/commands/lrem - - - - Trim an existing list so that it will contain only the specified range of elements specified. Both start and stop are zero-based indexes, where 0 is the first element of the list (the head), 1 the next element and so on. - start and end can also be negative numbers indicating offsets from the end of the list, where -1 is the last element of the list, -2 the penultimate element and so on. - - For example: LTRIM foobar 0 2 will modify the list stored at foobar so that only the first three elements of the list will remain. - Out of range indexes will not produce an error: if start is larger than the end of the list, or start > end, the result will be an empty list (which causes key to be removed). If end is larger than the end of the list, Redis will treat it like the last element of the list. - http://redis.io/commands/ltrim - - - - Trim an existing list so that it will contain only the specified count. - - http://redis.io/commands/ltrim - - - - Atomically returns and removes the last element (tail) of the list stored at source, and pushes the element at the first element (head) of the list stored at destination. - - For example: consider source holding the list a,b,c, and destination holding the list x,y,z. Executing RPOPLPUSH results in source holding a,b and destination holding c,x,y,z. - If source does not exist, the value nil is returned and no operation is performed. If source and destination are the same, the operation is equivalent to removing the last element from the list and pushing it as first element of the list, so it can be considered as a list rotation command. - the element being popped and pushed. - http://redis.io/commands/rpoplpush - - - - Atomically returns and removes the last element (tail) of the list stored at source, and pushes the element at the first element (head) of the list stored at destination. - - For example: consider source holding the list a,b,c, and destination holding the list x,y,z. Executing RPOPLPUSH results in source holding a,b and destination holding c,x,y,z. - If source does not exist, the value nil is returned and no operation is performed. If source and destination are the same, the operation is equivalent to removing the last element from the list and pushing it as first element of the list, so it can be considered as a list rotation command. - the element being popped and pushed. - http://redis.io/commands/rpoplpush - - - - Returns the specified elements of the list stored at key. The offsets start and end are zero-based indexes, with 0 being the first element of the list (the head of the list), 1 being the next element and so on. - - These offsets can also be negative numbers indicating offsets starting at the end of the list. For example, -1 is the last element of the list, -2 the penultimate, and so on. - list of elements in the specified range. - http://redis.io/commands/lrange - - - - Returns the specified elements of the list stored at key. The offsets start and end are zero-based indexes, with 0 being the first element of the list (the head of the list), 1 being the next element and so on. - - These offsets can also be negative numbers indicating offsets starting at the end of the list. For example, -1 is the last element of the list, -2 the penultimate, and so on. - list of elements in the specified range. - http://redis.io/commands/lrange - - - - Delete all the keys of the currently selected DB. - - - - - Delete all the keys of all the existing databases, not just the currently selected one. - - - - - This command is often used to test if a connection is still alive, or to measure latency. - - The latency in milliseconds. - http://redis.io/commands/ping - - - - See SortedSets.Add - - - - - See SortedSets.Add - - - - - See SortedSets.GetLength - - - - - See SortedSets.Increment - - - - - See SortedSets.Increment - - - - - See SortedSets.Increment - - - - - See SortedSets.Increment - - - - - See SortedSets.GetRange - - - - - See SortedSets.GetRange - - - - - See SortedSets.RemoveRange - - - - - Add an item to a set - - - - - Add an item to a set - - - - - Returns the number of items in a set - - - - - Intersect multiple sets - - - - - Union multiple sets - - - - - Intersect multiple sets, storing the result - - - - - Intersect multiple sets, storing the result - - - - - Is the given value in the set? - - - - - Is the given value in the set? - - - - - Gets all members of a set - - - - - Remove an item from a set - - - - - Removes a key from the database. - - - - Removes multiple keys from the database. - - - - Returns if key exists. - - - - - Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is said to be volatile in Redis terminology. - - - - - Remove the existing timeout on key. - - - - - Returns all keys matching pattern. - - - - - Returns all keys matching pattern. - - - - - Move key from the currently selected database to the specified destination database. - - - - - Return a random key from the currently selected database. - - the random key, or nil when the database is empty. - http://redis.io/commands/randomkey - - - - Renames a key in the database, overwriting any existing value; the source key must exist and be different to the destination. - - - - - Renames a key in the database, overwriting any existing value; the source key must exist and be different to the destination. - - - - - Returns the remaining time to live (seconds) of a key that has a timeout. - - - - - Enumerate all keys in a hash. - - - - - Returns all fields and values of the hash stored at key. - - list of fields and their values stored in the hash, or an empty list when key does not exist. - - - - Increment a field on a hash by an amount (1 by default) - - - - - Sets field in the hash stored at key to value. If key does not exist, a new key holding a hash is created. If field already exists in the hash, it is overwritten. - - 1 if field is a new field in the hash and value was set. 0 if field already exists in the hash and the value was updated. - - - - Sets the specified fields to their respective values in the hash stored at key. This command overwrites any existing fields in the hash. If key does not exist, a new key holding a hash is created. - - - - - Sets field in the hash stored at key to value. If key does not exist, a new key holding a hash is created. If field already exists in the hash, it is overwritten. - - 1 if field is a new field in the hash and value was set. 0 if field already exists in the hash and the value was updated. - - - - Sets field in the hash stored at key to value, only if field does not yet exist. If key does not exist, a new key holding a hash is created. If field already exists, this operation has no effect. - - 1 if field is a new field in the hash and value was set. 0 if field already exists in the hash and no operation was performed. - - - - Sets field in the hash stored at key to value, only if field does not yet exist. If key does not exist, a new key holding a hash is created. If field already exists, this operation has no effect. - - 1 if field is a new field in the hash and value was set. 0 if field already exists in the hash and no operation was performed. - - - - Returns the value associated with field in the hash stored at key. - - the value associated with field, or nil when field is not present in the hash or key does not exist. - - - - Returns the value associated with field in the hash stored at key. - - the value associated with field, or nil when field is not present in the hash or key does not exist. - - - - Returns the values associated with the specified fields in the hash stored at key. For every field that does not exist in the hash, a nil value is returned. - - list of values associated with the given fields, in the same order as they are requested. - - - - Returns the values associated with the specified fields in the hash stored at key. For every field that does not exist in the hash, a nil value is returned. - - list of values associated with the given fields, in the same order as they are requested. - - - - Removes the specified field from the hash stored at key. Non-existing fields are ignored. Non-existing keys are treated as empty hashes and this command returns 0. - - - - - Removes the specified fields from the hash stored at key. Non-existing fields are ignored. Non-existing keys are treated as empty hashes and this command returns 0. - - - - - Returns if field is an existing field in the hash stored at key. - - 1 if the hash contains field. 0 if the hash does not contain field, or key does not exist. - - - - Returns all field names in the hash stored at key. - - list of fields in the hash, or an empty list when key does not exist. - - - - Returns all values in the hash stored at key. - - list of values in the hash, or an empty list when key does not exist. - - - - Returns the number of fields contained in the hash stored at key. - - number of fields in the hash, or 0 when key does not exist. - - - - See Strings.Get - - - - - See Strings.GetString - - - - - See Strings.Increment - - - - - See Strings.Increment - - - - - See Strings.Decrement - - - - - See Strings.Decrement - - - - - See Strings.Set - - - - - See Strings.Set - - - - - See Strings.SetIfNotExists - - - - - See Strings.SetIfNotExists - - - - - See Strings.Append - - - - - See Strings.Append - - - - - See Strings.Set - - - - - See Strings.Set - - - - - Returns the element at index index in the list stored at key. The index is zero-based, so 0 means the first element, 1 the second element and so on. Negative indices can be used to designate elements starting at the tail of the list. Here, -1 means the last element, -2 means the penultimate and so forth. - - the requested element, or nil when index is out of range. - - - - Returns the element at index index in the list stored at key. The index is zero-based, so 0 means the first element, 1 the second element and so on. Negative indices can be used to designate elements starting at the tail of the list. Here, -1 means the last element, -2 means the penultimate and so forth. - - the requested element, or nil when index is out of range. - - - - Query the number of items in a list - - The database to operate on - The key of the list - Whether to overtake unsent messages - The number of items in the list, or 0 if it does not exist - - - - Removes an item from the start of a list - - The database to operatate on - The list to remove an item from - Whether to overtake unsent messages - The contents of the item removed, or null if empty - - - - Removes an item from the end of a list - - The database to operatate on - The list to remove an item from - Whether to overtake unsent messages - The contents of the item removed, or null if empty - - - - Prepend an item to a list - - The database to operate on - The key of the list - The item to add - Whether to overtake unsent messages - The number of items now in the list - - - - Prepend an item to a list - - The database to operate on - The key of the list - The item to add - Whether to overtake unsent messages - The number of items now in the list - - - - Append an item to a list - - The database to operate on - The key of the list - The item to add - Whether to overtake unsent messages - The number of items now in the list - - - - Append an item to a list - - The database to operate on - The key of the list - The item to add - Whether to overtake unsent messages - The number of items now in the list - - - - See Lists.RemoveLastAndAddFirst - - - - - See Lists.Range - - - - - Creates a new RedisConnection to a designated server - - - - - Creates a child RedisConnection, such as for a RedisTransaction - - - - - Allows multiple commands to be buffered and sent to redis as a single atomic unit - - - - - Allows multiple commands to be buffered and sent to redis collectively, but without any guarantee of atomicity - - - - - Configures an automatic keep-alive PING at a pre-determined interval; this is especially - useful if CONFIG GET is not available. - - - - - The message to supply to callers when rejecting messages - - - - - Closes the connection; either draining the unsent queue (to completion), or abandoning the unsent queue. - - - - - Called during connection init, but after the AUTH is sent (if needed) - - - - - Creates a pub/sub connection to the same redis server - - - - - Releases any resources associated with the connection - - - - - Query usage metrics for this connection - - - - - Query usage metrics for this connection - - - - - Give some information about the oldest incomplete (but sent) message on the server - - - - - Takes a server out of "slave" mode, to act as a replication master. - - - - - Temporarily suspends eager-flushing (flushing if the write-queue becomes empty briefly). Buffer-based flushing - will still occur when the data is full. This is useful if you are performing a large number of - operations in close duration, and want to avoid packet fragmentation. Note that you MUST call - ResumeFlush at the end of the operation - preferably using Try/Finally so that flushing is resumed - even upon error. This method is thread-safe; any number of callers can suspend/resume flushing - concurrently - eager flushing will resume fully when all callers have called ResumeFlush. - - Note that some operations (transaction conditions, etc) require flushing - this will still - occur even if the buffer is only part full. - - - - Resume eager-flushing (flushing if the write-queue becomes empty briefly). See SuspendFlush for - full usage. - - - - - Posts a message to the given channel. - - the number of clients that received the message. - - - - Posts a message to the given channel. - - the number of clients that received the message. - - - - Commands that apply to Redis scripting (Lua). - - http://redis.io/commands#scripting - - - - Commands related to server operation and configuration, rather than data. - - http://redis.io/commands#server - - - - Commands that apply to sorted sets per key. A sorted set keeps a "score" - per element, and this score is used to order the elements. Duplicates - are not allowed (typically, the score of the duplicate is added to the - pre-existing element instead). - - http://redis.io/commands#sorted_set - - - - Commands that apply to sets of items per key; sets - have no defined order and are strictly unique. Duplicates - are not allowed (typically, duplicates are silently discarded). - - http://redis.io/commands#set - - - - Generic commands that apply to all/most data structures - - http://redis.io/commands#generic - - - - Commands that apply to key/sub-key/value tuples, i.e. where - the item is a dictionary of inner values. This can be useful for - modelling members of an entity, for example. - - http://redis.io/commands#hash - - - - Commands that apply to key/value pairs, where the value - can be a string, a BLOB, or interpreted as a number - - http://redis.io/commands#string - - - - Commands that apply to basic lists of items per key; lists - preserve insertion order and have no enforced uniqueness (duplicates - are allowed) - - http://redis.io/commands#list - - - - How frequently should keep-alives be sent? - - - - - Time (in milliseconds) since the last command was sent - - - - - Indicates the number of messages that have not yet been sent to the server. - - - - - Constants representing the different storage devices in redis - - - - - Returned for a key that does not exist - - - - - Redis Lists are simply lists of strings, sorted by insertion order. It is possible to add elements to a Redis List pushing new elements on the head (on the left) or on the tail (on the right) of the list. - - http://redis.io/topics/data-types#lists - - - - Strings are the most basic kind of Redis value. Redis Strings are binary safe, this means that a Redis string can contain any kind of data, for instance a JPEG image or a serialized Ruby object. - - http://redis.io/topics/data-types#strings - - - - Redis Sets are an unordered collection of Strings. It is possible to add, remove, and test for existence of members in O(1) (constant time regardless of the number of elements contained inside the Set). - - http://redis.io/topics/data-types#sets - - - - Redis Sorted Sets are, similarly to Redis Sets, non repeating collections of Strings. The difference is that every member of a Sorted Set is associated with score, that is used in order to take the sorted set ordered, from the smallest to the greatest score. - - http://redis.io/topics/data-types#sorted-sets - - - - Redis Hashes are maps between string field and string values, so they are the perfect data type to represent objects (for instance Users with a number of fields like name, surname, age, and so forth) - - http://redis.io/topics/data-types#hashes - - - - Event data relating to an exception in Redis - - - - - The exception that occurred - - - - - What the system was doing when this error occurred - - - - - True if this error has rendered the connection unusable - - - - - With the AGGREGATE option, it is possible to specify how the results of the union are aggregated. This option defaults to SUM, where the score of an element is summed across the inputs where it exists. When this option is set to either MIN or MAX, the resulting set will contain the minimum or maximum score of an element across the inputs where it exists. - - - - - The score of an element is summed across the inputs where it exists - - - - - The resulting set will contain the minimum score of an element across the inputs where it exists - - - - - The resulting set will contain the maximum score of an element across the inputs where it exists - - - - - What type of server does this represent - - - - - The server is not yet connected, or is not recognised - - - - - The server is a master node, suitable for read and write - - - - - The server is a replication slave, suitable for read - - - - - The server is a sentinel, used for anutomated configuration - and failover - - - - - The server is part of a cluster - - - - - Summary statistics for the RedisConnection - - - - - Obtain a string representation of the counters - - - - - - How frequently should keep-alives be sent? - - - - - Time (in milliseconds) since the last command was sent - - - - - Time (in milliseconds) since the last command was sent explicitly because of a keep-alive - - - - - The state of the server connection - - - - - The number of callbacks executed (total) synchronously - - - - - The number of callbacks executed (total) asynchronously - - - - - The number of callbacks executing (currently) synchronously - - - - - The number of callbacks executing (currently) asynchronously - - - - - The number of messages sent to the Redis server - - - - - The number of messages received from the Redis server - - - - - The number of queued messages that were withdrawn without being sent - - - - - The number of operations that timed out - - - - - The number of operations that were sent ahead of queued items - - - - - The number of messages waiting to be sent - - - - - The number of error messages received by the server - - - - - The number of messages that have been sent and are waiting for a response - - - - The current time (milliseconds) taken to send a Redis PING command and - receive a PONG reply - - - - - Provides basic information about the features available on a particular version of Redis - - - - - Create a new RedisFeatures instance for the given version - - - - - Create a string representation of the available features - - - - - The Redis version of the server - - - - - Is the PERSIST operation supported? - - - - - Can EXPIRE be used to set expiration on a key that is already volatile (i.e. has an expiration)? - - - - - Does HDEL support varadic usage? - - - - - Is STRLEN available? - - - - - Is SETRANGE available? - - - - - Is RPUSHX and LPUSHX available? - - - - - Does SADD support varadic usage? - - - - - Does INCRBYFLOAT / HINCRBYFLOAT exist? - - - - - Does EVAL / EVALSHA / etc exist? - - - - - Does SRANDMEMBER support "count"? - - - - - Does TIME exist? - - - - - Does BITOP / BITCOUNT exist? - - - - - Is LINSERT available? - - - - - Is CLIENT SETNAME available? - - - - - Does SET have the EX|PX|NX|XX extensions? - - - - - Are cursor-based scans available? - - - - - Represents a group of operations that will be sent to the server in a group - - - - - Not supported, as nested transactions are not available. - - - - - Not supported, as nested batches are not available. - - - - - Release any resources held by this transaction/batch. - - - - - Discards any buffered commands; the transaction/batch may subsequently be re-used to buffer additional blocks of commands if needed. - - - - - Called before opening a connection - - - - - Send the buffered commands - - - - - The underlying connection that this batch operates upon - - - - - Features available to the redis server - - - - - The version of the connected redis server - - - - - Should a QUIT be sent when closing the connection? - - - - - Represents a group of redis messages that will be sent as a single atomic - - - - - Sends all currently buffered commands to the redis server in a single unit; the transaction may subsequently be re-used to buffer additional blocks of commands if needed. - - - - - Sends all currently buffered commands to the redis server in a single unit; the transaction may subsequently be re-used to buffer additional blocks of commands if needed. - - - - - Add a precondition to be enforced for this transaction - - - - - A redis-related exception; this could represent a message from the server, - or a protocol error talking to the server. - - - - - Create a new RedisException - - - - - Create a new RedisException - - - - - Create a new RedisException - - - - - Create a new RedisException - - - - - A redis-related exception, where an attempt has been made to change a value on a readonly slave (2.6 or above) - - - - - Create a new RedisReadonlySlaveException - - - - - Create a new RedisReadonlySlaveException - - - - - Create a new RedisReadonlySlaveException - - - - - Provides a Redis connection for listening for (and handling) the subscriber part of a pub/sub implementation. - Messages are sent using RedisConnection.Publish. - - - - - Create a new RedisSubscriberConnection instance - - The server to connect to (IP address or name) - The port on the server to connect to; typically 3679 - The timeout to use during IO operations; this can usually be left unlimited - If the server is secured, the server password (null if not secured) - The maximum number of unsent messages to enqueue before new requests are blocking calls - - - - Called during connection init, but after the AUTH is sent (if needed) - - Whether to release any queued messages - - - - Invoked when we have completed the handshake - - - - - Subscribe to a channel - - The channel name - A callback to invoke when messages are received on this channel; - note that the MessageReceived event will also be raised, so this callback can be null. - Channels are server-wide; they are not per-database - - - - Subscribe to a set of channels - - The channel names - A callback to invoke when messages are received on these channel; - note that the MessageReceived event will also be raised, so this callback can be null. - Channels are server-wide; they are not per-database - - - - Subscribe to a set of pattern (using wildcards, for exmaple "Foo*") - - The pattern to subscribe - A callback to invoke when matching messages are received; this can be null - as the MessageReceived event will also be raised - Channels are server-wide, not per-database - - - - Subscribe to a set of patterns (using wildcards, for exmaple "Foo*") - - The patterns to subscribe - A callback to invoke when matching messages are received; this can be null - as the MessageReceived event will also be raised - Channels are server-wide, not per-database - - - - Unsubscribe from a channel - - The channel name - Channels are server-wide; they are not per-database - - - - Unsubscribe from a set of channels - - The channel names - Channels are server-wide; they are not per-database - - - - Unsubscribe from a pattern (which must match a pattern previously subscribed) - - The pattern to unsubscribe - Channels are server-wide, not per-database - - - - Unsubscribe from a set of patterns (which must match patterns previously subscribed) - - The patterns to unsubscribe - Channels are server-wide, not per-database - - - - This event is raised when a message is received on any subscribed channel; this is supplemental - to any direct callbacks specified. - - - - - Should a QUIT be sent when closing the connection? - - - - - The number of subscriptions currently help by the current connection (as reported by the server during the last - subsribe/unsubscribe operation) - - - - - Indicates how incoming messages should be completed / dispatched. - - - - - Results are always completed synchronously; this guarantees to preserve order, but means that long-running - continuations may block other operations from completing even when the data is available. - - - - - Results are dispatched asynchronously; no guarantee of order is offered. - - - - - The system will attempt to determine whether any given operation has a continuation; if it does, it will - dispatch it concurrently to avoid risk of blocking scenarios; if it does not, it will complete it - synchronously for performance. - - - - - Indicates the reason that a connection was shut down - - - - - The connection is not shut down - - - - - The connection was closed by the client calling close - - - - - The connection was closed by the client being disposed - - - - - The server closed the connection (EOF) - - - - - The connection was terminated due to an unexpected error - - - - - Utility classes for working safely with tasks - - - - - Create a task wrapper that is safe to use with "await", by avoiding callback-inlining - - - - - Create a task wrapper that is safe to use with "await", by avoiding callback-inlining - - - - diff --git a/packages/BookSleeve.1.3.41/lib/license.txt b/packages/BookSleeve.1.3.41/lib/license.txt deleted file mode 100644 index 5e77c7465..000000000 --- a/packages/BookSleeve.1.3.41/lib/license.txt +++ /dev/null @@ -1,12 +0,0 @@ -This implementation is Copyright 2011 Marc Gravell - -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 - - http://www.apache.org/licenses/LICENSE-2.0 - -This software is distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Redis is licensed separately; see http://redis.io/. diff --git a/packages/Microsoft.Bcl.1.1.10/License-Stable.rtf b/packages/Microsoft.Bcl.1.1.10/License-Stable.rtf deleted file mode 100644 index 3aec6b654..000000000 --- a/packages/Microsoft.Bcl.1.1.10/License-Stable.rtf +++ /dev/null @@ -1,118 +0,0 @@ -{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fswiss\fprq2\fcharset0 Tahoma;}{\f1\froman\fprq2\fcharset0 Times New Roman;}{\f2\fswiss\fprq2\fcharset0 Calibri;}{\f3\fnil\fcharset0 Calibri;}{\f4\fnil\fcharset2 Symbol;}} -{\colortbl ;\red31\green73\blue125;\red0\green0\blue255;} -{\*\listtable -{\list\listhybrid -{\listlevel\levelnfc0\leveljc0\levelstartat1{\leveltext\'02\'00.;}{\levelnumbers\'01;}\jclisttab\tx360} -{\listlevel\levelnfc4\leveljc0\levelstartat1{\leveltext\'02\'01.;}{\levelnumbers\'01;}\jclisttab\tx363} -{\listlevel\levelnfc2\leveljc0\levelstartat1{\leveltext\'02\'02.;}{\levelnumbers\'01;}\jclisttab\tx720}\listid1 } -{\list\listhybrid -{\listlevel\levelnfc0\leveljc0\levelstartat1{\leveltext\'02\'00.;}{\levelnumbers\'01;}\jclisttab\tx363} -{\listlevel\levelnfc4\leveljc0\levelstartat1{\leveltext\'02\'01.;}{\levelnumbers\'01;}\jclisttab\tx363}\listid2 }} -{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}} -{\stylesheet{ Normal;}{\s1 heading 1;}{\s2 heading 2;}{\s3 heading 3;}} -{\*\generator Riched20 6.2.9200}\viewkind4\uc1 -\pard\nowidctlpar\sb120\sa120\b\f0\fs24 MICROSOFT SOFTWARE LICENSE TERMS\par - -\pard\brdrb\brdrs\brdrw10\brsp20 \nowidctlpar\sb120\sa120 MICROSOFT .NET LIBRARY \par - -\pard\nowidctlpar\sb120\sa120\fs19 These license terms are an agreement between Microsoft Corporation (or based on where you live, one of its affiliates) and you. Please read them. They apply to the software named above, which includes the media on which you received it, if any. The terms also apply to any Microsoft\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent363{\pntxtb\'B7}}\nowidctlpar\fi-363\li720\sb120\sa120\b0 updates,\par -{\pntext\f4\'B7\tab}supplements,\par -{\pntext\f4\'B7\tab}Internet-based services, and\par -{\pntext\f4\'B7\tab}support services\par - -\pard\nowidctlpar\sb120\sa120\b for this software, unless other terms accompany those items. If so, those terms apply.\par -BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS. IF YOU DO NOT ACCEPT THEM, DO NOT USE THE SOFTWARE.\par - -\pard\brdrt\brdrs\brdrw10\brsp20 \nowidctlpar\sb120\sa120 IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE PERPETUAL RIGHTS BELOW.\par - -\pard -{\listtext\f0 1.\tab}\jclisttab\tx360\ls1\nowidctlpar\s1\fi-357\li357\sb120\sa120 INSTALLATION AND USE RIGHTS. \par - -\pard -{\listtext\f0 a.\tab}\jclisttab\tx363\ls1\ilvl1\nowidctlpar\s2\fi-363\li720\sb120\sa120 Installation and Use.\b0\fs20 You may install and use any number of copies of the software to design, develop and test your programs.\par -{\listtext\f0 b.\tab}\b\fs19 Third Party Programs.\b0\fs20 The software may include third party programs that Microsoft, not the third party, licenses to you under this agreement. Notices, if any, for the third party program are included for your information only.\b\fs19\par - -\pard -{\listtext\f0 2.\tab}\jclisttab\tx360\ls1\nowidctlpar\s1\fi-357\li357\sb120\sa120\fs20 ADDITIONAL LICENSING REQUIREMENTS AND/OR USE RIGHTS.\par - -\pard -{\listtext\f0 a.\tab}\jclisttab\tx363\ls1\ilvl1\nowidctlpar\s2\fi-363\li720\sb120\sa120 DISTRIBUTABLE CODE.\~ \b0 The software is comprised of Distributable Code. \f1\ldblquote\f0 Distributable Code\f1\rdblquote\f0 is code that you are permitted to distribute in programs you develop if you comply with the terms below.\b\par - -\pard -{\listtext\f0 i.\tab}\jclisttab\tx720\ls1\ilvl2\nowidctlpar\s3\fi-357\li1077\sb120\sa120\tx1077 Right to Use and Distribute. \par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\fi-357\li1434\sb120\sa120\b0 You may copy and distribute the object code form of the software.\par -{\pntext\f4\'B7\tab}Third Party Distribution. You may permit distributors of your programs to copy and distribute the Distributable Code as part of those programs.\par - -\pard\nowidctlpar\s3\fi-357\li1077\sb120\sa120\tx1077\b ii.\tab Distribution Requirements.\b0 \b For any Distributable Code you distribute, you must\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\fi-357\li1434\sb120\sa120\b0 add significant primary functionality to it in your programs;\par -{\pntext\f4\'B7\tab}require distributors and external end users to agree to terms that protect it at least as much as this agreement;\par -{\pntext\f4\'B7\tab}display your valid copyright notice on your programs; and\par -{\pntext\f4\'B7\tab}indemnify, defend, and hold harmless Microsoft from any claims, including attorneys\rquote fees, related to the distribution or use of your programs.\par - -\pard\nowidctlpar\s3\fi-357\li1077\sb120\sa120\tx1077\b iii.\tab Distribution Restrictions.\b0 \b You may not\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\fi-357\li1434\sb120\sa120\b0 alter any copyright, trademark or patent notice in the Distributable Code;\par -{\pntext\f4\'B7\tab}use Microsoft\rquote s trademarks in your programs\rquote names or in a way that suggests your programs come from or are endorsed by Microsoft;\par -{\pntext\f4\'B7\tab}include Distributable Code in malicious, deceptive or unlawful programs; or\par -{\pntext\f4\'B7\tab}modify or distribute the source code of any Distributable Code so that any part of it becomes subject to an Excluded License. An Excluded License is one that requires, as a condition of use, modification or distribution, that\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\fi-358\li1792\sb120\sa120 the code be disclosed or distributed in source code form; or\cf1\f2\par -{\pntext\f4\'B7\tab}\cf0\f0 others have the right to modify it.\cf1\f2\par - -\pard\nowidctlpar\s1\fi-357\li357\sb120\sa120\cf0\b\f0 3.\tab\fs19 SCOPE OF LICENSE. \b0 The software is licensed, not sold. This agreement only gives you some rights to use the software. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the software only as expressly permitted in this agreement. In doing so, you must comply with any technical limitations in the software that only allow you to use it in certain ways. You may not\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent363{\pntxtb\'B7}}\nowidctlpar\fi-363\li720\sb120\sa120 work around any technical limitations in the software;\par -{\pntext\f4\'B7\tab}reverse engineer, decompile or disassemble the software, except and only to the extent that applicable law expressly permits, despite this limitation;\par -{\pntext\f4\'B7\tab}publish the software for others to copy;\par -{\pntext\f4\'B7\tab}rent, lease or lend the software;\par -{\pntext\f4\'B7\tab}transfer the software or this agreement to any third party; or\par -{\pntext\f4\'B7\tab}use the software for commercial software hosting services.\par - -\pard\nowidctlpar\s1\fi-357\li357\sb120\sa120\b\fs20 4.\tab\fs19 BACKUP COPY. \b0 You may make one backup copy of the software. You may use it only to reinstall the software.\par -\b\fs20 5.\tab\fs19 DOCUMENTATION. \b0 Any person that has valid access to your computer or internal network may copy and use the documentation for your internal, reference purposes.\par -\b\fs20 6.\tab\fs19 EXPORT RESTRICTIONS. \b0 The software is subject to United States export laws and regulations. You must comply with all domestic and international export laws and regulations that apply to the software. These laws include restrictions on destinations, end users and end use. For additional information, see {\cf2\ul\fs20{\field{\*\fldinst{HYPERLINK www.microsoft.com/exporting }}{\fldrslt{www.microsoft.com/exporting}}}}\f0\fs19 .\cf2\ul\fs20\par -\cf0\ulnone\b 7.\tab\fs19 SUPPORT SERVICES. \b0 Because this software is \ldblquote as is,\rdblquote we may not provide support services for it.\par -\b\fs20 8.\tab\fs19 ENTIRE AGREEMENT. \b0 This agreement, and the terms for supplements, updates, Internet-based services and support services that you use, are the entire agreement for the software and support services.\par -\b\fs20 9.\tab\fs19 APPLICABLE LAW.\par - -\pard -{\listtext\f0 a.\tab}\jclisttab\tx363\ls2\ilvl1\nowidctlpar\s2\fi-363\li720\sb120\sa120 United States. \b0 If you acquired the software in the United States, Washington state law governs the interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws principles. The laws of the state where you live govern all other claims, including claims under state consumer protection laws, unfair competition laws, and in tort.\par -{\listtext\f0 b.\tab}\b Outside the United States. If you acquired the software in any other country, the laws of that country apply.\par - -\pard\nowidctlpar\s1\fi-357\li357\sb120\sa120\fs20 10.\tab\fs19 LEGAL EFFECT. \b0 This agreement describes certain legal rights. You may have other rights under the laws of your country. You may also have rights with respect to the party from whom you acquired the software. This agreement does not change your rights under the laws of your country if the laws of your country do not permit it to do so.\par -\b\fs20 11.\tab\fs19 DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED \ldblquote AS-IS.\rdblquote YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES OR CONDITIONS. YOU MAY HAVE ADDITIONAL CONSUMER RIGHTS OR STATUTORY GUARANTEES UNDER YOUR LOCAL LAWS WHICH THIS AGREEMENT CANNOT CHANGE. TO THE EXTENT PERMITTED UNDER YOUR LOCAL LAWS, MICROSOFT EXCLUDES THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.\par - -\pard\nowidctlpar\li357\sb120\sa120 FOR AUSTRALIA \endash YOU HAVE STATUTORY GUARANTEES UNDER THE AUSTRALIAN CONSUMER LAW AND NOTHING IN THESE TERMS IS INTENDED TO AFFECT THOSE RIGHTS.\par - -\pard\nowidctlpar\s1\fi-357\li357\sb120\sa120\fs20 12.\tab\fs19 LIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES. YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES.\par - -\pard\nowidctlpar\li357\sb120\sa120\b0 This limitation applies to\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent363{\pntxtb\'B7}}\nowidctlpar\fi-363\li720\sb120\sa120 anything related to the software, services, content (including code) on third party Internet sites, or third party programs; and\par -{\pntext\f4\'B7\tab}claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the extent permitted by applicable law.\par - -\pard\nowidctlpar\sb120\sa120 It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential or other damages.\par -\lang9 Please note: As this software is distributed in Quebec, Canada, some of the clauses in this agreement are provided below in French.\par -Remarque : Ce logiciel \'e9tant distribu\'e9 au Qu\'e9bec, Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en fran\'e7ais.\par - -\pard\nowidctlpar\s1\sb120\sa120\b\lang1033 EXON\'c9RATION DE GARANTIE. \b0 Le logiciel vis\'e9 par une licence est offert \'ab tel quel \'bb. Toute utilisation de ce logiciel est \'e0 votre seule risque et p\'e9ril. Microsoft n\rquote accorde aucune autre garantie expresse. Vous pouvez b\'e9n\'e9ficier de droits additionnels en vertu du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualit\'e9 marchande, d\rquote ad\'e9quation \'e0 un usage particulier et d\rquote absence de contrefa\'e7on sont exclues.\par -\b LIMITATION DES DOMMAGES-INT\'c9R\'caTS ET EXCLUSION DE RESPONSABILIT\'c9 POUR LES DOMMAGES. \b0 Vous pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de dommages directs uniquement \'e0 hauteur de 5,00 $ US. Vous ne pouvez pr\'e9tendre \'e0 aucune indemnisation pour les autres dommages, y compris les dommages sp\'e9ciaux, indirects ou accessoires et pertes de b\'e9n\'e9fices.\par - -\pard\nowidctlpar\sb120\sa120\lang9 Cette limitation concerne :\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\li720\sb120\sa120 tout ce qui est reli\'e9 au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et\par -{\pntext\f4\'B7\tab}les r\'e9clamations au titre de violation de contrat ou de garantie, ou au titre de responsabilit\'e9 stricte, de n\'e9gligence ou d\rquote une autre faute dans la limite autoris\'e9e par la loi en vigueur.\par - -\pard\nowidctlpar\sb120\sa120 Elle s\rquote applique \'e9galement, m\'eame si Microsoft connaissait ou devrait conna\'eetre l\rquote\'e9ventualit\'e9 d\rquote un tel dommage. Si votre pays n\rquote autorise pas l\rquote exclusion ou la limitation de responsabilit\'e9 pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l\rquote exclusion ci-dessus ne s\rquote appliquera pas \'e0 votre \'e9gard.\par - -\pard\nowidctlpar\s1\sb120\sa120\b\lang1033 EFFET JURIDIQUE. \b0 Le pr\'e9sent contrat d\'e9crit certains droits juridiques. Vous pourriez avoir d\rquote autres droits pr\'e9vus par les lois de votre pays. Le pr\'e9sent contrat ne modifie pas les droits que vous conf\'e8rent les lois de votre pays si celles-ci ne le permettent pas.\par - -\pard\nowidctlpar\sb120\sa120\b\fs20\lang1036\par - -\pard\sa200\sl276\slmult1\b0\f3\fs22\lang9\par -} - \ No newline at end of file diff --git a/packages/Microsoft.Bcl.1.1.10/lib/Xamarin.iOS10/_._ b/packages/Microsoft.Bcl.1.1.10/lib/Xamarin.iOS10/_._ deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/monoandroid/_._ b/packages/Microsoft.Bcl.1.1.10/lib/monoandroid/_._ deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/monotouch/_._ b/packages/Microsoft.Bcl.1.1.10/lib/monotouch/_._ deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/net40/System.IO.dll b/packages/Microsoft.Bcl.1.1.10/lib/net40/System.IO.dll deleted file mode 100644 index 26cd551c3..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/net40/System.IO.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/net40/System.IO.xml b/packages/Microsoft.Bcl.1.1.10/lib/net40/System.IO.xml deleted file mode 100644 index 865aa1a4f..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/net40/System.IO.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - System.IO - - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/net40/System.Runtime.dll b/packages/Microsoft.Bcl.1.1.10/lib/net40/System.Runtime.dll deleted file mode 100644 index 118fcce20..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/net40/System.Runtime.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/net40/System.Runtime.xml b/packages/Microsoft.Bcl.1.1.10/lib/net40/System.Runtime.xml deleted file mode 100644 index 93cb00d74..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/net40/System.Runtime.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - System.Runtime - - - - Defines a provider for progress updates. - The type of progress update value. - - - Reports a progress update. - The value of the updated progress. - - - Identities the async state machine type for this method. - - - Identities the state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - Gets the type that implements the state machine. - - - Initializes the attribute. - The type that implements the state machine. - - - - Allows you to obtain the method or property name of the caller to the method. - - - - - Allows you to obtain the line number in the source file at which the method is called. - - - - - Allows you to obtain the full path of the source file that contains the caller. - This is the file path at the time of compile. - - - - Identities the iterator state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/net40/System.Threading.Tasks.dll b/packages/Microsoft.Bcl.1.1.10/lib/net40/System.Threading.Tasks.dll deleted file mode 100644 index a60ab2657..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/net40/System.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/net40/System.Threading.Tasks.xml b/packages/Microsoft.Bcl.1.1.10/lib/net40/System.Threading.Tasks.xml deleted file mode 100644 index b47921e5d..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/net40/System.Threading.Tasks.xml +++ /dev/null @@ -1,475 +0,0 @@ - - - - System.Threading.Tasks - - - - Holds state related to the builder's IAsyncStateMachine. - This is a mutable struct. Be very delicate with it. - - - A reference to the heap-allocated state machine object associated with this builder. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument is null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - - Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method. - On first invocation, the supplied state machine will be boxed. - - Specifies the type of the method builder used. - Specifies the type of the state machine used. - The builder. - The state machine. - An Action to provide to the awaiter. - - - Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext. - - - The context with which to run MoveNext. - - - The state machine whose MoveNext method should be invoked. - - - Initializes the runner. - The context with which to run MoveNext. - - - Invokes MoveNext under the provided context. - - - Cached delegate used with ExecutionContext.Run. - - - Invokes the MoveNext method on the supplied IAsyncStateMachine. - The IAsyncStateMachine machine instance. - - - Provides a base class used to cache tasks of a specific return type. - Specifies the type of results the cached tasks return. - - - - A singleton cache for this result type. - This may be null if there are no cached tasks for this TResult. - - - - Creates a non-disposable task. - The result for the task. - The cacheable task. - - - Creates a cache. - A task cache for this result type. - - - Gets a cached task if one exists. - The result for which we want a cached task. - A cached task if one exists; otherwise, null. - - - Provides a cache for Boolean tasks. - - - A true task. - - - A false task. - - - Gets a cached task for the Boolean result. - true or false - A cached task for the Boolean result. - - - Provides a cache for zero Int32 tasks. - - - The minimum value, inclusive, for which we want a cached task. - - - The maximum value, exclusive, for which we want a cached task. - - - The cache of Task{Int32}. - - - Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). - - - Gets a cached task for the zero Int32 result. - The integer value - A cached task for the Int32 result or null if not cached. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - Represents an asynchronous method builder. - - - A cached VoidTaskResult task used for builders that complete synchronously. - - - The generic builder object to which this non-generic instance delegates. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state. - - The builder is not initialized. - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - - Gets the for this builder. - The representing the builder's asynchronous operation. - The builder is not initialized. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - A cached task for default(TResult). - - - State related to the IAsyncStateMachine. - - - The lazily-initialized task. - Must be named m_task for debugger step-over to work correctly. - - - The lazily-initialized task completion source. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state with the specified result. - - The result to use to complete the task. - The task has already completed. - - - - Completes the builder by using either the supplied completed task, or by completing - the builder's previously accessed task using default(TResult). - - A task already completed with the value default(TResult). - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - This should only be invoked from within an asynchronous method, - and only by the debugger. - - - - - Gets a task for the specified result. This will either - be a cached or new task, never null. - - The result for which we need a task. - The completed task containing the result. - - - Gets the lazily-initialized TaskCompletionSource. - - - Gets the for this builder. - The representing the builder's asynchronous operation. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - - Provides a builder for asynchronous methods that return void. - This type is intended for compiler use only. - - - - The synchronization context associated with this operation. - - - State related to the IAsyncStateMachine. - - - An object used by the debugger to uniquely identify this builder. Lazily initialized. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Registers with UnobservedTaskException to suppress exception crashing. - - - Non-zero if PreventUnobservedTaskExceptions has already been invoked. - - - Initializes a new . - The initialized . - - - Initializes the . - The synchronizationContext associated with this operation. This may be null. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument was null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - Completes the method builder successfully. - - - Faults the method builder with an exception. - The exception that is the cause of this fault. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - - - Notifies the current synchronization context that the operation completed. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger and only in a single-threaded manner. - - - - - Represents state machines generated for asynchronous methods. - This type is intended for compiler use only. - - - - Moves the state machine to its next state. - - - Configures the state machine with a heap-allocated replica. - The heap-allocated replica. - - - - Represents an awaiter used to schedule continuations when an await operation completes. - - - - - Represents an operation that will schedule continuations when the operation completes. - - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information. - - - Used with Task(of void) - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/net40/ensureRedirect.xml b/packages/Microsoft.Bcl.1.1.10/lib/net40/ensureRedirect.xml deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/net45/_._ b/packages/Microsoft.Bcl.1.1.10/lib/net45/_._ deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.IO.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.IO.dll deleted file mode 100644 index 32dd41b78..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.IO.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.IO.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.IO.xml deleted file mode 100644 index e83273427..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.IO.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - System.IO - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Found invalid data while decoding.. - - - - - The exception that is thrown when a data stream is in an invalid format. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a reference to the inner exception that is the cause of this exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. If the parameter is not null, the current exception is raised in a catch block that handles the inner exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.Runtime.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.Runtime.dll deleted file mode 100644 index 18e255b25..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.Runtime.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.Runtime.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.Runtime.xml deleted file mode 100644 index 53f5bef44..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.Runtime.xml +++ /dev/null @@ -1,860 +0,0 @@ - - - - System.Runtime - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Argument must be of type {0}.. - - - - - Looks up a localized string similar to The last element of an eight element tuple must be a Tuple.. - - - - - Defines methods to support the comparison of objects for structural equality. - - - - - Determines whether an object is structurally equal to the current instance. - - The object to compare with the current instance. - An object that determines whether the current instance and other are equal. - true if the two objects are equal; otherwise, false. - - - - Returns a hash code for the current instance. - - An object that computes the hash code of the current object. - The hash code for the current instance. - - - - Supports the structural comparison of collection objects. - - - - - Determines whether the current collection object precedes, occurs in the same position as, or follows another object in the sort order. - - The object to compare with the current instance. - An object that compares members of the current collection object with the corresponding members of other. - An integer that indicates the relationship of the current collection object to other. - - This instance and other are not the same type. - - - - - Encapsulates a method that has five parameters and returns a value of the type specified by the TResult parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - Defines a provider for progress updates. - The type of progress update value. - - - Reports a progress update. - The value of the updated progress. - - - Identities the async state machine type for this method. - - - Identities the state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - Gets the type that implements the state machine. - - - Initializes the attribute. - The type that implements the state machine. - - - - Allows you to obtain the method or property name of the caller to the method. - - - - - Allows you to obtain the line number in the source file at which the method is called. - - - - - Allows you to obtain the full path of the source file that contains the caller. - This is the file path at the time of compile. - - - - Identities the iterator state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - - Helper so we can call some tuple methods recursively without knowing the underlying types. - - - - - Provides static methods for creating tuple objects. - - - - - Creates a new 1-tuple, or singleton. - - The type of the only component of the tuple. - The value of the only component of the tuple. - A tuple whose value is (item1). - - - - Creates a new 3-tuple, or pair. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - An 2-tuple (pair) whose value is (item1, item2). - - - - Creates a new 3-tuple, or triple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - An 3-tuple (triple) whose value is (item1, item2, item3). - - - - Creates a new 4-tuple, or quadruple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - An 4-tuple (quadruple) whose value is (item1, item2, item3, item4). - - - - Creates a new 5-tuple, or quintuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - An 5-tuple (quintuple) whose value is (item1, item2, item3, item4, item5). - - - - Creates a new 6-tuple, or sextuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The type of the sixth component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - The value of the sixth component of the tuple. - An 6-tuple (sextuple) whose value is (item1, item2, item3, item4, item5, item6). - - - - Creates a new 7-tuple, or septuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The type of the sixth component of the tuple. - The type of the seventh component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - The value of the sixth component of the tuple. - The value of the seventh component of the tuple. - An 7-tuple (septuple) whose value is (item1, item2, item3, item4, item5, item6, item7). - - - - Creates a new 8-tuple, or octuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The type of the sixth component of the tuple. - The type of the seventh component of the tuple. - The type of the eighth component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - The value of the sixth component of the tuple. - The value of the seventh component of the tuple. - The value of the eighth component of the tuple. - An 8-tuple (octuple) whose value is (item1, item2, item3, item4, item5, item6, item7, item8). - - - - Represents a 1-tuple, or singleton. - - The type of the tuple's only component. - - - - Initializes a new instance of the class. - - The value of the current tuple object's single component. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the tuple object's single component. - - - The value of the current tuple object's single component. - - - - - Represents an 2-tuple, or pair. - - The type of the first component of the tuple. - The type of the second component of the tuple. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Represents an 3-tuple, or triple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Gets the value of the current tuple object's third component. - - - The value of the current tuple object's third component. - - - - - Represents an 4-tuple, or quadruple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Gets the value of the current tuple object's third component. - - - The value of the current tuple object's third component. - - - - - Gets the value of the current tuple object's fourth component. - - - The value of the current tuple object's fourth component. - - - - - Represents an 5-tuple, or quintuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Gets the value of the current tuple object's third component. - - - The value of the current tuple object's third component. - - - - - Gets the value of the current tuple object's fourth component. - - - The value of the current tuple object's fourth component. - - - - - Gets the value of the current tuple object's fifth component. - - - The value of the current tuple object's fifth component. - - - - - Represents an 6-tuple, or sextuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The type of the sixth component of the tuple. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - The value of the sixth component of the tuple. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Gets the value of the current tuple object's third component. - - - The value of the current tuple object's third component. - - - - - Gets the value of the current tuple object's fourth component. - - - The value of the current tuple object's fourth component. - - - - - Gets the value of the current tuple object's fifth component. - - - The value of the current tuple object's fifth component. - - - - - Gets the value of the current tuple object's sixth component. - - - The value of the current tuple object's sixth component. - - - - - Represents an 7-tuple, or septuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The type of the sixth component of the tuple. - The type of the seventh component of the tuple. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - The value of the sixth component of the tuple. - The value of the seventh component of the tuple. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Gets the value of the current tuple object's third component. - - - The value of the current tuple object's third component. - - - - - Gets the value of the current tuple object's fourth component. - - - The value of the current tuple object's fourth component. - - - - - Gets the value of the current tuple object's fifth component. - - - The value of the current tuple object's fifth component. - - - - - Gets the value of the current tuple object's sixth component. - - - The value of the current tuple object's sixth component. - - - - - Gets the value of the current tuple object's seventh component. - - - The value of the current tuple object's seventh component. - - - - - Represents an n-tuple, where n is 8 or greater. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The type of the sixth component of the tuple. - The type of the seventh component of the tuple. - Any generic Tuple object that defines the types of the tuple's remaining components. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - The value of the sixth component of the tuple. - The value of the seventh component of the tuple. - Any generic Tuple object that contains the values of the tuple's remaining components. - - rest is not a generic Tuple object. - - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Gets the value of the current tuple object's third component. - - - The value of the current tuple object's third component. - - - - - Gets the value of the current tuple object's fourth component. - - - The value of the current tuple object's fourth component. - - - - - Gets the value of the current tuple object's fifth component. - - - The value of the current tuple object's fifth component. - - - - - Gets the value of the current tuple object's sixth component. - - - The value of the current tuple object's sixth component. - - - - - Gets the value of the current tuple object's seventh component. - - - The value of the current tuple object's seventh component. - - - - - Gets the current tuple object's remaining components. - - - The value of the current tuple object's remaining components. - - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.Threading.Tasks.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.Threading.Tasks.dll deleted file mode 100644 index a089d474d..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.Threading.Tasks.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.Threading.Tasks.xml deleted file mode 100644 index 6c770122e..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/System.Threading.Tasks.xml +++ /dev/null @@ -1,8969 +0,0 @@ - - - - System.Threading.Tasks - - - - Represents one or more errors that occur during application execution. - - is used to consolidate multiple failures into a single, throwable - exception object. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with - a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a specified error - message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - The argument - is null. - - - - Initializes a new instance of the class with - references to the inner exceptions that are the cause of this exception. - - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with - references to the inner exceptions that are the cause of this exception. - - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with a specified error - message and references to the inner exceptions that are the cause of this exception. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with a specified error - message and references to the inner exceptions that are the cause of this exception. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Allocates a new aggregate exception with the specified message and list of inner exceptions. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Returns the that is the root cause of this exception. - - - - - Invokes a handler on each contained by this . - - The predicate to execute for each exception. The predicate accepts as an - argument the to be processed and returns a Boolean to indicate - whether the exception was handled. - - Each invocation of the returns true or false to indicate whether the - was handled. After all invocations, if any exceptions went - unhandled, all unhandled exceptions will be put into a new - which will be thrown. Otherwise, the method simply returns. If any - invocations of the throws an exception, it will halt the processing - of any more exceptions and immediately propagate the thrown exception as-is. - - An exception contained by this was not handled. - The argument is - null. - - - - Flattens an instances into a single, new instance. - - A new, flattened . - - If any inner exceptions are themselves instances of - , this method will recursively flatten all of them. The - inner exceptions returned in the new - will be the union of all of the the inner exceptions from exception tree rooted at the provided - instance. - - - - - Creates and returns a string representation of the current . - - A string representation of the current exception. - - - - Gets a read-only collection of the instances that caused the - current exception. - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to One or more errors occurred.. - - - - - Looks up a localized string similar to An element of innerExceptions was null.. - - - - - Looks up a localized string similar to {0}{1}---> (Inner Exception #{2}) {3}{4}{5}. - - - - - Looks up a localized string similar to No tokens were supplied.. - - - - - Looks up a localized string similar to The CancellationTokenSource associated with this CancellationToken has been disposed.. - - - - - Looks up a localized string similar to The CancellationTokenSource has been disposed.. - - - - - Looks up a localized string similar to The SyncRoot property may not be used for the synchronization of concurrent collections.. - - - - - Looks up a localized string similar to The array is multidimensional, or the type parameter for the set cannot be cast automatically to the type of the destination array.. - - - - - Looks up a localized string similar to The index is equal to or greater than the length of the array, or the number of elements in the dictionary is greater than the available space from index to the end of the destination array.. - - - - - Looks up a localized string similar to The capacity argument must be greater than or equal to zero.. - - - - - Looks up a localized string similar to The concurrencyLevel argument must be positive.. - - - - - Looks up a localized string similar to The index argument is less than zero.. - - - - - Looks up a localized string similar to TKey is a reference type and item.Key is null.. - - - - - Looks up a localized string similar to The key already existed in the dictionary.. - - - - - Looks up a localized string similar to The source argument contains duplicate keys.. - - - - - Looks up a localized string similar to The key was of an incorrect type for this dictionary.. - - - - - Looks up a localized string similar to The value was of an incorrect type for this dictionary.. - - - - - Looks up a localized string similar to The lazily-initialized type does not have a public, parameterless constructor.. - - - - - Looks up a localized string similar to ValueFactory returned null.. - - - - - Looks up a localized string similar to The spinCount argument must be in the range 0 to {0}, inclusive.. - - - - - Looks up a localized string similar to There are too many threads currently waiting on the event. A maximum of {0} waiting threads are supported.. - - - - - Looks up a localized string similar to The event has been disposed.. - - - - - Looks up a localized string similar to The operation was canceled.. - - - - - Looks up a localized string similar to The condition argument is null.. - - - - - Looks up a localized string similar to The timeout must represent a value between -1 and Int32.MaxValue, inclusive.. - - - - - Looks up a localized string similar to The specified TaskContinuationOptions combined LongRunning and ExecuteSynchronously. Synchronous continuations should not be long running.. - - - - - Looks up a localized string similar to The specified TaskContinuationOptions excluded all continuation kinds.. - - - - - Looks up a localized string similar to (Internal)An attempt was made to create a LongRunning SelfReplicating task.. - - - - - Looks up a localized string similar to The value needs to translate in milliseconds to -1 (signifying an infinite timeout), 0 or a positive integer less than or equal to Int32.MaxValue.. - - - - - Looks up a localized string similar to The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer.. - - - - - Looks up a localized string similar to A task may only be disposed if it is in a completion state (RanToCompletion, Faulted or Canceled).. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.LongRunning in calls to FromAsync.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.PreferFairness in calls to FromAsync.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.SelfReplicating in calls to FromAsync.. - - - - - Looks up a localized string similar to FromAsync was called with a TaskManager that had already shut down.. - - - - - Looks up a localized string similar to The tasks argument contains no tasks.. - - - - - Looks up a localized string similar to It is invalid to exclude specific continuation kinds for continuations off of multiple tasks.. - - - - - Looks up a localized string similar to The tasks argument included a null value.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task that was already started.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a continuation task.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task that has already completed.. - - - - - Looks up a localized string similar to Start may not be called on a task that was already started.. - - - - - Looks up a localized string similar to Start may not be called on a continuation task.. - - - - - Looks up a localized string similar to Start may not be called on a task with null action.. - - - - - Looks up a localized string similar to Start may not be called on a promise-style task.. - - - - - Looks up a localized string similar to Start may not be called on a task that has completed.. - - - - - Looks up a localized string similar to The task has been disposed.. - - - - - Looks up a localized string similar to The tasks array included at least one null element.. - - - - - Looks up a localized string similar to The awaited task has not yet completed.. - - - - - Looks up a localized string similar to A task was canceled.. - - - - - Looks up a localized string similar to The exceptions collection was empty.. - - - - - Looks up a localized string similar to The exceptions collection included at least one null element.. - - - - - Looks up a localized string similar to A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread.. - - - - - Looks up a localized string similar to (Internal)Expected an Exception or an IEnumerable<Exception>. - - - - - Looks up a localized string similar to ExecuteTask may not be called for a task which was already executed.. - - - - - Looks up a localized string similar to ExecuteTask may not be called for a task which was previously queued to a different TaskScheduler.. - - - - - Looks up a localized string similar to The current SynchronizationContext may not be used as a TaskScheduler.. - - - - - Looks up a localized string similar to The TryExecuteTaskInline call to the underlying scheduler succeeded, but the task body was not invoked.. - - - - - Looks up a localized string similar to An exception was thrown by a TaskScheduler.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.SelfReplicating for a Task<TResult>.. - - - - - Looks up a localized string similar to {Not yet computed}. - - - - - Looks up a localized string similar to A task's Exception may only be set directly if the task was created without a function.. - - - - - Looks up a localized string similar to An attempt was made to transition a task to a final state when it had already completed.. - - - - - Represents a thread-safe collection of keys and values. - - The type of the keys in the dictionary. - The type of the values in the dictionary. - - All public and protected members of are thread-safe and may be used - concurrently from multiple threads. - - - - - Initializes a new instance of the - class that is empty, has the default concurrency level, has the default initial capacity, and - uses the default comparer for the key type. - - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level and capacity, and uses the default - comparer for the key type. - - The estimated number of threads that will update the - concurrently. - The initial number of elements that the - can contain. - is - less than 1. - is less than - 0. - - - - Initializes a new instance of the - class that contains elements copied from the specified , has the default concurrency - level, has the default initial capacity, and uses the default comparer for the key type. - - The whose elements are copied to - the new - . - is a null reference - (Nothing in Visual Basic). - contains one or more - duplicate keys. - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level and capacity, and uses the specified - . - - The - implementation to use when comparing keys. - is a null reference - (Nothing in Visual Basic). - - - - Initializes a new instance of the - class that contains elements copied from the specified , has the default concurrency level, has the default - initial capacity, and uses the specified - . - - The whose elements are copied to - the new - . - The - implementation to use when comparing keys. - is a null reference - (Nothing in Visual Basic). -or- - is a null reference (Nothing in Visual Basic). - - - - - Initializes a new instance of the - class that contains elements copied from the specified , - has the specified concurrency level, has the specified initial capacity, and uses the specified - . - - The estimated number of threads that will update the - concurrently. - The whose elements are copied to the new - . - The implementation to use - when comparing keys. - - is a null reference (Nothing in Visual Basic). - -or- - is a null reference (Nothing in Visual Basic). - - - is less than 1. - - contains one or more duplicate keys. - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level, has the specified initial capacity, and - uses the specified . - - The estimated number of threads that will update the - concurrently. - The initial number of elements that the - can contain. - The - implementation to use when comparing keys. - - is less than 1. -or- - is less than 0. - - is a null reference - (Nothing in Visual Basic). - - - - Attempts to add the specified key and value to the . - - The key of the element to add. - The value of the element to add. The value can be a null reference (Nothing - in Visual Basic) for reference types. - true if the key/value pair was added to the - successfully; otherwise, false. - is null reference - (Nothing in Visual Basic). - The - contains too many elements. - - - - Determines whether the contains the specified - key. - - The key to locate in the . - true if the contains an element with - the specified key; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Attempts to remove and return the the value with the specified key from the - . - - The key of the element to remove and return. - When this method returns, contains the object removed from the - or the default value of - if the operation failed. - true if an object was removed successfully; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Removes the specified key from the dictionary if it exists and returns its associated value. - If matchValue flag is set, the key will be removed only if is associated with a particular - value. - - The key to search for and remove if it exists. - The variable into which the removed value, if found, is stored. - Whether removal of the key is conditional on its value. - The conditional value to compare against if is true - - - - - Attempts to get the value associated with the specified key from the . - - The key of the value to get. - When this method returns, contains the object from - the - with the spedified key or the default value of - , if the operation failed. - true if the key was found in the ; - otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Compares the existing value for the specified key with a specified value, and if they’re equal, - updates the key with a third value. - - The key whose value is compared with and - possibly replaced. - The value that replaces the value of the element with if the comparison results in equality. - The value that is compared to the value of the element with - . - true if the value with was equal to and replaced with ; otherwise, - false. - is a null - reference. - - - - Removes all keys and values from the . - - - - - Copies the elements of the to an array of - type , starting at the - specified array index. - - The one-dimensional array of type - that is the destination of the elements copied from the . The array must have zero-based indexing. - The zero-based index in at which copying - begins. - is a null reference - (Nothing in Visual Basic). - is less than - 0. - is equal to or greater than - the length of the . -or- The number of elements in the source - is greater than the available space from to the end of the destination - . - - - - Copies the key and value pairs stored in the to a - new array. - - A new array containing a snapshot of key and value pairs copied from the . - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToPairs. - - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToEntries. - - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToObjects. - - - - Returns an enumerator that iterates through the . - An enumerator for the . - - The enumerator returned from the dictionary is safe to use concurrently with - reads and writes to the dictionary, however it does not represent a moment-in-time snapshot - of the dictionary. The contents exposed through the enumerator may contain modifications - made to the dictionary after was called. - - - - - Shared internal implementation for inserts and updates. - If key exists, we always return false; and if updateIfExists == true we force update with value; - If key doesn't exist, we always add value and return true; - - - - - Adds a key/value pair to the - if the key does not already exist. - - The key of the element to add. - The function used to generate a value for the key - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The value for the key. This will be either the existing value for the key if the - key is already in the dictionary, or the new value for the key as returned by valueFactory - if the key was not in the dictionary. - - - - Adds a key/value pair to the - if the key does not already exist. - - The key of the element to add. - the value to be added, if the key does not already exist - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The value for the key. This will be either the existing value for the key if the - key is already in the dictionary, or the new value if the key was not in the dictionary. - - - - Adds a key/value pair to the if the key does not already - exist, or updates a key/value pair in the if the key - already exists. - - The key to be added or whose value should be updated - The function used to generate a value for an absent key - The function used to generate a new value for an existing key - based on the key's existing value - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The new value for the key. This will be either be the result of addValueFactory (if the key was - absent) or the result of updateValueFactory (if the key was present). - - - - Adds a key/value pair to the if the key does not already - exist, or updates a key/value pair in the if the key - already exists. - - The key to be added or whose value should be updated - The value to be added for an absent key - The function used to generate a new value for an existing key based on - the key's existing value - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The new value for the key. This will be either be the result of addValueFactory (if the key was - absent) or the result of updateValueFactory (if the key was present). - - - - Adds the specified key and value to the . - - The object to use as the key of the element to add. - The object to use as the value of the element to add. - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - - An element with the same key already exists in the . - - - - Removes the element with the specified key from the . - - The key of the element to remove. - true if the element is successfully remove; otherwise false. This method also returns - false if - was not found in the original . - - is a null reference - (Nothing in Visual Basic). - - - - Adds the specified value to the - with the specified key. - - The - structure representing the key and value to add to the . - The of is null. - The - contains too many elements. - An element with the same key already exists in the - - - - - Determines whether the - contains a specific key and value. - - The - structure to locate in the . - true if the is found in the ; otherwise, false. - - - - Removes a key and value from the dictionary. - - The - structure representing the key and value to remove from the . - true if the key and value represented by is successfully - found and removed; otherwise, false. - The Key property of is a null reference (Nothing in Visual Basic). - - - Returns an enumerator that iterates through the . - An enumerator for the . - - The enumerator returned from the dictionary is safe to use concurrently with - reads and writes to the dictionary, however it does not represent a moment-in-time snapshot - of the dictionary. The contents exposed through the enumerator may contain modifications - made to the dictionary after was called. - - - - - Adds the specified key and value to the dictionary. - - The object to use as the key. - The object to use as the value. - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - - is of a type that is not assignable to the key type of the . -or- - is of a type that is not assignable to , - the type of values in the . - -or- A value with the same key already exists in the . - - - - - Gets whether the contains an - element with the specified key. - - The key to locate in the . - true if the contains - an element with the specified key; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - Provides an for the - . - An for the . - - - - Removes the element with the specified key from the . - - The key of the element to remove. - is a null reference - (Nothing in Visual Basic). - - - - Copies the elements of the to an array, starting - at the specified array index. - - The one-dimensional array that is the destination of the elements copied from - the . The array must have zero-based - indexing. - The zero-based index in at which copying - begins. - is a null reference - (Nothing in Visual Basic). - is less than - 0. - is equal to or greater than - the length of the . -or- The number of elements in the source - is greater than the available space from to the end of the destination - . - - - - Replaces the internal table with a larger one. To prevent multiple threads from resizing the - table as a result of races, the table of buckets that was deemed too small is passed in as - an argument to GrowTable(). GrowTable() obtains a lock, and then checks whether the bucket - table has been replaced in the meantime or not. - - Reference to the bucket table that was deemed too small. - - - - Computes the bucket and lock number for a particular key. - - - - - Acquires all locks for this hash table, and increments locksAcquired by the number - of locks that were successfully acquired. The locks are acquired in an increasing - order. - - - - - Acquires a contiguous range of locks for this hash table, and increments locksAcquired - by the number of locks that were successfully acquired. The locks are acquired in an - increasing order. - - - - - Releases a contiguous range of locks. - - - - - Gets a collection containing the keys in the dictionary. - - - - - Gets a collection containing the values in the dictionary. - - - - - A helper method for asserts. - - - - - Get the data array to be serialized - - - - - Construct the dictionary from a previously seiralized one - - - - - Gets or sets the value associated with the specified key. - - The key of the value to get or set. - The value associated with the specified key. If the specified key is not found, a get - operation throws a - , and a set operation creates a new - element with the specified key. - is a null reference - (Nothing in Visual Basic). - The property is retrieved and - - does not exist in the collection. - - - - Gets the number of key/value pairs contained in the . - - The dictionary contains too many - elements. - The number of key/value paris contained in the . - Count has snapshot semantics and represents the number of items in the - at the moment when Count was accessed. - - - - Gets a value that indicates whether the is empty. - - true if the is empty; otherwise, - false. - - - - Gets a collection containing the keys in the . - - An containing the keys in the - . - - - - Gets a collection containing the values in the . - - An containing the values in - the - . - - - - Gets a value indicating whether the dictionary is read-only. - - true if the is - read-only; otherwise, false. For , this property always returns - false. - - - - Gets a value indicating whether the has a fixed size. - - true if the has a - fixed size; otherwise, false. For , this property always - returns false. - - - - Gets a value indicating whether the is read-only. - - true if the is - read-only; otherwise, false. For , this property always - returns false. - - - - Gets an containing the keys of the . - - An containing the keys of the . - - - - Gets an containing the values in the . - - An containing the values in the . - - - - Gets or sets the value associated with the specified key. - - The key of the value to get or set. - The value associated with the specified key, or a null reference (Nothing in Visual Basic) - if is not in the dictionary or is of a type that is - not assignable to the key type of the . - is a null reference - (Nothing in Visual Basic). - - A value is being assigned, and is of a type that is not assignable to the - key type of the . -or- A value is being - assigned, and is of a type that is not assignable to the value type - of the - - - - - Gets a value indicating whether access to the is - synchronized with the SyncRoot. - - true if access to the is synchronized - (thread safe); otherwise, false. For , this property always - returns false. - - - - Gets an object that can be used to synchronize access to the . This property is not supported. - - The SyncRoot property is not supported. - - - - The number of concurrent writes for which to optimize by default. - - - - - A node in a singly-linked list representing a particular hash table bucket. - - - - - A private class to represent enumeration over the dictionary that implements the - IDictionaryEnumerator interface. - - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - Represents an asynchronous method builder. - - - A cached VoidTaskResult task used for builders that complete synchronously. - - - The generic builder object to which this non-generic instance delegates. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state. - - The builder is not initialized. - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - - Gets the for this builder. - The representing the builder's asynchronous operation. - The builder is not initialized. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - Holds state related to the builder's IAsyncStateMachine. - This is a mutable struct. Be very delicate with it. - - - A reference to the heap-allocated state machine object associated with this builder. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument is null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - - Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method. - On first invocation, the supplied state machine will be boxed. - - Specifies the type of the method builder used. - Specifies the type of the state machine used. - The builder. - The state machine. - An Action to provide to the awaiter. - - - Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext. - - - The context with which to run MoveNext. - - - The state machine whose MoveNext method should be invoked. - - - Initializes the runner. - The context with which to run MoveNext. - - - Invokes MoveNext under the provided context. - - - Cached delegate used with ExecutionContext.Run. - - - Invokes the MoveNext method on the supplied IAsyncStateMachine. - The IAsyncStateMachine machine instance. - - - - Provides a builder for asynchronous methods that return void. - This type is intended for compiler use only. - - - - The synchronization context associated with this operation. - - - State related to the IAsyncStateMachine. - - - An object used by the debugger to uniquely identify this builder. Lazily initialized. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Registers with UnobservedTaskException to suppress exception crashing. - - - Non-zero if PreventUnobservedTaskExceptions has already been invoked. - - - Initializes a new . - The initialized . - - - Initializes the . - The synchronizationContext associated with this operation. This may be null. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument was null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - Completes the method builder successfully. - - - Faults the method builder with an exception. - The exception that is the cause of this fault. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - - - Notifies the current synchronization context that the operation completed. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger and only in a single-threaded manner. - - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - A cached task for default(TResult). - - - State related to the IAsyncStateMachine. - - - The lazily-initialized task. - Must be named m_task for debugger step-over to work correctly. - - - The lazily-initialized task completion source. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state with the specified result. - - The result to use to complete the task. - The task has already completed. - - - - Completes the builder by using either the supplied completed task, or by completing - the builder's previously accessed task using default(TResult). - - A task already completed with the value default(TResult). - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - This should only be invoked from within an asynchronous method, - and only by the debugger. - - - - - Gets a task for the specified result. This will either - be a cached or new task, never null. - - The result for which we need a task. - The completed task containing the result. - - - Gets the lazily-initialized TaskCompletionSource. - - - Gets the for this builder. - The representing the builder's asynchronous operation. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - Provides a base class used to cache tasks of a specific return type. - Specifies the type of results the cached tasks return. - - - - A singleton cache for this result type. - This may be null if there are no cached tasks for this TResult. - - - - Creates a non-disposable task. - The result for the task. - The cacheable task. - - - Creates a cache. - A task cache for this result type. - - - Gets a cached task if one exists. - The result for which we want a cached task. - A cached task if one exists; otherwise, null. - - - Provides a cache for Boolean tasks. - - - A true task. - - - A false task. - - - Gets a cached task for the Boolean result. - true or false - A cached task for the Boolean result. - - - Provides a cache for zero Int32 tasks. - - - The minimum value, inclusive, for which we want a cached task. - - - The maximum value, exclusive, for which we want a cached task. - - - The cache of Task{Int32}. - - - Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). - - - Gets a cached task for the zero Int32 result. - The integer value - A cached task for the Int32 result or null if not cached. - - - - Represents state machines generated for asynchronous methods. - This type is intended for compiler use only. - - - - Moves the state machine to its next state. - - - Configures the state machine with a heap-allocated replica. - The heap-allocated replica. - - - - Represents an awaiter used to schedule continuations when an await operation completes. - - - - - Represents an operation that will schedule continuations when the operation completes. - - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information. - - - Used with Task(of void) - - - - An interface similar to the one added in .NET 4.0. - - - - The exception that is thrown in a thread upon cancellation of an operation that the thread was executing. - - - Initializes the exception. - - - Initializes the exception. - The error message that explains the reason for the exception. - - - Initializes the exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - Initializes the exception. - A cancellation token associated with the operation that was canceled. - - - Initializes the exception. - The error message that explains the reason for the exception. - A cancellation token associated with the operation that was canceled. - - - Initializes the exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - A cancellation token associated with the operation that was canceled. - - - Gets a token associated with the operation that was canceled. - - - - A dummy replacement for the .NET internal class StackCrawlMark. - - - - - Propogates notification that operations should be canceled. - - - - A may be created directly in an unchangeable canceled or non-canceled state - using the CancellationToken's constructors. However, to have a CancellationToken that can change - from a non-canceled to a canceled state, - CancellationTokenSource must be used. - CancellationTokenSource exposes the associated CancellationToken that may be canceled by the source through its - Token property. - - - Once canceled, a token may not transition to a non-canceled state, and a token whose - is false will never change to one that can be canceled. - - - All members of this struct are thread-safe and may be used concurrently from multiple threads. - - - - - - Internal constructor only a CancellationTokenSource should create a CancellationToken - - - - - Initializes the CancellationToken. - - - The canceled state for the token. - - - Tokens created with this constructor will remain in the canceled state specified - by the parameter. If is false, - both and will be false. - If is true, - both and will be true. - - - - - Registers a delegate that will be called when this CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - A Boolean value that indicates whether to capture - the current SynchronizationContext and use it - when invoking the . - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The state to pass to the when the delegate is invoked. This may be null. - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The state to pass to the when the delegate is invoked. This may be null. - A Boolean value that indicates whether to capture - the current SynchronizationContext and use it - when invoking the . - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Determines whether the current CancellationToken instance is equal to the - specified token. - - The other CancellationToken to which to compare this - instance. - True if the instances are equal; otherwise, false. Two tokens are equal if they are associated - with the same CancellationTokenSource or if they were both constructed - from public CancellationToken constructors and their values are equal. - - - - Determines whether the current CancellationToken instance is equal to the - specified . - - The other object to which to compare this instance. - True if is a CancellationToken - and if the two instances are equal; otherwise, false. Two tokens are equal if they are associated - with the same CancellationTokenSource or if they were both constructed - from public CancellationToken constructors and their values are equal. - An associated CancellationTokenSource has been disposed. - - - - Serves as a hash function for a CancellationToken. - - A hash code for the current CancellationToken instance. - - - - Determines whether two CancellationToken instances are equal. - - The first instance. - The second instance. - True if the instances are equal; otherwise, false. - An associated CancellationTokenSource has been disposed. - - - - Determines whether two CancellationToken instances are not equal. - - The first instance. - The second instance. - True if the instances are not equal; otherwise, false. - An associated CancellationTokenSource has been disposed. - - - - Throws a OperationCanceledException if - this token has had cancellation requested. - - - This method provides functionality equivalent to: - - if (token.IsCancellationRequested) - throw new OperationCanceledException(token); - - - The token has had cancellation requested. - The associated CancellationTokenSource has been disposed. - - - - Returns an empty CancellationToken value. - - - The value returned by this property will be non-cancelable by default. - - - - - Gets whether cancellation has been requested for this token. - - Whether cancellation has been requested for this token. - - - This property indicates whether cancellation has been requested for this token, - either through the token initially being construted in a canceled state, or through - calling Cancel - on the token's associated . - - - If this property is true, it only guarantees that cancellation has been requested. - It does not guarantee that every registered handler - has finished executing, nor that cancellation requests have finished propagating - to all registered handlers. Additional synchronization may be required, - particularly in situations where related objects are being canceled concurrently. - - - - - - Gets whether this token is capable of being in the canceled state. - - - If CanBeCanceled returns false, it is guaranteed that the token will never transition - into a canceled state, meaning that will never - return true. - - - - - Gets a that is signaled when the token is canceled. - - Accessing this property causes a WaitHandle - to be instantiated. It is preferable to only use this property when necessary, and to then - dispose the associated instance at the earliest opportunity (disposing - the source will dispose of this allocated handle). The handle should not be closed or disposed directly. - - The associated CancellationTokenSource has been disposed. - - - - Represents a callback delegate that has been registered with a CancellationToken. - - - To unregister a callback, dispose the corresponding Registration instance. - - - - - Attempts to deregister the item. If it's already being run, this may fail. - Entails a full memory fence. - - True if the callback was found and deregistered, false otherwise. - - - - Disposes of the registration and unregisters the target callback from the associated - CancellationToken. - If the target callback is currently executing this method will wait until it completes, except - in the degenerate cases where a callback method deregisters itself. - - - - - Determines whether two CancellationTokenRegistration - instances are equal. - - The first instance. - The second instance. - True if the instances are equal; otherwise, false. - - - - Determines whether two CancellationTokenRegistration instances are not equal. - - The first instance. - The second instance. - True if the instances are not equal; otherwise, false. - - - - Determines whether the current CancellationTokenRegistration instance is equal to the - specified . - - The other object to which to compare this instance. - True, if both this and are equal. False, otherwise. - Two CancellationTokenRegistration instances are equal if - they both refer to the output of a single call to the same Register method of a - CancellationToken. - - - - - Determines whether the current CancellationToken instance is equal to the - specified . - - The other CancellationTokenRegistration to which to compare this instance. - True, if both this and are equal. False, otherwise. - Two CancellationTokenRegistration instances are equal if - they both refer to the output of a single call to the same Register method of a - CancellationToken. - - - - - Serves as a hash function for a CancellationTokenRegistration.. - - A hash code for the current CancellationTokenRegistration instance. - - - - Signals to a that it should be canceled. - - - - is used to instantiate a - (via the source's Token property) - that can be handed to operations that wish to be notified of cancellation or that can be used to - register asynchronous operations for cancellation. That token may have cancellation requested by - calling to the source's Cancel - method. - - - All members of this class, except Dispose, are thread-safe and may be used - concurrently from multiple threads. - - - - - The ID of the thread currently executing the main body of CTS.Cancel() - this helps us to know if a call to ctr.Dispose() is running 'within' a cancellation callback. - This is updated as we move between the main thread calling cts.Cancel() and any syncContexts that are used to - actually run the callbacks. - - - - Initializes the . - - - - - Communicates a request for cancellation. - - - - The associated will be - notified of the cancellation and will transition to a state where - IsCancellationRequested returns true. - Any callbacks or cancelable operations - registered with the will be executed. - - - Cancelable operations and callbacks registered with the token should not throw exceptions. - However, this overload of Cancel will aggregate any exceptions thrown into a , - such that one callback throwing an exception will not prevent other registered callbacks from being executed. - - - The that was captured when each callback was registered - will be reestablished when the callback is invoked. - - - An aggregate exception containing all the exceptions thrown - by the registered callbacks on the associated . - This has been disposed. - - - - Communicates a request for cancellation. - - - - The associated will be - notified of the cancellation and will transition to a state where - IsCancellationRequested returns true. - Any callbacks or cancelable operations - registered with the will be executed. - - - Cancelable operations and callbacks registered with the token should not throw exceptions. - If is true, an exception will immediately propagate out of the - call to Cancel, preventing the remaining callbacks and cancelable operations from being processed. - If is false, this overload will aggregate any - exceptions thrown into a , - such that one callback throwing an exception will not prevent other registered callbacks from being executed. - - - The that was captured when each callback was registered - will be reestablished when the callback is invoked. - - - Specifies whether exceptions should immediately propagate. - An aggregate exception containing all the exceptions thrown - by the registered callbacks on the associated . - This has been disposed. - - - - Releases the resources used by this . - - - This method is not thread-safe for any other concurrent calls. - - - - - Throws an exception if the source has been disposed. - - - - - InternalGetStaticSource() - - Whether the source should be set. - A static source to be shared among multiple tokens. - - - - Registers a callback object. If cancellation has already occurred, the - callback will have been run by the time this method returns. - - - - - - - - - - Invoke the Canceled event. - - - The handlers are invoked synchronously in LIFO order. - - - - - Creates a CancellationTokenSource that will be in the canceled state - when any of the source tokens are in the canceled state. - - The first CancellationToken to observe. - The second CancellationToken to observe. - A CancellationTokenSource that is linked - to the source tokens. - A CancellationTokenSource associated with - one of the source tokens has been disposed. - - - - Creates a CancellationTokenSource that will be in the canceled state - when any of the source tokens are in the canceled state. - - The CancellationToken instances to observe. - A CancellationTokenSource that is linked - to the source tokens. - is null. - A CancellationTokenSource associated with - one of the source tokens has been disposed. - - - - Gets whether cancellation has been requested for this CancellationTokenSource. - - Whether cancellation has been requested for this CancellationTokenSource. - - - This property indicates whether cancellation has been requested for this token source, such as - due to a call to its - Cancel method. - - - If this property returns true, it only guarantees that cancellation has been requested. It does not - guarantee that every handler registered with the corresponding token has finished executing, nor - that cancellation requests have finished propagating to all registered handlers. Additional - synchronization may be required, particularly in situations where related objects are being - canceled concurrently. - - - - - - A simple helper to determine whether cancellation has finished. - - - - - A simple helper to determine whether disposal has occured. - - - - - The ID of the thread that is running callbacks. - - - - - Gets the CancellationToken - associated with this . - - The CancellationToken - associated with this . - The token source has been - disposed. - - - - - - - - - - - - - - The currently executing callback - - - - - A helper class for collating the various bits of information required to execute - cancellation callbacks. - - - - - InternalExecuteCallbackSynchronously_GeneralPath - This will be called on the target synchronization context, however, we still need to restore the required execution context - - - - - A sparsely populated array. Elements can be sparse and some null, but this allows for - lock-free additions and growth, and also for constant time removal (by nulling out). - - The kind of elements contained within. - - - - Allocates a new array with the given initial size. - - How many array slots to pre-allocate. - - - - Adds an element in the first available slot, beginning the search from the tail-to-head. - If no slots are available, the array is grown. The method doesn't return until successful. - - The element to add. - Information about where the add happened, to enable O(1) deregistration. - - - - The tail of the doubly linked list. - - - - - A struct to hold a link to the exact spot in an array an element was inserted, enabling - constant time removal later on. - - - - - A fragment of a sparsely populated array, doubly linked. - - The kind of elements contained within. - - - - Provides lazy initialization routines. - - - These routines avoid needing to allocate a dedicated, lazy-initialization instance, instead using - references to ensure targets have been initialized as they are accessed. - - - - - Initializes a target reference type with the type's default constructor if the target has not - already been initialized. - - The refence type of the reference to be initialized. - A reference of type to initialize if it has not - already been initialized. - The initialized reference of type . - Type does not have a default - constructor. - - Permissions to access the constructor of type were missing. - - - - This method may only be used on reference types. To ensure initialization of value - types, see other overloads of EnsureInitialized. - - - This method may be used concurrently by multiple threads to initialize . - In the event that multiple threads access this method concurrently, multiple instances of - may be created, but only one will be stored into . In such an occurrence, this method will not dispose of the - objects that were not stored. If such objects must be disposed, it is up to the caller to determine - if an object was not used and to then dispose of the object appropriately. - - - - - - Initializes a target reference type using the specified function if it has not already been - initialized. - - The reference type of the reference to be initialized. - The reference of type to initialize if it has not - already been initialized. - The invoked to initialize the - reference. - The initialized reference of type . - Type does not have a - default constructor. - returned - null. - - - This method may only be used on reference types, and may - not return a null reference (Nothing in Visual Basic). To ensure initialization of value types or - to allow null reference types, see other overloads of EnsureInitialized. - - - This method may be used concurrently by multiple threads to initialize . - In the event that multiple threads access this method concurrently, multiple instances of - may be created, but only one will be stored into . In such an occurrence, this method will not dispose of the - objects that were not stored. If such objects must be disposed, it is up to the caller to determine - if an object was not used and to then dispose of the object appropriately. - - - - - - Initialize the target using the given delegate (slow path). - - The reference type of the reference to be initialized. - The variable that need to be initialized - The delegate that will be executed to initialize the target - The initialized variable - - - - Initializes a target reference or value type with its default constructor if it has not already - been initialized. - - The type of the reference to be initialized. - A reference or value of type to initialize if it - has not already been initialized. - A reference to a boolean that determines whether the target has already - been initialized. - A reference to an object used as the mutually exclusive lock for initializing - . - The initialized value of type . - - - - Initializes a target reference or value type with a specified function if it has not already been - initialized. - - The type of the reference to be initialized. - A reference or value of type to initialize if it - has not already been initialized. - A reference to a boolean that determines whether the target has already - been initialized. - A reference to an object used as the mutually exclusive lock for initializing - . - The invoked to initialize the - reference or value. - The initialized value of type . - - - - Ensure the target is initialized and return the value (slow path). This overload permits nulls - and also works for value type targets. Uses the supplied function to create the value. - - The type of target. - A reference to the target to be initialized. - A reference to a location tracking whether the target has been initialized. - A reference to a location containing a mutual exclusive lock. - - The to invoke in order to produce the lazily-initialized value. - - The initialized object. - - - - Provides a slimmed down version of . - - - All public and protected members of are thread-safe and may be used - concurrently from multiple threads, with the exception of Dispose, which - must only be used when all other operations on the have - completed, and Reset, which should only be used when no other threads are - accessing the event. - - - - - Initializes a new instance of the - class with an initial state of nonsignaled. - - - - - Initializes a new instance of the - class with a Boolen value indicating whether to set the intial state to signaled. - - true to set the initial state signaled; false to set the initial state - to nonsignaled. - - - - Initializes a new instance of the - class with a Boolen value indicating whether to set the intial state to signaled and a specified - spin count. - - true to set the initial state to signaled; false to set the initial state - to nonsignaled. - The number of spin waits that will occur before falling back to a true - wait. - is less than - 0 or greater than the maximum allowed value. - - - - Initializes the internal state of the event. - - Whether the event is set initially or not. - The spin count that decides when the event will block. - - - - Helper to ensure the lock object is created before first use. - - - - - This method lazily initializes the event object. It uses CAS to guarantee that - many threads racing to call this at once don't result in more than one event - being stored and used. The event will be signaled or unsignaled depending on - the state of the thin-event itself, with synchronization taken into account. - - True if a new event was created and stored, false otherwise. - - - - Sets the state of the event to signaled, which allows one or more threads waiting on the event to - proceed. - - - - - Private helper to actually perform the Set. - - Indicates whether we are calling Set() during cancellation. - The object has been canceled. - - - - Sets the state of the event to nonsignaled, which causes threads to block. - - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - Blocks the current thread until the current is set. - - - The maximum number of waiters has been exceeded. - - - The caller of this method blocks indefinitely until the current instance is set. The caller will - return immediately if the event is currently in a set state. - - - - - Blocks the current thread until the current receives a signal, - while observing a . - - The to - observe. - - The maximum number of waiters has been exceeded. - - was - canceled. - - The caller of this method blocks indefinitely until the current instance is set. The caller will - return immediately if the event is currently in a set state. - - - - - Blocks the current thread until the current is set, using a - to measure the time interval. - - A that represents the number of milliseconds - to wait, or a that represents -1 milliseconds to wait indefinitely. - - true if the was set; otherwise, - false. - is a negative - number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater - than . - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - to measure the time interval, while observing a . - - A that represents the number of milliseconds - to wait, or a that represents -1 milliseconds to wait indefinitely. - - The to - observe. - true if the was set; otherwise, - false. - is a negative - number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater - than . - was canceled. - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - 32-bit signed integer to measure the time interval. - - The number of milliseconds to wait, or (-1) to wait indefinitely. - true if the was set; otherwise, - false. - is a - negative number other than -1, which represents an infinite time-out. - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - 32-bit signed integer to measure the time interval, while observing a . - - The number of milliseconds to wait, or (-1) to wait indefinitely. - The to - observe. - true if the was set; otherwise, - false. - is a - negative number other than -1, which represents an infinite time-out. - - The maximum number of waiters has been exceeded. - - was canceled. - - - - Releases all resources used by the current instance of . - - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - When overridden in a derived class, releases the unmanaged resources used by the - , and optionally releases the managed resources. - - true to release both managed and unmanaged resources; - false to release only unmanaged resources. - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - Throw ObjectDisposedException if the MRES is disposed - - - - - Private helper method to wake up waiters when a cancellationToken gets canceled. - - - - - Private helper method for updating parts of a bit-string state value. - Mainly called from the IsSet and Waiters properties setters - - - Note: the parameter types must be int as CompareExchange cannot take a Uint - - The new value - The mask used to set the bits - - - - Private helper method - performs Mask and shift, particular helpful to extract a field from a packed word. - eg ExtractStatePortionAndShiftRight(0x12345678, 0xFF000000, 24) => 0x12, ie extracting the top 8-bits as a simple integer - - ?? is there a common place to put this rather than being private to MRES? - - - - - - - - - Performs a Mask operation, but does not perform the shift. - This is acceptable for boolean values for which the shift is unnecessary - eg (val & Mask) != 0 is an appropriate way to extract a boolean rather than using - ((val & Mask) >> shiftAmount) == 1 - - ?? is there a common place to put this rather than being private to MRES? - - - - - - - Helper function to measure and update the wait time - - The first time (in Ticks) observed when the wait started. - The orginal wait timeoutout in milliseconds. - The new wait time in milliseconds, -1 if the time expired, -2 if overflow in counters - has occurred. - - - - Gets the underlying object for this . - - The underlying event object fore this . - - Accessing this property forces initialization of an underlying event object if one hasn't - already been created. To simply wait on this , - the public Wait methods should be preferred. - - - - - Gets whether the event is set. - - true if the event has is set; otherwise, false. - - - - Gets the number of spin waits that will be occur before falling back to a true wait. - - - - - How many threads are waiting. - - - - - Provides support for spin-based waiting. - - - - encapsulates common spinning logic. On single-processor machines, yields are - always used instead of busy waits, and on computers with Intel™ processors employing Hyper-Threading™ - technology, it helps to prevent hardware thread starvation. SpinWait encapsulates a good mixture of - spinning and true yielding. - - - is a value type, which means that low-level code can utilize SpinWait without - fear of unnecessary allocation overheads. SpinWait is not generally useful for ordinary applications. - In most cases, you should use the synchronization classes provided by the .NET Framework, such as - . For most purposes where spin waiting is required, however, - the type should be preferred over the System.Threading.Thread.SpinWait method. - - - While SpinWait is designed to be used in concurrent applications, it is not designed to be - used from multiple threads concurrently. SpinWait's members are not thread-safe. If multiple - threads must spin, each should use its own instance of SpinWait. - - - - - - Performs a single spin. - - - This is typically called in a loop, and may change in behavior based on the number of times a - has been called thus far on this instance. - - - - - Resets the spin counter. - - - This makes and behave as though no calls - to had been issued on this instance. If a instance - is reused many times, it may be useful to reset it to avoid yielding too soon. - - - - - Spins until the specified condition is satisfied. - - A delegate to be executed over and over until it returns true. - The argument is null. - - - - Spins until the specified condition is satisfied or until the specified timeout is expired. - - A delegate to be executed over and over until it returns true. - - A that represents the number of milliseconds to wait, - or a TimeSpan that represents -1 milliseconds to wait indefinitely. - True if the condition is satisfied within the timeout; otherwise, false - The argument is null. - is a negative number - other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than - . - - - - Spins until the specified condition is satisfied or until the specified timeout is expired. - - A delegate to be executed over and over until it returns true. - The number of milliseconds to wait, or (-1) to wait indefinitely. - True if the condition is satisfied within the timeout; otherwise, false - The argument is null. - is a - negative number other than -1, which represents an infinite time-out. - - - - Gets the number of times has been called on this instance. - - - - - Gets whether the next call to will yield the processor, triggering a - forced context switch. - - Whether the next call to will yield the processor, triggering a - forced context switch. - - On a single-CPU machine, always yields the processor. On machines with - multiple CPUs, may yield after an unspecified number of calls. - - - - - A helper class to get the number of preocessors, it updates the numbers of processors every sampling interval - - - - - Gets the number of available processors - - - - - Gets whether the current machine has only a single processor. - - - - - Represents an asynchronous operation that produces a result at some time in the future. - - - The type of the result produced by this . - - - - instances may be created in a variety of ways. The most common approach is by - using the task's property to retrieve a instance that can be used to create tasks for several - purposes. For example, to create a that runs a function, the factory's StartNew - method may be used: - - // C# - var t = Task<int>.Factory.StartNew(() => GenerateResult()); - - or - - var t = Task.Factory.StartNew(() => GenerateResult()); - - ' Visual Basic - Dim t = Task<int>.Factory.StartNew(Function() GenerateResult()) - - or - - Dim t = Task.Factory.StartNew(Function() GenerateResult()) - - - - The class also provides constructors that initialize the task but that do not - schedule it for execution. For performance reasons, the StartNew method should be the - preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation - and scheduling must be separated, the constructors may be used, and the task's - Start - method may then be used to schedule the task for execution at a later time. - - - All members of , except for - Dispose, are thread-safe - and may be used from multiple threads concurrently. - - - - - - Represents an asynchronous operation. - - - - instances may be created in a variety of ways. The most common approach is by - using the Task type's property to retrieve a instance that can be used to create tasks for several - purposes. For example, to create a that runs an action, the factory's StartNew - method may be used: - - // C# - var t = Task.Factory.StartNew(() => DoAction()); - - ' Visual Basic - Dim t = Task.Factory.StartNew(Function() DoAction()) - - - - The class also provides constructors that initialize the Task but that do not - schedule it for execution. For performance reasons, TaskFactory's StartNew method should be the - preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation - and scheduling must be separated, the constructors may be used, and the task's - method may then be used to schedule the task for execution at a later time. - - - All members of , except for , are thread-safe - and may be used from multiple threads concurrently. - - - For operations that return values, the class - should be used. - - - For developers implementing custom debuggers, several internal and private members of Task may be - useful (these may change from release to release). The Int32 m_taskId field serves as the backing - store for the property, however accessing this field directly from a debugger may be - more efficient than accessing the same value through the property's getter method (the - s_taskIdCounter Int32 counter is used to retrieve the next available ID for a Task). Similarly, the - Int32 m_stateFlags field stores information about the current lifecycle stage of the Task, - information also accessible through the property. The m_action System.Object - field stores a reference to the Task's delegate, and the m_stateObject System.Object field stores the - async state passed to the Task by the developer. Finally, for debuggers that parse stack frames, the - InternalWait method serves a potential marker for when a Task is entering a wait operation. - - - - - - A type initializer that runs with the appropriate permissions. - - - - - Initializes a new with the specified action. - - The delegate that represents the code to execute in the Task. - The argument is null. - - - - Initializes a new with the specified action and CancellationToken. - - The delegate that represents the code to execute in the Task. - The CancellationToken - that will be assigned to the new Task. - The argument is null. - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action and creation options. - - The delegate that represents the code to execute in the task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action and creation options. - - The delegate that represents the code to execute in the task. - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action and state. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - - The argument is null. - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - The that will be assigned to the new task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - An internal constructor used by the factory methods on task and its descendent(s). - This variant does not capture the ExecutionContext; it is up to the caller to do that. - - An action to execute. - Optional state to pass to the action. - Parent of Task. - A CancellationToken for the task. - A task scheduler under which the task will run. - Options to control its execution. - Internal options to control its execution - - - - Common logic used by the following internal ctors: - Task() - Task(object action, object state, Task parent, TaskCreationOptions options, TaskScheduler taskScheduler) - - ASSUMES THAT m_creatingTask IS ALREADY SET. - - - Action for task to execute. - Object to which to pass to action (may be null) - Task scheduler on which to run thread (only used by continuation tasks). - A CancellationToken for the Task. - Options to customize behavior of Task. - Internal options to customize behavior of Task. - - - - Checks if we registered a CT callback during construction, and deregisters it. - This should be called when we know the registration isn't useful anymore. Specifically from Finish() if the task has completed - successfully or with an exception. - - - - - Captures the ExecutionContext so long as flow isn't suppressed. - - A stack crawl mark pointing to the frame of the caller. - - - - Internal function that will be called by a new child task to add itself to - the children list of the parent (this). - - Since a child task can only be created from the thread executing the action delegate - of this task, reentrancy is neither required nor supported. This should not be called from - anywhere other than the task construction/initialization codepaths. - - - - - Starts the , scheduling it for execution to the current TaskScheduler. - - - A task may only be started and run only once. Any attempts to schedule a task a second time - will result in an exception. - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Starts the , scheduling it for execution to the specified TaskScheduler. - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - The TaskScheduler with which to associate - and execute this task. - - - The argument is null. - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Runs the synchronously on the current TaskScheduler. - - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - Tasks executed with will be associated with the current TaskScheduler. - - - If the target scheduler does not support running this Task on the current thread, the Task will - be scheduled for execution on the scheduler, and the current thread will block until the - Task has completed execution. - - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Runs the synchronously on the scheduler provided. - - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - If the target scheduler does not support running this Task on the current thread, the Task will - be scheduled for execution on the scheduler, and the current thread will block until the - Task has completed execution. - - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - The parameter - is null. - The scheduler on which to attempt to run this task inline. - - - - Throws an exception if the task has been disposed, and hence can no longer be accessed. - - The task has been disposed. - - - - Sets the internal completion event. - - - - - Disposes the , releasing all of its unmanaged resources. - - - Unlike most of the members of , this method is not thread-safe. - Also, may only be called on a that is in one of - the final states: RanToCompletion, - Faulted, or - Canceled. - - - The exception that is thrown if the is not in - one of the final states: RanToCompletion, - Faulted, or - Canceled. - - - - - Disposes the , releasing all of its unmanaged resources. - - - A Boolean value that indicates whether this method is being called due to a call to . - - - Unlike most of the members of , this method is not thread-safe. - - - - - Schedules the task for execution. - - If true, TASK_STATE_STARTED bit is turned on in - an atomic fashion, making sure that TASK_STATE_CANCELED does not get set - underneath us. If false, TASK_STATE_STARTED bit is OR-ed right in. This - allows us to streamline things a bit for StartNew(), where competing cancellations - are not a problem. - - - - Adds an exception to the list of exceptions this task has thrown. - - An object representing either an Exception or a collection of Exceptions. - - - - Returns a list of exceptions by aggregating the holder's contents. Or null if - no exceptions have been thrown. - - Whether to include a TCE if cancelled. - An aggregate exception, or null if no exceptions have been caught. - - - - Throws an aggregate exception if the task contains exceptions. - - - - - Checks whether this is an attached task, and whether we are being called by the parent task. - And sets the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag based on that. - - This is meant to be used internally when throwing an exception, and when WaitAll is gathering - exceptions for tasks it waited on. If this flag gets set, the implicit wait on children - will skip exceptions to prevent duplication. - - This should only be called when this task has completed with an exception - - - - - - Signals completion of this particular task. - - The bUserDelegateExecuted parameter indicates whether this Finish() call comes following the - full execution of the user delegate. - - If bUserDelegateExecuted is false, it mean user delegate wasn't invoked at all (either due to - a cancellation request, or because this task is a promise style Task). In this case, the steps - involving child tasks (i.e. WaitForChildren) will be skipped. - - - - - - FinishStageTwo is to be executed as soon as we known there are no more children to complete. - It can happen i) either on the thread that originally executed this task (if no children were spawned, or they all completed by the time this task's delegate quit) - ii) or on the thread that executed the last child. - - - - - Final stage of the task completion code path. Notifies the parent (if any) that another of its childre are done, and runs continuations. - This function is only separated out from FinishStageTwo because these two operations are also needed to be called from CancellationCleanupLogic() - - - - - This is called by children of this task when they are completed. - - - - - This is to be called just before the task does its final state transition. - It traverses the list of exceptional children, and appends their aggregate exceptions into this one's exception list - - - - - Special purpose Finish() entry point to be used when the task delegate throws a ThreadAbortedException - This makes a note in the state flags so that we avoid any costly synchronous operations in the finish codepath - such as inlined continuations - - - Indicates whether the ThreadAbortException was added to this task's exception holder. - This should always be true except for the case of non-root self replicating task copies. - - Whether the delegate was executed. - - - - Executes the task. This method will only be called once, and handles bookeeping associated with - self-replicating tasks, in addition to performing necessary exception marshaling. - - The task has already been disposed. - - - - IThreadPoolWorkItem override, which is the entry function for this task when the TP scheduler decides to run it. - - - - - - Outermost entry function to execute this task. Handles all aspects of executing a task on the caller thread. - Currently this is called by IThreadPoolWorkItem.ExecuteWorkItem(), and TaskManager.TryExecuteInline. - - - Performs atomic updates to prevent double execution. Should only be set to true - in codepaths servicing user provided TaskSchedulers. The ConcRT or ThreadPool schedulers don't need this. - - - - The actual code which invokes the body of the task. This can be overriden in derived types. - - - - - Alternate InnerInvoke prototype to be called from ExecuteSelfReplicating() so that - the Parallel Debugger can discover the actual task being invoked. - Details: Here, InnerInvoke is actually being called on the rootTask object while we are actually executing the - childTask. And the debugger needs to discover the childTask, so we pass that down as an argument. - The NoOptimization and NoInlining flags ensure that the childTask pointer is retained, and that this - function appears on the callstack. - - - - - - Performs whatever handling is necessary for an unhandled exception. Normally - this just entails adding the exception to the holder object. - - The exception that went unhandled. - - - - Waits for the to complete execution. - - - The was canceled -or- an exception was thrown during - the execution of the . - - - The has been disposed. - - - - - Waits for the to complete execution. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - true if the completed execution within the allotted time; otherwise, false. - - - The was canceled -or- an exception was thrown during the execution of the . - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - The has been disposed. - - - - - Waits for the to complete execution. - - - A to observe while waiting for the task to complete. - - - The was canceled. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - - - Waits for the to complete execution. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - true if the completed execution within the allotted time; otherwise, - false. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - - - Waits for the to complete execution. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for the task to complete. - - - true if the completed execution within the allotted time; otherwise, false. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - The core wait function, which is only accesible internally. It's meant to be used in places in TPL code where - the current context is known or cached. - - - - - Cancels the . - - Indiactes whether we should only cancel non-invoked tasks. - For the default scheduler this option will only be serviced through TryDequeue. - For custom schedulers we also attempt an atomic state transition. - true if the task was successfully canceled; otherwise, false. - The - has been disposed. - - - - Sets the task's cancellation acknowledged flag. - - - - - Runs all of the continuations, as appropriate. - - - - - Helper function to determine whether the current task is in the state desired by the - continuation kind under evaluation. Three possibilities exist: the task failed with - an unhandled exception (OnFailed), the task was canceled before running (OnAborted), - or the task completed successfully (OnCompletedSuccessfully). Note that the last - one includes completing due to cancellation. - - The continuation options under evaluation. - True if the continuation should be run given the task's current state. - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - The that will be assigned to the new continuation task. - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Converts TaskContinuationOptions to TaskCreationOptions, and also does - some validity checking along the way. - - Incoming TaskContinuationOptions - Outgoing TaskCreationOptions - Outgoing InternalTaskOptions - - - - Registers the continuation and possibly runs it (if the task is already finished). - - The continuation task itself. - TaskScheduler with which to associate continuation task. - Restrictions on when the continuation becomes active. - - - - Waits for all of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - An array of instances on which to wait. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - A to observe while waiting for the tasks to complete. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The was canceled. - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for the tasks to complete. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - Waits for a set of handles in a STA-aware way. In other words, it will wait for each - of the events individually if we're on a STA thread, because MsgWaitForMultipleObjectsEx - can't do a true wait-all due to its hidden message queue event. This is not atomic, - of course, but we only wait on one-way (MRE) events anyway so this is OK. - - An array of wait handles to wait on. - The timeout to use during waits. - The cancellationToken that enables a wait to be canceled. - True if all waits succeeded, false if a timeout occurred. - - - - Internal WaitAll implementation which is meant to be used with small number of tasks, - optimized for Parallel.Invoke and other structured primitives. - - - - - This internal function is only meant to be called by WaitAll() - If the completed task is canceled or it has other exceptions, here we will add those - into the passed in exception list (which will be lazily initialized here). - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - The index of the completed task in the array argument. - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - A to observe while waiting for a task to complete. - - - The index of the completed task in the array argument. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - The was canceled. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for a task to complete. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - Gets a unique ID for this Task instance. - - - Task IDs are assigned on-demand and do not necessarily represent the order in the which Task - instances were created. - - - - - Returns the unique ID of the currently executing Task. - - - - - Gets the Task instance currently executing, or - null if none exists. - - - - - Gets the Exception that caused the Task to end prematurely. If the Task completed successfully or has not yet thrown any - exceptions, this will return null. - - - Tasks that throw unhandled exceptions store the resulting exception and propagate it wrapped in a - in calls to Wait - or in accesses to the property. Any exceptions not observed by the time - the Task instance is garbage collected will be propagated on the finalizer thread. - - - The Task - has been disposed. - - - - - Gets the TaskStatus of this Task. - - - - - Gets whether this Task instance has completed - execution due to being canceled. - - - A Task will complete in Canceled state either if its CancellationToken - was marked for cancellation before the task started executing, or if the task acknowledged the cancellation request on - its already signaled CancellationToken by throwing an - OperationCanceledException2 that bears the same - CancellationToken. - - - - - Returns true if this task has a cancellation token and it was signaled. - To be used internally in execute entry codepaths. - - - - - This internal property provides access to the CancellationToken that was set on the task - when it was constructed. - - - - - Gets whether this threw an OperationCanceledException2 while its CancellationToken was signaled. - - - - - Gets whether this Task has completed. - - - will return true when the Task is in one of the three - final states: RanToCompletion, - Faulted, or - Canceled. - - - - - Checks whether this task has been disposed. - - - - - Gets the TaskCreationOptions used - to create this task. - - - - - Gets a that can be used to wait for the task to - complete. - - - Using the wait functionality provided by - should be preferred over using for similar - functionality. - - - The has been disposed. - - - - - Gets the state object supplied when the Task was created, - or null if none was supplied. - - - - - Gets an indication of whether the asynchronous operation completed synchronously. - - true if the asynchronous operation completed synchronously; otherwise, false. - - - - Provides access to the TaskScheduler responsible for executing this Task. - - - - - Provides access to factory methods for creating and instances. - - - The factory returned from is a default instance - of , as would result from using - the default constructor on TaskFactory. - - - - - Provides an event that can be used to wait for completion. - Only called by Wait*(), which means that we really do need to instantiate a completion event. - - - - - Determines whether this is the root task of a self replicating group. - - - - - Determines whether the task is a replica itself. - - - - - The property formerly known as IsFaulted. - - - - - Gets whether the completed due to an unhandled exception. - - - If is true, the Task's will be equal to - TaskStatus.Faulted, and its - property will be non-null. - - - - - Checks whether the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag is set, - This will only be used by the implicit wait to prevent double throws - - - - - - Checks whether the body was ever invoked. Used by task scheduler code to verify custom schedulers actually ran the task. - - - - - A structure to hold continuation information. - - - - - Constructs a new continuation structure. - - The task to be activated. - The continuation options. - The scheduler to use for the continuation. - - - - Invokes the continuation for the target completion task. - - The completed task. - Whether the continuation can be inlined. - - - - Initializes a new with the specified function. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - - The argument is null. - - - - - Initializes a new with the specified function. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - The to be assigned to this task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified function and creation options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified function and creation options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified function and state. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the action. - - The argument is null. - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - The to be assigned to the new task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - The to be assigned to the new task. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Creates a new future object. - - The parent task for this future. - A function that yields the future value. - The task scheduler which will be used to execute the future. - The CancellationToken for the task. - Options to control the future's behavior. - Internal options to control the future's behavior. - The argument specifies - a SelfReplicating , which is illegal."/>. - - - - Creates a new future object. - - The parent task for this future. - An object containing data to be used by the action; may be null. - A function that yields the future value. - The CancellationToken for the task. - The task scheduler which will be used to execute the future. - Options to control the future's behavior. - Internal options to control the future's behavior. - The argument specifies - a SelfReplicating , which is illegal."/>. - - - - Evaluates the value selector of the Task which is passed in as an object and stores the result. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new task. - A new continuation . - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The , when executed, should return a . This task's completion state will be transferred to the task returned - from the ContinueWith call. - - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be passed as - an argument this completed task. - - The that will be assigned to the new task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The , when executed, should return a . - This task's completion state will be transferred to the task returned from the - ContinueWith call. - - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Gets the result value of this . - - - The get accessor for this property ensures that the asynchronous operation is complete before - returning. Once the result of the computation is available, it is stored and will be returned - immediately on later calls to . - - - - - Provides access to factory methods for creating instances. - - - The factory returned from is a default instance - of , as would result from using - the default constructor on the factory type. - - - - - Provides support for creating and scheduling - Task{TResult} objects. - - The type of the results that are available though - the Task{TResult} objects that are associated with - the methods in this class. - - - There are many common patterns for which tasks are relevant. The - class encodes some of these patterns into methods that pick up default settings, which are - configurable through its constructors. - - - A default instance of is available through the - Task{TResult}.Factory property. - - - - - - Initializes a instance with the default configuration. - - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the default configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The - TaskScheduler to use to schedule any tasks created with this TaskFactory{TResult}. A null value - indicates that the current TaskScheduler should be used. - - - With this constructor, the - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to , unless it's null, in which case the property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory{TResult}. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory{TResult}. - - - The exception that is thrown when the - argument or the - argument specifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory{TResult}. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory{TResult}. - - - The default - TaskScheduler to use to schedule any Tasks created with this TaskFactory{TResult}. A null value - indicates that TaskScheduler.Current should be used. - - - The exception that is thrown when the - argument or the - argumentspecifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to - , unless it's null, in which case the property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new task. - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The that will be assigned to the new task. - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Gets the default CancellationToken of this - TaskFactory. - - - This property returns the default that will be assigned to all - tasks created by this factory unless another CancellationToken value is explicitly specified - during the call to the factory methods. - - - - - Gets the TaskScheduler of this - TaskFactory{TResult}. - - - This property returns the default scheduler for this factory. It will be used to schedule all - tasks unless another scheduler is explicitly specified during calls to this factory's methods. - If null, TaskScheduler.Current - will be used. - - - - - Gets the TaskCreationOptions - value of this TaskFactory{TResult}. - - - This property returns the default creation options for this factory. They will be used to create all - tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Gets the TaskContinuationOptions - value of this TaskFactory{TResult}. - - - This property returns the default continuation options for this factory. They will be used to create - all continuation tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Represents the current stage in the lifecycle of a . - - - - - The task has been initialized but has not yet been scheduled. - - - - - The task is waiting to be activated and scheduled internally by the .NET Framework infrastructure. - - - - - The task has been scheduled for execution but has not yet begun executing. - - - - - The task is running but has not yet completed. - - - - - The task has finished executing and is implicitly waiting for - attached child tasks to complete. - - - - - The task completed execution successfully. - - - - - The task acknowledged cancellation by throwing an OperationCanceledException2 with its own CancellationToken - while the token was in signaled state, or the task's CancellationToken was already signaled before the - task started executing. - - - - - The task completed due to an unhandled exception. - - - - - Specifies flags that control optional behavior for the creation and execution of tasks. - - - - - Specifies that the default behavior should be used. - - - - - A hint to a TaskScheduler to schedule a - task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to - be run sooner, and tasks scheduled later will be more likely to be run later. - - - - - Specifies that a task will be a long-running, course-grained operation. It provides a hint to the - TaskScheduler that oversubscription may be - warranted. - - - - - Specifies that a task is attached to a parent in the task hierarchy. - - - - - Task creation flags which are only used internally. - - - - Specifies "No internal task options" - - - Used to filter out internal vs. public task creation options. - - - Specifies that the task will be queued by the runtime before handing it over to the user. - This flag will be used to skip the cancellationtoken registration step, which is only meant for unstarted tasks. - - - - Specifies flags that control optional behavior for the creation and execution of continuation tasks. - - - - - Default = "Continue on any, no task options, run asynchronously" - Specifies that the default behavior should be used. Continuations, by default, will - be scheduled when the antecedent task completes, regardless of the task's final TaskStatus. - - - - - A hint to a TaskScheduler to schedule a - task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to - be run sooner, and tasks scheduled later will be more likely to be run later. - - - - - Specifies that a task will be a long-running, course-grained operation. It provides - a hint to the TaskScheduler that - oversubscription may be warranted. - - - - - Specifies that a task is attached to a parent in the task hierarchy. - - - - - Specifies that the continuation task should not be scheduled if its antecedent ran to completion. - This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should not be scheduled if its antecedent threw an unhandled - exception. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should not be scheduled if its antecedent was canceled. This - option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent ran to - completion. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent threw an - unhandled exception. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent was canceled. - This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be executed synchronously. With this option - specified, the continuation will be run on the same thread that causes the antecedent task to - transition into its final state. If the antecedent is already complete when the continuation is - created, the continuation will run on the thread creating the continuation. Only very - short-running continuations should be executed synchronously. - - - - - Represents an exception used to communicate task cancellation. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the - class with a specified error message and a reference to the inner exception that is the cause of - this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - - Initializes a new instance of the class - with a reference to the that has been canceled. - - A task that has been canceled. - - - - Gets the task associated with this exception. - - - It is permissible for no Task to be associated with a - , in which case - this property will return null. - - - - - Represents the producer side of a unbound to a - delegate, providing access to the consumer side through the property. - - - - It is often the case that a is desired to - represent another asynchronous operation. - TaskCompletionSource is provided for this purpose. It enables - the creation of a task that can be handed out to consumers, and those consumers can use the members - of the task as they would any other. However, unlike most tasks, the state of a task created by a - TaskCompletionSource is controlled explicitly by the methods on TaskCompletionSource. This enables the - completion of the external asynchronous operation to be propagated to the underlying Task. The - separation also ensures that consumers are not able to transition the state without access to the - corresponding TaskCompletionSource. - - - All members of are thread-safe - and may be used from multiple threads concurrently. - - - The type of the result value assocatied with this . - - - - Creates a . - - - - - Creates a - with the specified options. - - - The created - by this instance and accessible through its property - will be instantiated using the specified . - - The options to use when creating the underlying - . - - The represent options invalid for use - with a . - - - - - Creates a - with the specified state. - - The state to use as the underlying - 's AsyncState. - - - - Creates a with - the specified state and options. - - The options to use when creating the underlying - . - The state to use as the underlying - 's AsyncState. - - The represent options invalid for use - with a . - - - - - Attempts to transition the underlying - into the - Faulted - state. - - The exception to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The argument is null. - The was disposed. - - - - Attempts to transition the underlying - into the - Faulted - state. - - The collection of exceptions to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The argument is null. - There are one or more null elements in . - The collection is empty. - The was disposed. - - - - Transitions the underlying - into the - Faulted - state. - - The exception to bind to this . - The argument is null. - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - Faulted - state. - - The collection of exceptions to bind to this . - The argument is null. - There are one or more null elements in . - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Attempts to transition the underlying - into the - RanToCompletion - state. - - The result value to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - RanToCompletion - state. - - The result value to bind to this . - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - Canceled - state. - - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Attempts to transition the underlying - into the - Canceled - state. - - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Gets the created - by this . - - - This property enables a consumer access to the that is controlled by this instance. - The , , - , and - methods (and their "Try" variants) on this instance all result in the relevant state - transitions on this underlying Task. - - - - - An exception holder manages a list of exceptions for one particular task. - It offers the ability to aggregate, but more importantly, also offers intrinsic - support for propagating unhandled exceptions that are never observed. It does - this by aggregating and throwing if the holder is ever GC'd without the holder's - contents ever having been requested (e.g. by a Task.Wait, Task.get_Exception, etc). - - - - - Creates a new holder; it will be registered for finalization. - - The task this holder belongs to. - - - - A finalizer that repropagates unhandled exceptions. - - - - - Add an exception to the internal list. This will ensure the holder is - in the proper state (handled/unhandled) depending on the list's contents. - - An exception object (either an Exception or an - IEnumerable{Exception}) to add to the list. - - - - A private helper method that ensures the holder is considered - unhandled, i.e. it is registered for finalization. - - - - - A private helper method that ensures the holder is considered - handled, i.e. it is not registered for finalization. - - Whether this is called from the finalizer thread. - - - - Allocates a new aggregate exception and adds the contents of the list to - it. By calling this method, the holder assumes exceptions to have been - "observed", such that the finalization check will be subsequently skipped. - - Whether this is being called from a finalizer. - An extra exception to be included (optionally). - The aggregate exception to throw. - - - - Provides a set of static (Shared in Visual Basic) methods for working with specific kinds of - instances. - - - - - Creates a proxy Task that represents the - asynchronous operation of a Task{Task}. - - - It is often useful to be able to return a Task from a - Task{TResult}, where the inner Task represents work done as part of the outer Task{TResult}. However, - doing so results in a Task{Task}, which, if not dealt with carefully, could produce unexpected behavior. Unwrap - solves this problem by creating a proxy Task that represents the entire asynchronous operation of such a Task{Task}. - - The Task{Task} to unwrap. - The exception that is thrown if the - argument is null. - A Task that represents the asynchronous operation of the provided Task{Task}. - - - - Creates a proxy Task{TResult} that represents the - asynchronous operation of a Task{Task{TResult}}. - - - It is often useful to be able to return a Task{TResult} from a Task{TResult}, where the inner Task{TResult} - represents work done as part of the outer Task{TResult}. However, doing so results in a Task{Task{TResult}}, - which, if not dealt with carefully, could produce unexpected behavior. Unwrap solves this problem by - creating a proxy Task{TResult} that represents the entire asynchronous operation of such a Task{Task{TResult}}. - - The Task{Task{TResult}} to unwrap. - The exception that is thrown if the - argument is null. - A Task{TResult} that represents the asynchronous operation of the provided Task{Task{TResult}}. /// Unwraps a Task that returns another Task. - - - - Provides support for creating and scheduling - Tasks. - - - - There are many common patterns for which tasks are relevant. The - class encodes some of these patterns into methods that pick up default settings, which are - configurable through its constructors. - - - A default instance of is available through the - Task.Factory property. - - - - - - Initializes a instance with the default configuration. - - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The - TaskScheduler to use to schedule any tasks created with this TaskFactory. A null value - indicates that the current TaskScheduler should be used. - - - With this constructor, the - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to , unless it's null, in which case the property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory. - - - The exception that is thrown when the - argument or the - argument specifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory. - - - The default - TaskScheduler to use to schedule any Tasks created with this TaskFactory. A null value - indicates that TaskScheduler.Current should be used. - - - The exception that is thrown when the - argument or the - argumentspecifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to - , unless it's null, in which case the property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The started Task. - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors - and then calling - Start to schedule it for execution. However, - unless creation and scheduling must be separated, StartNew is the recommended - approach for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The that will be assigned to the new task. - The started Task. - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors - and then calling - Start to schedule it for execution. However, - unless creation and scheduling must be separated, StartNew is the recommended - approach for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The that will be assigned to the new - A TaskCreationOptions value that controls the behavior of the - created - Task. - The TaskScheduler - that is used to schedule the created Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The started Task. - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The that will be assigned to the new - The started Task. - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The TaskScheduler - that is used to schedule the created Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the asynchronous - operation. - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the asynchronous - operation. - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the asynchronous - operation. - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Check validity of options passed to FromAsync method - - The options to be validated. - determines type of FromAsync method that called this method - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Gets the default CancellationToken of this - TaskFactory. - - - This property returns the default that will be assigned to all - tasks created by this factory unless another CancellationToken value is explicitly specified - during the call to the factory methods. - - - - - Gets the TaskScheduler of this - TaskFactory. - - - This property returns the default scheduler for this factory. It will be used to schedule all - tasks unless another scheduler is explicitly specified during calls to this factory's methods. - If null, TaskScheduler.Current - will be used. - - - - - Gets the TaskCreationOptions - value of this TaskFactory. - - - This property returns the default creation options for this factory. They will be used to create all - tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Gets the TaskContinuationOptions - value of this TaskFactory. - - - This property returns the default continuation options for this factory. They will be used to create - all continuation tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Represents an abstract scheduler for tasks. - - - - TaskScheduler acts as the extension point for all - pluggable scheduling logic. This includes mechanisms such as how to schedule a task for execution, and - how scheduled tasks should be exposed to debuggers. - - - All members of the abstract type are thread-safe - and may be used from multiple threads concurrently. - - - - - - Queues a Task to the scheduler. - - - - A class derived from TaskScheduler - implements this method to accept tasks being scheduled on the scheduler. - A typical implementation would store the task in an internal data structure, which would - be serviced by threads that would execute those tasks at some time in the future. - - - This method is only meant to be called by the .NET Framework and - should not be called directly by the derived class. This is necessary - for maintaining the consistency of the system. - - - The Task to be queued. - The argument is null. - - - - Determines whether the provided Task - can be executed synchronously in this call, and if it can, executes it. - - - - A class derived from TaskScheduler implements this function to - support inline execution of a task on a thread that initiates a wait on that task object. Inline - execution is optional, and the request may be rejected by returning false. However, better - scalability typically results the more tasks that can be inlined, and in fact a scheduler that - inlines too little may be prone to deadlocks. A proper implementation should ensure that a - request executing under the policies guaranteed by the scheduler can successfully inline. For - example, if a scheduler uses a dedicated thread to execute tasks, any inlining requests from that - thread should succeed. - - - If a scheduler decides to perform the inline execution, it should do so by calling to the base - TaskScheduler's - TryExecuteTask method with the provided task object, propagating - the return value. It may also be appropriate for the scheduler to remove an inlined task from its - internal data structures if it decides to honor the inlining request. Note, however, that under - some circumstances a scheduler may be asked to inline a task that was not previously provided to - it with the method. - - - The derived scheduler is responsible for making sure that the calling thread is suitable for - executing the given task as far as its own scheduling and execution policies are concerned. - - - The Task to be - executed. - A Boolean denoting whether or not task has previously been - queued. If this parameter is True, then the task may have been previously queued (scheduled); if - False, then the task is known not to have been queued, and this call is being made in order to - execute the task inline without queueing it. - A Boolean value indicating whether the task was executed inline. - The argument is - null. - The was already - executed. - - - - Generates an enumerable of Task instances - currently queued to the scheduler waiting to be executed. - - - - A class derived from implements this method in order to support - integration with debuggers. This method will only be invoked by the .NET Framework when the - debugger requests access to the data. The enumerable returned will be traversed by debugging - utilities to access the tasks currently queued to this scheduler, enabling the debugger to - provide a representation of this information in the user interface. - - - It is important to note that, when this method is called, all other threads in the process will - be frozen. Therefore, it's important to avoid synchronization with other threads that may lead to - blocking. If synchronization is necessary, the method should prefer to throw a - than to block, which could cause a debugger to experience delays. Additionally, this method and - the enumerable returned must not modify any globally visible state. - - - The returned enumerable should never be null. If there are currently no queued tasks, an empty - enumerable should be returned instead. - - - For developers implementing a custom debugger, this method shouldn't be called directly, but - rather this functionality should be accessed through the internal wrapper method - GetScheduledTasksForDebugger: - internal Task[] GetScheduledTasksForDebugger(). This method returns an array of tasks, - rather than an enumerable. In order to retrieve a list of active schedulers, a debugger may use - another internal method: internal static TaskScheduler[] GetTaskSchedulersForDebugger(). - This static method returns an array of all active TaskScheduler instances. - GetScheduledTasksForDebugger then may be used on each of these scheduler instances to retrieve - the list of scheduled tasks for each. - - - An enumerable that allows traversal of tasks currently queued to this scheduler. - - - This scheduler is unable to generate a list of queued tasks at this time. - - - - - Retrieves some thread static state that can be cached and passed to multiple - TryRunInline calls, avoiding superflous TLS fetches. - - A bag of TLS state (or null if none exists). - - - - Attempts to execute the target task synchronously. - - The task to run. - True if the task may have been previously queued, - false if the task was absolutely not previously queued. - The state retrieved from GetThreadStatics - True if it ran, false otherwise. - - - - Attempts to dequeue a Task that was previously queued to - this scheduler. - - The Task to be dequeued. - A Boolean denoting whether the argument was successfully dequeued. - The argument is null. - - - - Notifies the scheduler that a work item has made progress. - - - - - Initializes the . - - - - - Frees all resources associated with this scheduler. - - - - - Creates a - associated with the current . - - - All Task instances queued to - the returned scheduler will be executed through a call to the - Post method - on that context. - - - A associated with - the current SynchronizationContext, as - determined by SynchronizationContext.Current. - - - The current SynchronizationContext may not be used as a TaskScheduler. - - - - - Attempts to execute the provided Task - on this scheduler. - - - - Scheduler implementations are provided with Task - instances to be executed through either the method or the - method. When the scheduler deems it appropriate to run the - provided task, should be used to do so. TryExecuteTask handles all - aspects of executing a task, including action invocation, exception handling, state management, - and lifecycle control. - - - must only be used for tasks provided to this scheduler by the .NET - Framework infrastructure. It should not be used to execute arbitrary tasks obtained through - custom mechanisms. - - - - A Task object to be executed. - - The is not associated with this scheduler. - - A Boolean that is true if was successfully executed, false if it - was not. A common reason for execution failure is that the task had previously been executed or - is in the process of being executed by another thread. - - - - Provides an array of all queued Task instances - for the debugger. - - - The returned array is populated through a call to . - Note that this function is only meant to be invoked by a debugger remotely. - It should not be called by any other codepaths. - - An array of Task instances. - - This scheduler is unable to generate a list of queued tasks at this time. - - - - - Provides an array of all active TaskScheduler - instances for the debugger. - - - This function is only meant to be invoked by a debugger remotely. - It should not be called by any other codepaths. - - An array of TaskScheduler instances. - - - - Registers a new TaskScheduler instance in the global collection of schedulers. - - - - - Removes a TaskScheduler instance from the global collection of schedulers. - - - - - Indicates the maximum concurrency level this - is able to support. - - - - - Indicates whether this is a custom scheduler, in which case the safe code paths will be taken upon task entry - using a CAS to transition from queued state to executing. - - - - - Gets the default TaskScheduler instance. - - - - - Gets the TaskScheduler - associated with the currently executing task. - - - When not called from within a task, will return the scheduler. - - - - - Gets the unique ID for this . - - - - - Occurs when a faulted 's unobserved exception is about to trigger exception escalation - policy, which, by default, would terminate the process. - - - This AppDomain-wide event provides a mechanism to prevent exception - escalation policy (which, by default, terminates the process) from triggering. - Each handler is passed a - instance, which may be used to examine the exception and to mark it as observed. - - - - - Nested class that provides debugger view for TaskScheduler - - - - Default thread pool scheduler. - - - - A TaskScheduler implementation that executes all tasks queued to it through a call to - on the - that its associated with. The default constructor for this class binds to the current - - - - - Constructs a SynchronizationContextTaskScheduler associated with - - This constructor expects to be set. - - - - Implemetation of for this scheduler class. - - Simply posts the tasks to be executed on the associated . - - - - - - Implementation of for this scheduler class. - - The task will be executed inline only if the call happens within - the associated . - - - - - - - Implementes the property for - this scheduler class. - - By default it returns 1, because a based - scheduler only supports execution on a single thread. - - - - - Provides data for the event that is raised when a faulted 's - exception goes unobserved. - - - The Exception property is used to examine the exception without marking it - as observed, whereas the method is used to mark the exception - as observed. Marking the exception as observed prevents it from triggering exception escalation policy - which, by default, terminates the process. - - - - - Initializes a new instance of the class - with the unobserved exception. - - The Exception that has gone unobserved. - - - - Marks the as "observed," thus preventing it - from triggering exception escalation policy which, by default, terminates the process. - - - - - Gets whether this exception has been marked as "observed." - - - - - The Exception that went unobserved. - - - - - Represents an exception used to communicate an invalid operation by a - . - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the - class using the default error message and a reference to the inner exception that is the cause of - this exception. - - The exception that is the cause of the current exception. - - - - Initializes a new instance of the - class with a specified error message and a reference to the inner exception that is the cause of - this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/ensureRedirect.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp71+wpa81/ensureRedirect.xml deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.IO.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.IO.dll deleted file mode 100644 index 32dd41b78..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.IO.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.IO.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.IO.xml deleted file mode 100644 index e83273427..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.IO.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - System.IO - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Found invalid data while decoding.. - - - - - The exception that is thrown when a data stream is in an invalid format. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a reference to the inner exception that is the cause of this exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. If the parameter is not null, the current exception is raised in a catch block that handles the inner exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.Runtime.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.Runtime.dll deleted file mode 100644 index 118fcce20..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.Runtime.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.Runtime.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.Runtime.xml deleted file mode 100644 index 93cb00d74..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.Runtime.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - System.Runtime - - - - Defines a provider for progress updates. - The type of progress update value. - - - Reports a progress update. - The value of the updated progress. - - - Identities the async state machine type for this method. - - - Identities the state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - Gets the type that implements the state machine. - - - Initializes the attribute. - The type that implements the state machine. - - - - Allows you to obtain the method or property name of the caller to the method. - - - - - Allows you to obtain the line number in the source file at which the method is called. - - - - - Allows you to obtain the full path of the source file that contains the caller. - This is the file path at the time of compile. - - - - Identities the iterator state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.Threading.Tasks.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.Threading.Tasks.dll deleted file mode 100644 index a089d474d..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.Threading.Tasks.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.Threading.Tasks.xml deleted file mode 100644 index 6c770122e..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/System.Threading.Tasks.xml +++ /dev/null @@ -1,8969 +0,0 @@ - - - - System.Threading.Tasks - - - - Represents one or more errors that occur during application execution. - - is used to consolidate multiple failures into a single, throwable - exception object. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with - a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a specified error - message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - The argument - is null. - - - - Initializes a new instance of the class with - references to the inner exceptions that are the cause of this exception. - - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with - references to the inner exceptions that are the cause of this exception. - - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with a specified error - message and references to the inner exceptions that are the cause of this exception. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with a specified error - message and references to the inner exceptions that are the cause of this exception. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Allocates a new aggregate exception with the specified message and list of inner exceptions. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Returns the that is the root cause of this exception. - - - - - Invokes a handler on each contained by this . - - The predicate to execute for each exception. The predicate accepts as an - argument the to be processed and returns a Boolean to indicate - whether the exception was handled. - - Each invocation of the returns true or false to indicate whether the - was handled. After all invocations, if any exceptions went - unhandled, all unhandled exceptions will be put into a new - which will be thrown. Otherwise, the method simply returns. If any - invocations of the throws an exception, it will halt the processing - of any more exceptions and immediately propagate the thrown exception as-is. - - An exception contained by this was not handled. - The argument is - null. - - - - Flattens an instances into a single, new instance. - - A new, flattened . - - If any inner exceptions are themselves instances of - , this method will recursively flatten all of them. The - inner exceptions returned in the new - will be the union of all of the the inner exceptions from exception tree rooted at the provided - instance. - - - - - Creates and returns a string representation of the current . - - A string representation of the current exception. - - - - Gets a read-only collection of the instances that caused the - current exception. - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to One or more errors occurred.. - - - - - Looks up a localized string similar to An element of innerExceptions was null.. - - - - - Looks up a localized string similar to {0}{1}---> (Inner Exception #{2}) {3}{4}{5}. - - - - - Looks up a localized string similar to No tokens were supplied.. - - - - - Looks up a localized string similar to The CancellationTokenSource associated with this CancellationToken has been disposed.. - - - - - Looks up a localized string similar to The CancellationTokenSource has been disposed.. - - - - - Looks up a localized string similar to The SyncRoot property may not be used for the synchronization of concurrent collections.. - - - - - Looks up a localized string similar to The array is multidimensional, or the type parameter for the set cannot be cast automatically to the type of the destination array.. - - - - - Looks up a localized string similar to The index is equal to or greater than the length of the array, or the number of elements in the dictionary is greater than the available space from index to the end of the destination array.. - - - - - Looks up a localized string similar to The capacity argument must be greater than or equal to zero.. - - - - - Looks up a localized string similar to The concurrencyLevel argument must be positive.. - - - - - Looks up a localized string similar to The index argument is less than zero.. - - - - - Looks up a localized string similar to TKey is a reference type and item.Key is null.. - - - - - Looks up a localized string similar to The key already existed in the dictionary.. - - - - - Looks up a localized string similar to The source argument contains duplicate keys.. - - - - - Looks up a localized string similar to The key was of an incorrect type for this dictionary.. - - - - - Looks up a localized string similar to The value was of an incorrect type for this dictionary.. - - - - - Looks up a localized string similar to The lazily-initialized type does not have a public, parameterless constructor.. - - - - - Looks up a localized string similar to ValueFactory returned null.. - - - - - Looks up a localized string similar to The spinCount argument must be in the range 0 to {0}, inclusive.. - - - - - Looks up a localized string similar to There are too many threads currently waiting on the event. A maximum of {0} waiting threads are supported.. - - - - - Looks up a localized string similar to The event has been disposed.. - - - - - Looks up a localized string similar to The operation was canceled.. - - - - - Looks up a localized string similar to The condition argument is null.. - - - - - Looks up a localized string similar to The timeout must represent a value between -1 and Int32.MaxValue, inclusive.. - - - - - Looks up a localized string similar to The specified TaskContinuationOptions combined LongRunning and ExecuteSynchronously. Synchronous continuations should not be long running.. - - - - - Looks up a localized string similar to The specified TaskContinuationOptions excluded all continuation kinds.. - - - - - Looks up a localized string similar to (Internal)An attempt was made to create a LongRunning SelfReplicating task.. - - - - - Looks up a localized string similar to The value needs to translate in milliseconds to -1 (signifying an infinite timeout), 0 or a positive integer less than or equal to Int32.MaxValue.. - - - - - Looks up a localized string similar to The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer.. - - - - - Looks up a localized string similar to A task may only be disposed if it is in a completion state (RanToCompletion, Faulted or Canceled).. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.LongRunning in calls to FromAsync.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.PreferFairness in calls to FromAsync.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.SelfReplicating in calls to FromAsync.. - - - - - Looks up a localized string similar to FromAsync was called with a TaskManager that had already shut down.. - - - - - Looks up a localized string similar to The tasks argument contains no tasks.. - - - - - Looks up a localized string similar to It is invalid to exclude specific continuation kinds for continuations off of multiple tasks.. - - - - - Looks up a localized string similar to The tasks argument included a null value.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task that was already started.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a continuation task.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task that has already completed.. - - - - - Looks up a localized string similar to Start may not be called on a task that was already started.. - - - - - Looks up a localized string similar to Start may not be called on a continuation task.. - - - - - Looks up a localized string similar to Start may not be called on a task with null action.. - - - - - Looks up a localized string similar to Start may not be called on a promise-style task.. - - - - - Looks up a localized string similar to Start may not be called on a task that has completed.. - - - - - Looks up a localized string similar to The task has been disposed.. - - - - - Looks up a localized string similar to The tasks array included at least one null element.. - - - - - Looks up a localized string similar to The awaited task has not yet completed.. - - - - - Looks up a localized string similar to A task was canceled.. - - - - - Looks up a localized string similar to The exceptions collection was empty.. - - - - - Looks up a localized string similar to The exceptions collection included at least one null element.. - - - - - Looks up a localized string similar to A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread.. - - - - - Looks up a localized string similar to (Internal)Expected an Exception or an IEnumerable<Exception>. - - - - - Looks up a localized string similar to ExecuteTask may not be called for a task which was already executed.. - - - - - Looks up a localized string similar to ExecuteTask may not be called for a task which was previously queued to a different TaskScheduler.. - - - - - Looks up a localized string similar to The current SynchronizationContext may not be used as a TaskScheduler.. - - - - - Looks up a localized string similar to The TryExecuteTaskInline call to the underlying scheduler succeeded, but the task body was not invoked.. - - - - - Looks up a localized string similar to An exception was thrown by a TaskScheduler.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.SelfReplicating for a Task<TResult>.. - - - - - Looks up a localized string similar to {Not yet computed}. - - - - - Looks up a localized string similar to A task's Exception may only be set directly if the task was created without a function.. - - - - - Looks up a localized string similar to An attempt was made to transition a task to a final state when it had already completed.. - - - - - Represents a thread-safe collection of keys and values. - - The type of the keys in the dictionary. - The type of the values in the dictionary. - - All public and protected members of are thread-safe and may be used - concurrently from multiple threads. - - - - - Initializes a new instance of the - class that is empty, has the default concurrency level, has the default initial capacity, and - uses the default comparer for the key type. - - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level and capacity, and uses the default - comparer for the key type. - - The estimated number of threads that will update the - concurrently. - The initial number of elements that the - can contain. - is - less than 1. - is less than - 0. - - - - Initializes a new instance of the - class that contains elements copied from the specified , has the default concurrency - level, has the default initial capacity, and uses the default comparer for the key type. - - The whose elements are copied to - the new - . - is a null reference - (Nothing in Visual Basic). - contains one or more - duplicate keys. - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level and capacity, and uses the specified - . - - The - implementation to use when comparing keys. - is a null reference - (Nothing in Visual Basic). - - - - Initializes a new instance of the - class that contains elements copied from the specified , has the default concurrency level, has the default - initial capacity, and uses the specified - . - - The whose elements are copied to - the new - . - The - implementation to use when comparing keys. - is a null reference - (Nothing in Visual Basic). -or- - is a null reference (Nothing in Visual Basic). - - - - - Initializes a new instance of the - class that contains elements copied from the specified , - has the specified concurrency level, has the specified initial capacity, and uses the specified - . - - The estimated number of threads that will update the - concurrently. - The whose elements are copied to the new - . - The implementation to use - when comparing keys. - - is a null reference (Nothing in Visual Basic). - -or- - is a null reference (Nothing in Visual Basic). - - - is less than 1. - - contains one or more duplicate keys. - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level, has the specified initial capacity, and - uses the specified . - - The estimated number of threads that will update the - concurrently. - The initial number of elements that the - can contain. - The - implementation to use when comparing keys. - - is less than 1. -or- - is less than 0. - - is a null reference - (Nothing in Visual Basic). - - - - Attempts to add the specified key and value to the . - - The key of the element to add. - The value of the element to add. The value can be a null reference (Nothing - in Visual Basic) for reference types. - true if the key/value pair was added to the - successfully; otherwise, false. - is null reference - (Nothing in Visual Basic). - The - contains too many elements. - - - - Determines whether the contains the specified - key. - - The key to locate in the . - true if the contains an element with - the specified key; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Attempts to remove and return the the value with the specified key from the - . - - The key of the element to remove and return. - When this method returns, contains the object removed from the - or the default value of - if the operation failed. - true if an object was removed successfully; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Removes the specified key from the dictionary if it exists and returns its associated value. - If matchValue flag is set, the key will be removed only if is associated with a particular - value. - - The key to search for and remove if it exists. - The variable into which the removed value, if found, is stored. - Whether removal of the key is conditional on its value. - The conditional value to compare against if is true - - - - - Attempts to get the value associated with the specified key from the . - - The key of the value to get. - When this method returns, contains the object from - the - with the spedified key or the default value of - , if the operation failed. - true if the key was found in the ; - otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Compares the existing value for the specified key with a specified value, and if they’re equal, - updates the key with a third value. - - The key whose value is compared with and - possibly replaced. - The value that replaces the value of the element with if the comparison results in equality. - The value that is compared to the value of the element with - . - true if the value with was equal to and replaced with ; otherwise, - false. - is a null - reference. - - - - Removes all keys and values from the . - - - - - Copies the elements of the to an array of - type , starting at the - specified array index. - - The one-dimensional array of type - that is the destination of the elements copied from the . The array must have zero-based indexing. - The zero-based index in at which copying - begins. - is a null reference - (Nothing in Visual Basic). - is less than - 0. - is equal to or greater than - the length of the . -or- The number of elements in the source - is greater than the available space from to the end of the destination - . - - - - Copies the key and value pairs stored in the to a - new array. - - A new array containing a snapshot of key and value pairs copied from the . - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToPairs. - - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToEntries. - - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToObjects. - - - - Returns an enumerator that iterates through the . - An enumerator for the . - - The enumerator returned from the dictionary is safe to use concurrently with - reads and writes to the dictionary, however it does not represent a moment-in-time snapshot - of the dictionary. The contents exposed through the enumerator may contain modifications - made to the dictionary after was called. - - - - - Shared internal implementation for inserts and updates. - If key exists, we always return false; and if updateIfExists == true we force update with value; - If key doesn't exist, we always add value and return true; - - - - - Adds a key/value pair to the - if the key does not already exist. - - The key of the element to add. - The function used to generate a value for the key - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The value for the key. This will be either the existing value for the key if the - key is already in the dictionary, or the new value for the key as returned by valueFactory - if the key was not in the dictionary. - - - - Adds a key/value pair to the - if the key does not already exist. - - The key of the element to add. - the value to be added, if the key does not already exist - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The value for the key. This will be either the existing value for the key if the - key is already in the dictionary, or the new value if the key was not in the dictionary. - - - - Adds a key/value pair to the if the key does not already - exist, or updates a key/value pair in the if the key - already exists. - - The key to be added or whose value should be updated - The function used to generate a value for an absent key - The function used to generate a new value for an existing key - based on the key's existing value - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The new value for the key. This will be either be the result of addValueFactory (if the key was - absent) or the result of updateValueFactory (if the key was present). - - - - Adds a key/value pair to the if the key does not already - exist, or updates a key/value pair in the if the key - already exists. - - The key to be added or whose value should be updated - The value to be added for an absent key - The function used to generate a new value for an existing key based on - the key's existing value - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The new value for the key. This will be either be the result of addValueFactory (if the key was - absent) or the result of updateValueFactory (if the key was present). - - - - Adds the specified key and value to the . - - The object to use as the key of the element to add. - The object to use as the value of the element to add. - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - - An element with the same key already exists in the . - - - - Removes the element with the specified key from the . - - The key of the element to remove. - true if the element is successfully remove; otherwise false. This method also returns - false if - was not found in the original . - - is a null reference - (Nothing in Visual Basic). - - - - Adds the specified value to the - with the specified key. - - The - structure representing the key and value to add to the . - The of is null. - The - contains too many elements. - An element with the same key already exists in the - - - - - Determines whether the - contains a specific key and value. - - The - structure to locate in the . - true if the is found in the ; otherwise, false. - - - - Removes a key and value from the dictionary. - - The - structure representing the key and value to remove from the . - true if the key and value represented by is successfully - found and removed; otherwise, false. - The Key property of is a null reference (Nothing in Visual Basic). - - - Returns an enumerator that iterates through the . - An enumerator for the . - - The enumerator returned from the dictionary is safe to use concurrently with - reads and writes to the dictionary, however it does not represent a moment-in-time snapshot - of the dictionary. The contents exposed through the enumerator may contain modifications - made to the dictionary after was called. - - - - - Adds the specified key and value to the dictionary. - - The object to use as the key. - The object to use as the value. - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - - is of a type that is not assignable to the key type of the . -or- - is of a type that is not assignable to , - the type of values in the . - -or- A value with the same key already exists in the . - - - - - Gets whether the contains an - element with the specified key. - - The key to locate in the . - true if the contains - an element with the specified key; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - Provides an for the - . - An for the . - - - - Removes the element with the specified key from the . - - The key of the element to remove. - is a null reference - (Nothing in Visual Basic). - - - - Copies the elements of the to an array, starting - at the specified array index. - - The one-dimensional array that is the destination of the elements copied from - the . The array must have zero-based - indexing. - The zero-based index in at which copying - begins. - is a null reference - (Nothing in Visual Basic). - is less than - 0. - is equal to or greater than - the length of the . -or- The number of elements in the source - is greater than the available space from to the end of the destination - . - - - - Replaces the internal table with a larger one. To prevent multiple threads from resizing the - table as a result of races, the table of buckets that was deemed too small is passed in as - an argument to GrowTable(). GrowTable() obtains a lock, and then checks whether the bucket - table has been replaced in the meantime or not. - - Reference to the bucket table that was deemed too small. - - - - Computes the bucket and lock number for a particular key. - - - - - Acquires all locks for this hash table, and increments locksAcquired by the number - of locks that were successfully acquired. The locks are acquired in an increasing - order. - - - - - Acquires a contiguous range of locks for this hash table, and increments locksAcquired - by the number of locks that were successfully acquired. The locks are acquired in an - increasing order. - - - - - Releases a contiguous range of locks. - - - - - Gets a collection containing the keys in the dictionary. - - - - - Gets a collection containing the values in the dictionary. - - - - - A helper method for asserts. - - - - - Get the data array to be serialized - - - - - Construct the dictionary from a previously seiralized one - - - - - Gets or sets the value associated with the specified key. - - The key of the value to get or set. - The value associated with the specified key. If the specified key is not found, a get - operation throws a - , and a set operation creates a new - element with the specified key. - is a null reference - (Nothing in Visual Basic). - The property is retrieved and - - does not exist in the collection. - - - - Gets the number of key/value pairs contained in the . - - The dictionary contains too many - elements. - The number of key/value paris contained in the . - Count has snapshot semantics and represents the number of items in the - at the moment when Count was accessed. - - - - Gets a value that indicates whether the is empty. - - true if the is empty; otherwise, - false. - - - - Gets a collection containing the keys in the . - - An containing the keys in the - . - - - - Gets a collection containing the values in the . - - An containing the values in - the - . - - - - Gets a value indicating whether the dictionary is read-only. - - true if the is - read-only; otherwise, false. For , this property always returns - false. - - - - Gets a value indicating whether the has a fixed size. - - true if the has a - fixed size; otherwise, false. For , this property always - returns false. - - - - Gets a value indicating whether the is read-only. - - true if the is - read-only; otherwise, false. For , this property always - returns false. - - - - Gets an containing the keys of the . - - An containing the keys of the . - - - - Gets an containing the values in the . - - An containing the values in the . - - - - Gets or sets the value associated with the specified key. - - The key of the value to get or set. - The value associated with the specified key, or a null reference (Nothing in Visual Basic) - if is not in the dictionary or is of a type that is - not assignable to the key type of the . - is a null reference - (Nothing in Visual Basic). - - A value is being assigned, and is of a type that is not assignable to the - key type of the . -or- A value is being - assigned, and is of a type that is not assignable to the value type - of the - - - - - Gets a value indicating whether access to the is - synchronized with the SyncRoot. - - true if access to the is synchronized - (thread safe); otherwise, false. For , this property always - returns false. - - - - Gets an object that can be used to synchronize access to the . This property is not supported. - - The SyncRoot property is not supported. - - - - The number of concurrent writes for which to optimize by default. - - - - - A node in a singly-linked list representing a particular hash table bucket. - - - - - A private class to represent enumeration over the dictionary that implements the - IDictionaryEnumerator interface. - - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - Represents an asynchronous method builder. - - - A cached VoidTaskResult task used for builders that complete synchronously. - - - The generic builder object to which this non-generic instance delegates. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state. - - The builder is not initialized. - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - - Gets the for this builder. - The representing the builder's asynchronous operation. - The builder is not initialized. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - Holds state related to the builder's IAsyncStateMachine. - This is a mutable struct. Be very delicate with it. - - - A reference to the heap-allocated state machine object associated with this builder. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument is null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - - Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method. - On first invocation, the supplied state machine will be boxed. - - Specifies the type of the method builder used. - Specifies the type of the state machine used. - The builder. - The state machine. - An Action to provide to the awaiter. - - - Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext. - - - The context with which to run MoveNext. - - - The state machine whose MoveNext method should be invoked. - - - Initializes the runner. - The context with which to run MoveNext. - - - Invokes MoveNext under the provided context. - - - Cached delegate used with ExecutionContext.Run. - - - Invokes the MoveNext method on the supplied IAsyncStateMachine. - The IAsyncStateMachine machine instance. - - - - Provides a builder for asynchronous methods that return void. - This type is intended for compiler use only. - - - - The synchronization context associated with this operation. - - - State related to the IAsyncStateMachine. - - - An object used by the debugger to uniquely identify this builder. Lazily initialized. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Registers with UnobservedTaskException to suppress exception crashing. - - - Non-zero if PreventUnobservedTaskExceptions has already been invoked. - - - Initializes a new . - The initialized . - - - Initializes the . - The synchronizationContext associated with this operation. This may be null. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument was null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - Completes the method builder successfully. - - - Faults the method builder with an exception. - The exception that is the cause of this fault. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - - - Notifies the current synchronization context that the operation completed. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger and only in a single-threaded manner. - - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - A cached task for default(TResult). - - - State related to the IAsyncStateMachine. - - - The lazily-initialized task. - Must be named m_task for debugger step-over to work correctly. - - - The lazily-initialized task completion source. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state with the specified result. - - The result to use to complete the task. - The task has already completed. - - - - Completes the builder by using either the supplied completed task, or by completing - the builder's previously accessed task using default(TResult). - - A task already completed with the value default(TResult). - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - This should only be invoked from within an asynchronous method, - and only by the debugger. - - - - - Gets a task for the specified result. This will either - be a cached or new task, never null. - - The result for which we need a task. - The completed task containing the result. - - - Gets the lazily-initialized TaskCompletionSource. - - - Gets the for this builder. - The representing the builder's asynchronous operation. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - Provides a base class used to cache tasks of a specific return type. - Specifies the type of results the cached tasks return. - - - - A singleton cache for this result type. - This may be null if there are no cached tasks for this TResult. - - - - Creates a non-disposable task. - The result for the task. - The cacheable task. - - - Creates a cache. - A task cache for this result type. - - - Gets a cached task if one exists. - The result for which we want a cached task. - A cached task if one exists; otherwise, null. - - - Provides a cache for Boolean tasks. - - - A true task. - - - A false task. - - - Gets a cached task for the Boolean result. - true or false - A cached task for the Boolean result. - - - Provides a cache for zero Int32 tasks. - - - The minimum value, inclusive, for which we want a cached task. - - - The maximum value, exclusive, for which we want a cached task. - - - The cache of Task{Int32}. - - - Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). - - - Gets a cached task for the zero Int32 result. - The integer value - A cached task for the Int32 result or null if not cached. - - - - Represents state machines generated for asynchronous methods. - This type is intended for compiler use only. - - - - Moves the state machine to its next state. - - - Configures the state machine with a heap-allocated replica. - The heap-allocated replica. - - - - Represents an awaiter used to schedule continuations when an await operation completes. - - - - - Represents an operation that will schedule continuations when the operation completes. - - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information. - - - Used with Task(of void) - - - - An interface similar to the one added in .NET 4.0. - - - - The exception that is thrown in a thread upon cancellation of an operation that the thread was executing. - - - Initializes the exception. - - - Initializes the exception. - The error message that explains the reason for the exception. - - - Initializes the exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - Initializes the exception. - A cancellation token associated with the operation that was canceled. - - - Initializes the exception. - The error message that explains the reason for the exception. - A cancellation token associated with the operation that was canceled. - - - Initializes the exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - A cancellation token associated with the operation that was canceled. - - - Gets a token associated with the operation that was canceled. - - - - A dummy replacement for the .NET internal class StackCrawlMark. - - - - - Propogates notification that operations should be canceled. - - - - A may be created directly in an unchangeable canceled or non-canceled state - using the CancellationToken's constructors. However, to have a CancellationToken that can change - from a non-canceled to a canceled state, - CancellationTokenSource must be used. - CancellationTokenSource exposes the associated CancellationToken that may be canceled by the source through its - Token property. - - - Once canceled, a token may not transition to a non-canceled state, and a token whose - is false will never change to one that can be canceled. - - - All members of this struct are thread-safe and may be used concurrently from multiple threads. - - - - - - Internal constructor only a CancellationTokenSource should create a CancellationToken - - - - - Initializes the CancellationToken. - - - The canceled state for the token. - - - Tokens created with this constructor will remain in the canceled state specified - by the parameter. If is false, - both and will be false. - If is true, - both and will be true. - - - - - Registers a delegate that will be called when this CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - A Boolean value that indicates whether to capture - the current SynchronizationContext and use it - when invoking the . - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The state to pass to the when the delegate is invoked. This may be null. - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The state to pass to the when the delegate is invoked. This may be null. - A Boolean value that indicates whether to capture - the current SynchronizationContext and use it - when invoking the . - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Determines whether the current CancellationToken instance is equal to the - specified token. - - The other CancellationToken to which to compare this - instance. - True if the instances are equal; otherwise, false. Two tokens are equal if they are associated - with the same CancellationTokenSource or if they were both constructed - from public CancellationToken constructors and their values are equal. - - - - Determines whether the current CancellationToken instance is equal to the - specified . - - The other object to which to compare this instance. - True if is a CancellationToken - and if the two instances are equal; otherwise, false. Two tokens are equal if they are associated - with the same CancellationTokenSource or if they were both constructed - from public CancellationToken constructors and their values are equal. - An associated CancellationTokenSource has been disposed. - - - - Serves as a hash function for a CancellationToken. - - A hash code for the current CancellationToken instance. - - - - Determines whether two CancellationToken instances are equal. - - The first instance. - The second instance. - True if the instances are equal; otherwise, false. - An associated CancellationTokenSource has been disposed. - - - - Determines whether two CancellationToken instances are not equal. - - The first instance. - The second instance. - True if the instances are not equal; otherwise, false. - An associated CancellationTokenSource has been disposed. - - - - Throws a OperationCanceledException if - this token has had cancellation requested. - - - This method provides functionality equivalent to: - - if (token.IsCancellationRequested) - throw new OperationCanceledException(token); - - - The token has had cancellation requested. - The associated CancellationTokenSource has been disposed. - - - - Returns an empty CancellationToken value. - - - The value returned by this property will be non-cancelable by default. - - - - - Gets whether cancellation has been requested for this token. - - Whether cancellation has been requested for this token. - - - This property indicates whether cancellation has been requested for this token, - either through the token initially being construted in a canceled state, or through - calling Cancel - on the token's associated . - - - If this property is true, it only guarantees that cancellation has been requested. - It does not guarantee that every registered handler - has finished executing, nor that cancellation requests have finished propagating - to all registered handlers. Additional synchronization may be required, - particularly in situations where related objects are being canceled concurrently. - - - - - - Gets whether this token is capable of being in the canceled state. - - - If CanBeCanceled returns false, it is guaranteed that the token will never transition - into a canceled state, meaning that will never - return true. - - - - - Gets a that is signaled when the token is canceled. - - Accessing this property causes a WaitHandle - to be instantiated. It is preferable to only use this property when necessary, and to then - dispose the associated instance at the earliest opportunity (disposing - the source will dispose of this allocated handle). The handle should not be closed or disposed directly. - - The associated CancellationTokenSource has been disposed. - - - - Represents a callback delegate that has been registered with a CancellationToken. - - - To unregister a callback, dispose the corresponding Registration instance. - - - - - Attempts to deregister the item. If it's already being run, this may fail. - Entails a full memory fence. - - True if the callback was found and deregistered, false otherwise. - - - - Disposes of the registration and unregisters the target callback from the associated - CancellationToken. - If the target callback is currently executing this method will wait until it completes, except - in the degenerate cases where a callback method deregisters itself. - - - - - Determines whether two CancellationTokenRegistration - instances are equal. - - The first instance. - The second instance. - True if the instances are equal; otherwise, false. - - - - Determines whether two CancellationTokenRegistration instances are not equal. - - The first instance. - The second instance. - True if the instances are not equal; otherwise, false. - - - - Determines whether the current CancellationTokenRegistration instance is equal to the - specified . - - The other object to which to compare this instance. - True, if both this and are equal. False, otherwise. - Two CancellationTokenRegistration instances are equal if - they both refer to the output of a single call to the same Register method of a - CancellationToken. - - - - - Determines whether the current CancellationToken instance is equal to the - specified . - - The other CancellationTokenRegistration to which to compare this instance. - True, if both this and are equal. False, otherwise. - Two CancellationTokenRegistration instances are equal if - they both refer to the output of a single call to the same Register method of a - CancellationToken. - - - - - Serves as a hash function for a CancellationTokenRegistration.. - - A hash code for the current CancellationTokenRegistration instance. - - - - Signals to a that it should be canceled. - - - - is used to instantiate a - (via the source's Token property) - that can be handed to operations that wish to be notified of cancellation or that can be used to - register asynchronous operations for cancellation. That token may have cancellation requested by - calling to the source's Cancel - method. - - - All members of this class, except Dispose, are thread-safe and may be used - concurrently from multiple threads. - - - - - The ID of the thread currently executing the main body of CTS.Cancel() - this helps us to know if a call to ctr.Dispose() is running 'within' a cancellation callback. - This is updated as we move between the main thread calling cts.Cancel() and any syncContexts that are used to - actually run the callbacks. - - - - Initializes the . - - - - - Communicates a request for cancellation. - - - - The associated will be - notified of the cancellation and will transition to a state where - IsCancellationRequested returns true. - Any callbacks or cancelable operations - registered with the will be executed. - - - Cancelable operations and callbacks registered with the token should not throw exceptions. - However, this overload of Cancel will aggregate any exceptions thrown into a , - such that one callback throwing an exception will not prevent other registered callbacks from being executed. - - - The that was captured when each callback was registered - will be reestablished when the callback is invoked. - - - An aggregate exception containing all the exceptions thrown - by the registered callbacks on the associated . - This has been disposed. - - - - Communicates a request for cancellation. - - - - The associated will be - notified of the cancellation and will transition to a state where - IsCancellationRequested returns true. - Any callbacks or cancelable operations - registered with the will be executed. - - - Cancelable operations and callbacks registered with the token should not throw exceptions. - If is true, an exception will immediately propagate out of the - call to Cancel, preventing the remaining callbacks and cancelable operations from being processed. - If is false, this overload will aggregate any - exceptions thrown into a , - such that one callback throwing an exception will not prevent other registered callbacks from being executed. - - - The that was captured when each callback was registered - will be reestablished when the callback is invoked. - - - Specifies whether exceptions should immediately propagate. - An aggregate exception containing all the exceptions thrown - by the registered callbacks on the associated . - This has been disposed. - - - - Releases the resources used by this . - - - This method is not thread-safe for any other concurrent calls. - - - - - Throws an exception if the source has been disposed. - - - - - InternalGetStaticSource() - - Whether the source should be set. - A static source to be shared among multiple tokens. - - - - Registers a callback object. If cancellation has already occurred, the - callback will have been run by the time this method returns. - - - - - - - - - - Invoke the Canceled event. - - - The handlers are invoked synchronously in LIFO order. - - - - - Creates a CancellationTokenSource that will be in the canceled state - when any of the source tokens are in the canceled state. - - The first CancellationToken to observe. - The second CancellationToken to observe. - A CancellationTokenSource that is linked - to the source tokens. - A CancellationTokenSource associated with - one of the source tokens has been disposed. - - - - Creates a CancellationTokenSource that will be in the canceled state - when any of the source tokens are in the canceled state. - - The CancellationToken instances to observe. - A CancellationTokenSource that is linked - to the source tokens. - is null. - A CancellationTokenSource associated with - one of the source tokens has been disposed. - - - - Gets whether cancellation has been requested for this CancellationTokenSource. - - Whether cancellation has been requested for this CancellationTokenSource. - - - This property indicates whether cancellation has been requested for this token source, such as - due to a call to its - Cancel method. - - - If this property returns true, it only guarantees that cancellation has been requested. It does not - guarantee that every handler registered with the corresponding token has finished executing, nor - that cancellation requests have finished propagating to all registered handlers. Additional - synchronization may be required, particularly in situations where related objects are being - canceled concurrently. - - - - - - A simple helper to determine whether cancellation has finished. - - - - - A simple helper to determine whether disposal has occured. - - - - - The ID of the thread that is running callbacks. - - - - - Gets the CancellationToken - associated with this . - - The CancellationToken - associated with this . - The token source has been - disposed. - - - - - - - - - - - - - - The currently executing callback - - - - - A helper class for collating the various bits of information required to execute - cancellation callbacks. - - - - - InternalExecuteCallbackSynchronously_GeneralPath - This will be called on the target synchronization context, however, we still need to restore the required execution context - - - - - A sparsely populated array. Elements can be sparse and some null, but this allows for - lock-free additions and growth, and also for constant time removal (by nulling out). - - The kind of elements contained within. - - - - Allocates a new array with the given initial size. - - How many array slots to pre-allocate. - - - - Adds an element in the first available slot, beginning the search from the tail-to-head. - If no slots are available, the array is grown. The method doesn't return until successful. - - The element to add. - Information about where the add happened, to enable O(1) deregistration. - - - - The tail of the doubly linked list. - - - - - A struct to hold a link to the exact spot in an array an element was inserted, enabling - constant time removal later on. - - - - - A fragment of a sparsely populated array, doubly linked. - - The kind of elements contained within. - - - - Provides lazy initialization routines. - - - These routines avoid needing to allocate a dedicated, lazy-initialization instance, instead using - references to ensure targets have been initialized as they are accessed. - - - - - Initializes a target reference type with the type's default constructor if the target has not - already been initialized. - - The refence type of the reference to be initialized. - A reference of type to initialize if it has not - already been initialized. - The initialized reference of type . - Type does not have a default - constructor. - - Permissions to access the constructor of type were missing. - - - - This method may only be used on reference types. To ensure initialization of value - types, see other overloads of EnsureInitialized. - - - This method may be used concurrently by multiple threads to initialize . - In the event that multiple threads access this method concurrently, multiple instances of - may be created, but only one will be stored into . In such an occurrence, this method will not dispose of the - objects that were not stored. If such objects must be disposed, it is up to the caller to determine - if an object was not used and to then dispose of the object appropriately. - - - - - - Initializes a target reference type using the specified function if it has not already been - initialized. - - The reference type of the reference to be initialized. - The reference of type to initialize if it has not - already been initialized. - The invoked to initialize the - reference. - The initialized reference of type . - Type does not have a - default constructor. - returned - null. - - - This method may only be used on reference types, and may - not return a null reference (Nothing in Visual Basic). To ensure initialization of value types or - to allow null reference types, see other overloads of EnsureInitialized. - - - This method may be used concurrently by multiple threads to initialize . - In the event that multiple threads access this method concurrently, multiple instances of - may be created, but only one will be stored into . In such an occurrence, this method will not dispose of the - objects that were not stored. If such objects must be disposed, it is up to the caller to determine - if an object was not used and to then dispose of the object appropriately. - - - - - - Initialize the target using the given delegate (slow path). - - The reference type of the reference to be initialized. - The variable that need to be initialized - The delegate that will be executed to initialize the target - The initialized variable - - - - Initializes a target reference or value type with its default constructor if it has not already - been initialized. - - The type of the reference to be initialized. - A reference or value of type to initialize if it - has not already been initialized. - A reference to a boolean that determines whether the target has already - been initialized. - A reference to an object used as the mutually exclusive lock for initializing - . - The initialized value of type . - - - - Initializes a target reference or value type with a specified function if it has not already been - initialized. - - The type of the reference to be initialized. - A reference or value of type to initialize if it - has not already been initialized. - A reference to a boolean that determines whether the target has already - been initialized. - A reference to an object used as the mutually exclusive lock for initializing - . - The invoked to initialize the - reference or value. - The initialized value of type . - - - - Ensure the target is initialized and return the value (slow path). This overload permits nulls - and also works for value type targets. Uses the supplied function to create the value. - - The type of target. - A reference to the target to be initialized. - A reference to a location tracking whether the target has been initialized. - A reference to a location containing a mutual exclusive lock. - - The to invoke in order to produce the lazily-initialized value. - - The initialized object. - - - - Provides a slimmed down version of . - - - All public and protected members of are thread-safe and may be used - concurrently from multiple threads, with the exception of Dispose, which - must only be used when all other operations on the have - completed, and Reset, which should only be used when no other threads are - accessing the event. - - - - - Initializes a new instance of the - class with an initial state of nonsignaled. - - - - - Initializes a new instance of the - class with a Boolen value indicating whether to set the intial state to signaled. - - true to set the initial state signaled; false to set the initial state - to nonsignaled. - - - - Initializes a new instance of the - class with a Boolen value indicating whether to set the intial state to signaled and a specified - spin count. - - true to set the initial state to signaled; false to set the initial state - to nonsignaled. - The number of spin waits that will occur before falling back to a true - wait. - is less than - 0 or greater than the maximum allowed value. - - - - Initializes the internal state of the event. - - Whether the event is set initially or not. - The spin count that decides when the event will block. - - - - Helper to ensure the lock object is created before first use. - - - - - This method lazily initializes the event object. It uses CAS to guarantee that - many threads racing to call this at once don't result in more than one event - being stored and used. The event will be signaled or unsignaled depending on - the state of the thin-event itself, with synchronization taken into account. - - True if a new event was created and stored, false otherwise. - - - - Sets the state of the event to signaled, which allows one or more threads waiting on the event to - proceed. - - - - - Private helper to actually perform the Set. - - Indicates whether we are calling Set() during cancellation. - The object has been canceled. - - - - Sets the state of the event to nonsignaled, which causes threads to block. - - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - Blocks the current thread until the current is set. - - - The maximum number of waiters has been exceeded. - - - The caller of this method blocks indefinitely until the current instance is set. The caller will - return immediately if the event is currently in a set state. - - - - - Blocks the current thread until the current receives a signal, - while observing a . - - The to - observe. - - The maximum number of waiters has been exceeded. - - was - canceled. - - The caller of this method blocks indefinitely until the current instance is set. The caller will - return immediately if the event is currently in a set state. - - - - - Blocks the current thread until the current is set, using a - to measure the time interval. - - A that represents the number of milliseconds - to wait, or a that represents -1 milliseconds to wait indefinitely. - - true if the was set; otherwise, - false. - is a negative - number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater - than . - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - to measure the time interval, while observing a . - - A that represents the number of milliseconds - to wait, or a that represents -1 milliseconds to wait indefinitely. - - The to - observe. - true if the was set; otherwise, - false. - is a negative - number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater - than . - was canceled. - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - 32-bit signed integer to measure the time interval. - - The number of milliseconds to wait, or (-1) to wait indefinitely. - true if the was set; otherwise, - false. - is a - negative number other than -1, which represents an infinite time-out. - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - 32-bit signed integer to measure the time interval, while observing a . - - The number of milliseconds to wait, or (-1) to wait indefinitely. - The to - observe. - true if the was set; otherwise, - false. - is a - negative number other than -1, which represents an infinite time-out. - - The maximum number of waiters has been exceeded. - - was canceled. - - - - Releases all resources used by the current instance of . - - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - When overridden in a derived class, releases the unmanaged resources used by the - , and optionally releases the managed resources. - - true to release both managed and unmanaged resources; - false to release only unmanaged resources. - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - Throw ObjectDisposedException if the MRES is disposed - - - - - Private helper method to wake up waiters when a cancellationToken gets canceled. - - - - - Private helper method for updating parts of a bit-string state value. - Mainly called from the IsSet and Waiters properties setters - - - Note: the parameter types must be int as CompareExchange cannot take a Uint - - The new value - The mask used to set the bits - - - - Private helper method - performs Mask and shift, particular helpful to extract a field from a packed word. - eg ExtractStatePortionAndShiftRight(0x12345678, 0xFF000000, 24) => 0x12, ie extracting the top 8-bits as a simple integer - - ?? is there a common place to put this rather than being private to MRES? - - - - - - - - - Performs a Mask operation, but does not perform the shift. - This is acceptable for boolean values for which the shift is unnecessary - eg (val & Mask) != 0 is an appropriate way to extract a boolean rather than using - ((val & Mask) >> shiftAmount) == 1 - - ?? is there a common place to put this rather than being private to MRES? - - - - - - - Helper function to measure and update the wait time - - The first time (in Ticks) observed when the wait started. - The orginal wait timeoutout in milliseconds. - The new wait time in milliseconds, -1 if the time expired, -2 if overflow in counters - has occurred. - - - - Gets the underlying object for this . - - The underlying event object fore this . - - Accessing this property forces initialization of an underlying event object if one hasn't - already been created. To simply wait on this , - the public Wait methods should be preferred. - - - - - Gets whether the event is set. - - true if the event has is set; otherwise, false. - - - - Gets the number of spin waits that will be occur before falling back to a true wait. - - - - - How many threads are waiting. - - - - - Provides support for spin-based waiting. - - - - encapsulates common spinning logic. On single-processor machines, yields are - always used instead of busy waits, and on computers with Intel™ processors employing Hyper-Threading™ - technology, it helps to prevent hardware thread starvation. SpinWait encapsulates a good mixture of - spinning and true yielding. - - - is a value type, which means that low-level code can utilize SpinWait without - fear of unnecessary allocation overheads. SpinWait is not generally useful for ordinary applications. - In most cases, you should use the synchronization classes provided by the .NET Framework, such as - . For most purposes where spin waiting is required, however, - the type should be preferred over the System.Threading.Thread.SpinWait method. - - - While SpinWait is designed to be used in concurrent applications, it is not designed to be - used from multiple threads concurrently. SpinWait's members are not thread-safe. If multiple - threads must spin, each should use its own instance of SpinWait. - - - - - - Performs a single spin. - - - This is typically called in a loop, and may change in behavior based on the number of times a - has been called thus far on this instance. - - - - - Resets the spin counter. - - - This makes and behave as though no calls - to had been issued on this instance. If a instance - is reused many times, it may be useful to reset it to avoid yielding too soon. - - - - - Spins until the specified condition is satisfied. - - A delegate to be executed over and over until it returns true. - The argument is null. - - - - Spins until the specified condition is satisfied or until the specified timeout is expired. - - A delegate to be executed over and over until it returns true. - - A that represents the number of milliseconds to wait, - or a TimeSpan that represents -1 milliseconds to wait indefinitely. - True if the condition is satisfied within the timeout; otherwise, false - The argument is null. - is a negative number - other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than - . - - - - Spins until the specified condition is satisfied or until the specified timeout is expired. - - A delegate to be executed over and over until it returns true. - The number of milliseconds to wait, or (-1) to wait indefinitely. - True if the condition is satisfied within the timeout; otherwise, false - The argument is null. - is a - negative number other than -1, which represents an infinite time-out. - - - - Gets the number of times has been called on this instance. - - - - - Gets whether the next call to will yield the processor, triggering a - forced context switch. - - Whether the next call to will yield the processor, triggering a - forced context switch. - - On a single-CPU machine, always yields the processor. On machines with - multiple CPUs, may yield after an unspecified number of calls. - - - - - A helper class to get the number of preocessors, it updates the numbers of processors every sampling interval - - - - - Gets the number of available processors - - - - - Gets whether the current machine has only a single processor. - - - - - Represents an asynchronous operation that produces a result at some time in the future. - - - The type of the result produced by this . - - - - instances may be created in a variety of ways. The most common approach is by - using the task's property to retrieve a instance that can be used to create tasks for several - purposes. For example, to create a that runs a function, the factory's StartNew - method may be used: - - // C# - var t = Task<int>.Factory.StartNew(() => GenerateResult()); - - or - - var t = Task.Factory.StartNew(() => GenerateResult()); - - ' Visual Basic - Dim t = Task<int>.Factory.StartNew(Function() GenerateResult()) - - or - - Dim t = Task.Factory.StartNew(Function() GenerateResult()) - - - - The class also provides constructors that initialize the task but that do not - schedule it for execution. For performance reasons, the StartNew method should be the - preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation - and scheduling must be separated, the constructors may be used, and the task's - Start - method may then be used to schedule the task for execution at a later time. - - - All members of , except for - Dispose, are thread-safe - and may be used from multiple threads concurrently. - - - - - - Represents an asynchronous operation. - - - - instances may be created in a variety of ways. The most common approach is by - using the Task type's property to retrieve a instance that can be used to create tasks for several - purposes. For example, to create a that runs an action, the factory's StartNew - method may be used: - - // C# - var t = Task.Factory.StartNew(() => DoAction()); - - ' Visual Basic - Dim t = Task.Factory.StartNew(Function() DoAction()) - - - - The class also provides constructors that initialize the Task but that do not - schedule it for execution. For performance reasons, TaskFactory's StartNew method should be the - preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation - and scheduling must be separated, the constructors may be used, and the task's - method may then be used to schedule the task for execution at a later time. - - - All members of , except for , are thread-safe - and may be used from multiple threads concurrently. - - - For operations that return values, the class - should be used. - - - For developers implementing custom debuggers, several internal and private members of Task may be - useful (these may change from release to release). The Int32 m_taskId field serves as the backing - store for the property, however accessing this field directly from a debugger may be - more efficient than accessing the same value through the property's getter method (the - s_taskIdCounter Int32 counter is used to retrieve the next available ID for a Task). Similarly, the - Int32 m_stateFlags field stores information about the current lifecycle stage of the Task, - information also accessible through the property. The m_action System.Object - field stores a reference to the Task's delegate, and the m_stateObject System.Object field stores the - async state passed to the Task by the developer. Finally, for debuggers that parse stack frames, the - InternalWait method serves a potential marker for when a Task is entering a wait operation. - - - - - - A type initializer that runs with the appropriate permissions. - - - - - Initializes a new with the specified action. - - The delegate that represents the code to execute in the Task. - The argument is null. - - - - Initializes a new with the specified action and CancellationToken. - - The delegate that represents the code to execute in the Task. - The CancellationToken - that will be assigned to the new Task. - The argument is null. - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action and creation options. - - The delegate that represents the code to execute in the task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action and creation options. - - The delegate that represents the code to execute in the task. - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action and state. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - - The argument is null. - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - The that will be assigned to the new task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - An internal constructor used by the factory methods on task and its descendent(s). - This variant does not capture the ExecutionContext; it is up to the caller to do that. - - An action to execute. - Optional state to pass to the action. - Parent of Task. - A CancellationToken for the task. - A task scheduler under which the task will run. - Options to control its execution. - Internal options to control its execution - - - - Common logic used by the following internal ctors: - Task() - Task(object action, object state, Task parent, TaskCreationOptions options, TaskScheduler taskScheduler) - - ASSUMES THAT m_creatingTask IS ALREADY SET. - - - Action for task to execute. - Object to which to pass to action (may be null) - Task scheduler on which to run thread (only used by continuation tasks). - A CancellationToken for the Task. - Options to customize behavior of Task. - Internal options to customize behavior of Task. - - - - Checks if we registered a CT callback during construction, and deregisters it. - This should be called when we know the registration isn't useful anymore. Specifically from Finish() if the task has completed - successfully or with an exception. - - - - - Captures the ExecutionContext so long as flow isn't suppressed. - - A stack crawl mark pointing to the frame of the caller. - - - - Internal function that will be called by a new child task to add itself to - the children list of the parent (this). - - Since a child task can only be created from the thread executing the action delegate - of this task, reentrancy is neither required nor supported. This should not be called from - anywhere other than the task construction/initialization codepaths. - - - - - Starts the , scheduling it for execution to the current TaskScheduler. - - - A task may only be started and run only once. Any attempts to schedule a task a second time - will result in an exception. - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Starts the , scheduling it for execution to the specified TaskScheduler. - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - The TaskScheduler with which to associate - and execute this task. - - - The argument is null. - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Runs the synchronously on the current TaskScheduler. - - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - Tasks executed with will be associated with the current TaskScheduler. - - - If the target scheduler does not support running this Task on the current thread, the Task will - be scheduled for execution on the scheduler, and the current thread will block until the - Task has completed execution. - - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Runs the synchronously on the scheduler provided. - - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - If the target scheduler does not support running this Task on the current thread, the Task will - be scheduled for execution on the scheduler, and the current thread will block until the - Task has completed execution. - - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - The parameter - is null. - The scheduler on which to attempt to run this task inline. - - - - Throws an exception if the task has been disposed, and hence can no longer be accessed. - - The task has been disposed. - - - - Sets the internal completion event. - - - - - Disposes the , releasing all of its unmanaged resources. - - - Unlike most of the members of , this method is not thread-safe. - Also, may only be called on a that is in one of - the final states: RanToCompletion, - Faulted, or - Canceled. - - - The exception that is thrown if the is not in - one of the final states: RanToCompletion, - Faulted, or - Canceled. - - - - - Disposes the , releasing all of its unmanaged resources. - - - A Boolean value that indicates whether this method is being called due to a call to . - - - Unlike most of the members of , this method is not thread-safe. - - - - - Schedules the task for execution. - - If true, TASK_STATE_STARTED bit is turned on in - an atomic fashion, making sure that TASK_STATE_CANCELED does not get set - underneath us. If false, TASK_STATE_STARTED bit is OR-ed right in. This - allows us to streamline things a bit for StartNew(), where competing cancellations - are not a problem. - - - - Adds an exception to the list of exceptions this task has thrown. - - An object representing either an Exception or a collection of Exceptions. - - - - Returns a list of exceptions by aggregating the holder's contents. Or null if - no exceptions have been thrown. - - Whether to include a TCE if cancelled. - An aggregate exception, or null if no exceptions have been caught. - - - - Throws an aggregate exception if the task contains exceptions. - - - - - Checks whether this is an attached task, and whether we are being called by the parent task. - And sets the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag based on that. - - This is meant to be used internally when throwing an exception, and when WaitAll is gathering - exceptions for tasks it waited on. If this flag gets set, the implicit wait on children - will skip exceptions to prevent duplication. - - This should only be called when this task has completed with an exception - - - - - - Signals completion of this particular task. - - The bUserDelegateExecuted parameter indicates whether this Finish() call comes following the - full execution of the user delegate. - - If bUserDelegateExecuted is false, it mean user delegate wasn't invoked at all (either due to - a cancellation request, or because this task is a promise style Task). In this case, the steps - involving child tasks (i.e. WaitForChildren) will be skipped. - - - - - - FinishStageTwo is to be executed as soon as we known there are no more children to complete. - It can happen i) either on the thread that originally executed this task (if no children were spawned, or they all completed by the time this task's delegate quit) - ii) or on the thread that executed the last child. - - - - - Final stage of the task completion code path. Notifies the parent (if any) that another of its childre are done, and runs continuations. - This function is only separated out from FinishStageTwo because these two operations are also needed to be called from CancellationCleanupLogic() - - - - - This is called by children of this task when they are completed. - - - - - This is to be called just before the task does its final state transition. - It traverses the list of exceptional children, and appends their aggregate exceptions into this one's exception list - - - - - Special purpose Finish() entry point to be used when the task delegate throws a ThreadAbortedException - This makes a note in the state flags so that we avoid any costly synchronous operations in the finish codepath - such as inlined continuations - - - Indicates whether the ThreadAbortException was added to this task's exception holder. - This should always be true except for the case of non-root self replicating task copies. - - Whether the delegate was executed. - - - - Executes the task. This method will only be called once, and handles bookeeping associated with - self-replicating tasks, in addition to performing necessary exception marshaling. - - The task has already been disposed. - - - - IThreadPoolWorkItem override, which is the entry function for this task when the TP scheduler decides to run it. - - - - - - Outermost entry function to execute this task. Handles all aspects of executing a task on the caller thread. - Currently this is called by IThreadPoolWorkItem.ExecuteWorkItem(), and TaskManager.TryExecuteInline. - - - Performs atomic updates to prevent double execution. Should only be set to true - in codepaths servicing user provided TaskSchedulers. The ConcRT or ThreadPool schedulers don't need this. - - - - The actual code which invokes the body of the task. This can be overriden in derived types. - - - - - Alternate InnerInvoke prototype to be called from ExecuteSelfReplicating() so that - the Parallel Debugger can discover the actual task being invoked. - Details: Here, InnerInvoke is actually being called on the rootTask object while we are actually executing the - childTask. And the debugger needs to discover the childTask, so we pass that down as an argument. - The NoOptimization and NoInlining flags ensure that the childTask pointer is retained, and that this - function appears on the callstack. - - - - - - Performs whatever handling is necessary for an unhandled exception. Normally - this just entails adding the exception to the holder object. - - The exception that went unhandled. - - - - Waits for the to complete execution. - - - The was canceled -or- an exception was thrown during - the execution of the . - - - The has been disposed. - - - - - Waits for the to complete execution. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - true if the completed execution within the allotted time; otherwise, false. - - - The was canceled -or- an exception was thrown during the execution of the . - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - The has been disposed. - - - - - Waits for the to complete execution. - - - A to observe while waiting for the task to complete. - - - The was canceled. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - - - Waits for the to complete execution. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - true if the completed execution within the allotted time; otherwise, - false. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - - - Waits for the to complete execution. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for the task to complete. - - - true if the completed execution within the allotted time; otherwise, false. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - The core wait function, which is only accesible internally. It's meant to be used in places in TPL code where - the current context is known or cached. - - - - - Cancels the . - - Indiactes whether we should only cancel non-invoked tasks. - For the default scheduler this option will only be serviced through TryDequeue. - For custom schedulers we also attempt an atomic state transition. - true if the task was successfully canceled; otherwise, false. - The - has been disposed. - - - - Sets the task's cancellation acknowledged flag. - - - - - Runs all of the continuations, as appropriate. - - - - - Helper function to determine whether the current task is in the state desired by the - continuation kind under evaluation. Three possibilities exist: the task failed with - an unhandled exception (OnFailed), the task was canceled before running (OnAborted), - or the task completed successfully (OnCompletedSuccessfully). Note that the last - one includes completing due to cancellation. - - The continuation options under evaluation. - True if the continuation should be run given the task's current state. - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - The that will be assigned to the new continuation task. - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Converts TaskContinuationOptions to TaskCreationOptions, and also does - some validity checking along the way. - - Incoming TaskContinuationOptions - Outgoing TaskCreationOptions - Outgoing InternalTaskOptions - - - - Registers the continuation and possibly runs it (if the task is already finished). - - The continuation task itself. - TaskScheduler with which to associate continuation task. - Restrictions on when the continuation becomes active. - - - - Waits for all of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - An array of instances on which to wait. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - A to observe while waiting for the tasks to complete. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The was canceled. - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for the tasks to complete. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - Waits for a set of handles in a STA-aware way. In other words, it will wait for each - of the events individually if we're on a STA thread, because MsgWaitForMultipleObjectsEx - can't do a true wait-all due to its hidden message queue event. This is not atomic, - of course, but we only wait on one-way (MRE) events anyway so this is OK. - - An array of wait handles to wait on. - The timeout to use during waits. - The cancellationToken that enables a wait to be canceled. - True if all waits succeeded, false if a timeout occurred. - - - - Internal WaitAll implementation which is meant to be used with small number of tasks, - optimized for Parallel.Invoke and other structured primitives. - - - - - This internal function is only meant to be called by WaitAll() - If the completed task is canceled or it has other exceptions, here we will add those - into the passed in exception list (which will be lazily initialized here). - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - The index of the completed task in the array argument. - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - A to observe while waiting for a task to complete. - - - The index of the completed task in the array argument. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - The was canceled. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for a task to complete. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - Gets a unique ID for this Task instance. - - - Task IDs are assigned on-demand and do not necessarily represent the order in the which Task - instances were created. - - - - - Returns the unique ID of the currently executing Task. - - - - - Gets the Task instance currently executing, or - null if none exists. - - - - - Gets the Exception that caused the Task to end prematurely. If the Task completed successfully or has not yet thrown any - exceptions, this will return null. - - - Tasks that throw unhandled exceptions store the resulting exception and propagate it wrapped in a - in calls to Wait - or in accesses to the property. Any exceptions not observed by the time - the Task instance is garbage collected will be propagated on the finalizer thread. - - - The Task - has been disposed. - - - - - Gets the TaskStatus of this Task. - - - - - Gets whether this Task instance has completed - execution due to being canceled. - - - A Task will complete in Canceled state either if its CancellationToken - was marked for cancellation before the task started executing, or if the task acknowledged the cancellation request on - its already signaled CancellationToken by throwing an - OperationCanceledException2 that bears the same - CancellationToken. - - - - - Returns true if this task has a cancellation token and it was signaled. - To be used internally in execute entry codepaths. - - - - - This internal property provides access to the CancellationToken that was set on the task - when it was constructed. - - - - - Gets whether this threw an OperationCanceledException2 while its CancellationToken was signaled. - - - - - Gets whether this Task has completed. - - - will return true when the Task is in one of the three - final states: RanToCompletion, - Faulted, or - Canceled. - - - - - Checks whether this task has been disposed. - - - - - Gets the TaskCreationOptions used - to create this task. - - - - - Gets a that can be used to wait for the task to - complete. - - - Using the wait functionality provided by - should be preferred over using for similar - functionality. - - - The has been disposed. - - - - - Gets the state object supplied when the Task was created, - or null if none was supplied. - - - - - Gets an indication of whether the asynchronous operation completed synchronously. - - true if the asynchronous operation completed synchronously; otherwise, false. - - - - Provides access to the TaskScheduler responsible for executing this Task. - - - - - Provides access to factory methods for creating and instances. - - - The factory returned from is a default instance - of , as would result from using - the default constructor on TaskFactory. - - - - - Provides an event that can be used to wait for completion. - Only called by Wait*(), which means that we really do need to instantiate a completion event. - - - - - Determines whether this is the root task of a self replicating group. - - - - - Determines whether the task is a replica itself. - - - - - The property formerly known as IsFaulted. - - - - - Gets whether the completed due to an unhandled exception. - - - If is true, the Task's will be equal to - TaskStatus.Faulted, and its - property will be non-null. - - - - - Checks whether the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag is set, - This will only be used by the implicit wait to prevent double throws - - - - - - Checks whether the body was ever invoked. Used by task scheduler code to verify custom schedulers actually ran the task. - - - - - A structure to hold continuation information. - - - - - Constructs a new continuation structure. - - The task to be activated. - The continuation options. - The scheduler to use for the continuation. - - - - Invokes the continuation for the target completion task. - - The completed task. - Whether the continuation can be inlined. - - - - Initializes a new with the specified function. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - - The argument is null. - - - - - Initializes a new with the specified function. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - The to be assigned to this task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified function and creation options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified function and creation options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified function and state. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the action. - - The argument is null. - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - The to be assigned to the new task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - The to be assigned to the new task. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Creates a new future object. - - The parent task for this future. - A function that yields the future value. - The task scheduler which will be used to execute the future. - The CancellationToken for the task. - Options to control the future's behavior. - Internal options to control the future's behavior. - The argument specifies - a SelfReplicating , which is illegal."/>. - - - - Creates a new future object. - - The parent task for this future. - An object containing data to be used by the action; may be null. - A function that yields the future value. - The CancellationToken for the task. - The task scheduler which will be used to execute the future. - Options to control the future's behavior. - Internal options to control the future's behavior. - The argument specifies - a SelfReplicating , which is illegal."/>. - - - - Evaluates the value selector of the Task which is passed in as an object and stores the result. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new task. - A new continuation . - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The , when executed, should return a . This task's completion state will be transferred to the task returned - from the ContinueWith call. - - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be passed as - an argument this completed task. - - The that will be assigned to the new task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The , when executed, should return a . - This task's completion state will be transferred to the task returned from the - ContinueWith call. - - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Gets the result value of this . - - - The get accessor for this property ensures that the asynchronous operation is complete before - returning. Once the result of the computation is available, it is stored and will be returned - immediately on later calls to . - - - - - Provides access to factory methods for creating instances. - - - The factory returned from is a default instance - of , as would result from using - the default constructor on the factory type. - - - - - Provides support for creating and scheduling - Task{TResult} objects. - - The type of the results that are available though - the Task{TResult} objects that are associated with - the methods in this class. - - - There are many common patterns for which tasks are relevant. The - class encodes some of these patterns into methods that pick up default settings, which are - configurable through its constructors. - - - A default instance of is available through the - Task{TResult}.Factory property. - - - - - - Initializes a instance with the default configuration. - - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the default configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The - TaskScheduler to use to schedule any tasks created with this TaskFactory{TResult}. A null value - indicates that the current TaskScheduler should be used. - - - With this constructor, the - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to , unless it's null, in which case the property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory{TResult}. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory{TResult}. - - - The exception that is thrown when the - argument or the - argument specifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory{TResult}. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory{TResult}. - - - The default - TaskScheduler to use to schedule any Tasks created with this TaskFactory{TResult}. A null value - indicates that TaskScheduler.Current should be used. - - - The exception that is thrown when the - argument or the - argumentspecifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to - , unless it's null, in which case the property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new task. - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The that will be assigned to the new task. - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Gets the default CancellationToken of this - TaskFactory. - - - This property returns the default that will be assigned to all - tasks created by this factory unless another CancellationToken value is explicitly specified - during the call to the factory methods. - - - - - Gets the TaskScheduler of this - TaskFactory{TResult}. - - - This property returns the default scheduler for this factory. It will be used to schedule all - tasks unless another scheduler is explicitly specified during calls to this factory's methods. - If null, TaskScheduler.Current - will be used. - - - - - Gets the TaskCreationOptions - value of this TaskFactory{TResult}. - - - This property returns the default creation options for this factory. They will be used to create all - tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Gets the TaskContinuationOptions - value of this TaskFactory{TResult}. - - - This property returns the default continuation options for this factory. They will be used to create - all continuation tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Represents the current stage in the lifecycle of a . - - - - - The task has been initialized but has not yet been scheduled. - - - - - The task is waiting to be activated and scheduled internally by the .NET Framework infrastructure. - - - - - The task has been scheduled for execution but has not yet begun executing. - - - - - The task is running but has not yet completed. - - - - - The task has finished executing and is implicitly waiting for - attached child tasks to complete. - - - - - The task completed execution successfully. - - - - - The task acknowledged cancellation by throwing an OperationCanceledException2 with its own CancellationToken - while the token was in signaled state, or the task's CancellationToken was already signaled before the - task started executing. - - - - - The task completed due to an unhandled exception. - - - - - Specifies flags that control optional behavior for the creation and execution of tasks. - - - - - Specifies that the default behavior should be used. - - - - - A hint to a TaskScheduler to schedule a - task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to - be run sooner, and tasks scheduled later will be more likely to be run later. - - - - - Specifies that a task will be a long-running, course-grained operation. It provides a hint to the - TaskScheduler that oversubscription may be - warranted. - - - - - Specifies that a task is attached to a parent in the task hierarchy. - - - - - Task creation flags which are only used internally. - - - - Specifies "No internal task options" - - - Used to filter out internal vs. public task creation options. - - - Specifies that the task will be queued by the runtime before handing it over to the user. - This flag will be used to skip the cancellationtoken registration step, which is only meant for unstarted tasks. - - - - Specifies flags that control optional behavior for the creation and execution of continuation tasks. - - - - - Default = "Continue on any, no task options, run asynchronously" - Specifies that the default behavior should be used. Continuations, by default, will - be scheduled when the antecedent task completes, regardless of the task's final TaskStatus. - - - - - A hint to a TaskScheduler to schedule a - task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to - be run sooner, and tasks scheduled later will be more likely to be run later. - - - - - Specifies that a task will be a long-running, course-grained operation. It provides - a hint to the TaskScheduler that - oversubscription may be warranted. - - - - - Specifies that a task is attached to a parent in the task hierarchy. - - - - - Specifies that the continuation task should not be scheduled if its antecedent ran to completion. - This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should not be scheduled if its antecedent threw an unhandled - exception. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should not be scheduled if its antecedent was canceled. This - option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent ran to - completion. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent threw an - unhandled exception. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent was canceled. - This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be executed synchronously. With this option - specified, the continuation will be run on the same thread that causes the antecedent task to - transition into its final state. If the antecedent is already complete when the continuation is - created, the continuation will run on the thread creating the continuation. Only very - short-running continuations should be executed synchronously. - - - - - Represents an exception used to communicate task cancellation. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the - class with a specified error message and a reference to the inner exception that is the cause of - this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - - Initializes a new instance of the class - with a reference to the that has been canceled. - - A task that has been canceled. - - - - Gets the task associated with this exception. - - - It is permissible for no Task to be associated with a - , in which case - this property will return null. - - - - - Represents the producer side of a unbound to a - delegate, providing access to the consumer side through the property. - - - - It is often the case that a is desired to - represent another asynchronous operation. - TaskCompletionSource is provided for this purpose. It enables - the creation of a task that can be handed out to consumers, and those consumers can use the members - of the task as they would any other. However, unlike most tasks, the state of a task created by a - TaskCompletionSource is controlled explicitly by the methods on TaskCompletionSource. This enables the - completion of the external asynchronous operation to be propagated to the underlying Task. The - separation also ensures that consumers are not able to transition the state without access to the - corresponding TaskCompletionSource. - - - All members of are thread-safe - and may be used from multiple threads concurrently. - - - The type of the result value assocatied with this . - - - - Creates a . - - - - - Creates a - with the specified options. - - - The created - by this instance and accessible through its property - will be instantiated using the specified . - - The options to use when creating the underlying - . - - The represent options invalid for use - with a . - - - - - Creates a - with the specified state. - - The state to use as the underlying - 's AsyncState. - - - - Creates a with - the specified state and options. - - The options to use when creating the underlying - . - The state to use as the underlying - 's AsyncState. - - The represent options invalid for use - with a . - - - - - Attempts to transition the underlying - into the - Faulted - state. - - The exception to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The argument is null. - The was disposed. - - - - Attempts to transition the underlying - into the - Faulted - state. - - The collection of exceptions to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The argument is null. - There are one or more null elements in . - The collection is empty. - The was disposed. - - - - Transitions the underlying - into the - Faulted - state. - - The exception to bind to this . - The argument is null. - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - Faulted - state. - - The collection of exceptions to bind to this . - The argument is null. - There are one or more null elements in . - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Attempts to transition the underlying - into the - RanToCompletion - state. - - The result value to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - RanToCompletion - state. - - The result value to bind to this . - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - Canceled - state. - - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Attempts to transition the underlying - into the - Canceled - state. - - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Gets the created - by this . - - - This property enables a consumer access to the that is controlled by this instance. - The , , - , and - methods (and their "Try" variants) on this instance all result in the relevant state - transitions on this underlying Task. - - - - - An exception holder manages a list of exceptions for one particular task. - It offers the ability to aggregate, but more importantly, also offers intrinsic - support for propagating unhandled exceptions that are never observed. It does - this by aggregating and throwing if the holder is ever GC'd without the holder's - contents ever having been requested (e.g. by a Task.Wait, Task.get_Exception, etc). - - - - - Creates a new holder; it will be registered for finalization. - - The task this holder belongs to. - - - - A finalizer that repropagates unhandled exceptions. - - - - - Add an exception to the internal list. This will ensure the holder is - in the proper state (handled/unhandled) depending on the list's contents. - - An exception object (either an Exception or an - IEnumerable{Exception}) to add to the list. - - - - A private helper method that ensures the holder is considered - unhandled, i.e. it is registered for finalization. - - - - - A private helper method that ensures the holder is considered - handled, i.e. it is not registered for finalization. - - Whether this is called from the finalizer thread. - - - - Allocates a new aggregate exception and adds the contents of the list to - it. By calling this method, the holder assumes exceptions to have been - "observed", such that the finalization check will be subsequently skipped. - - Whether this is being called from a finalizer. - An extra exception to be included (optionally). - The aggregate exception to throw. - - - - Provides a set of static (Shared in Visual Basic) methods for working with specific kinds of - instances. - - - - - Creates a proxy Task that represents the - asynchronous operation of a Task{Task}. - - - It is often useful to be able to return a Task from a - Task{TResult}, where the inner Task represents work done as part of the outer Task{TResult}. However, - doing so results in a Task{Task}, which, if not dealt with carefully, could produce unexpected behavior. Unwrap - solves this problem by creating a proxy Task that represents the entire asynchronous operation of such a Task{Task}. - - The Task{Task} to unwrap. - The exception that is thrown if the - argument is null. - A Task that represents the asynchronous operation of the provided Task{Task}. - - - - Creates a proxy Task{TResult} that represents the - asynchronous operation of a Task{Task{TResult}}. - - - It is often useful to be able to return a Task{TResult} from a Task{TResult}, where the inner Task{TResult} - represents work done as part of the outer Task{TResult}. However, doing so results in a Task{Task{TResult}}, - which, if not dealt with carefully, could produce unexpected behavior. Unwrap solves this problem by - creating a proxy Task{TResult} that represents the entire asynchronous operation of such a Task{Task{TResult}}. - - The Task{Task{TResult}} to unwrap. - The exception that is thrown if the - argument is null. - A Task{TResult} that represents the asynchronous operation of the provided Task{Task{TResult}}. /// Unwraps a Task that returns another Task. - - - - Provides support for creating and scheduling - Tasks. - - - - There are many common patterns for which tasks are relevant. The - class encodes some of these patterns into methods that pick up default settings, which are - configurable through its constructors. - - - A default instance of is available through the - Task.Factory property. - - - - - - Initializes a instance with the default configuration. - - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The - TaskScheduler to use to schedule any tasks created with this TaskFactory. A null value - indicates that the current TaskScheduler should be used. - - - With this constructor, the - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to , unless it's null, in which case the property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory. - - - The exception that is thrown when the - argument or the - argument specifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory. - - - The default - TaskScheduler to use to schedule any Tasks created with this TaskFactory. A null value - indicates that TaskScheduler.Current should be used. - - - The exception that is thrown when the - argument or the - argumentspecifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to - , unless it's null, in which case the property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The started Task. - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors - and then calling - Start to schedule it for execution. However, - unless creation and scheduling must be separated, StartNew is the recommended - approach for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The that will be assigned to the new task. - The started Task. - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors - and then calling - Start to schedule it for execution. However, - unless creation and scheduling must be separated, StartNew is the recommended - approach for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The that will be assigned to the new - A TaskCreationOptions value that controls the behavior of the - created - Task. - The TaskScheduler - that is used to schedule the created Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The started Task. - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The that will be assigned to the new - The started Task. - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The TaskScheduler - that is used to schedule the created Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the asynchronous - operation. - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the asynchronous - operation. - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the asynchronous - operation. - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Check validity of options passed to FromAsync method - - The options to be validated. - determines type of FromAsync method that called this method - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Gets the default CancellationToken of this - TaskFactory. - - - This property returns the default that will be assigned to all - tasks created by this factory unless another CancellationToken value is explicitly specified - during the call to the factory methods. - - - - - Gets the TaskScheduler of this - TaskFactory. - - - This property returns the default scheduler for this factory. It will be used to schedule all - tasks unless another scheduler is explicitly specified during calls to this factory's methods. - If null, TaskScheduler.Current - will be used. - - - - - Gets the TaskCreationOptions - value of this TaskFactory. - - - This property returns the default creation options for this factory. They will be used to create all - tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Gets the TaskContinuationOptions - value of this TaskFactory. - - - This property returns the default continuation options for this factory. They will be used to create - all continuation tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Represents an abstract scheduler for tasks. - - - - TaskScheduler acts as the extension point for all - pluggable scheduling logic. This includes mechanisms such as how to schedule a task for execution, and - how scheduled tasks should be exposed to debuggers. - - - All members of the abstract type are thread-safe - and may be used from multiple threads concurrently. - - - - - - Queues a Task to the scheduler. - - - - A class derived from TaskScheduler - implements this method to accept tasks being scheduled on the scheduler. - A typical implementation would store the task in an internal data structure, which would - be serviced by threads that would execute those tasks at some time in the future. - - - This method is only meant to be called by the .NET Framework and - should not be called directly by the derived class. This is necessary - for maintaining the consistency of the system. - - - The Task to be queued. - The argument is null. - - - - Determines whether the provided Task - can be executed synchronously in this call, and if it can, executes it. - - - - A class derived from TaskScheduler implements this function to - support inline execution of a task on a thread that initiates a wait on that task object. Inline - execution is optional, and the request may be rejected by returning false. However, better - scalability typically results the more tasks that can be inlined, and in fact a scheduler that - inlines too little may be prone to deadlocks. A proper implementation should ensure that a - request executing under the policies guaranteed by the scheduler can successfully inline. For - example, if a scheduler uses a dedicated thread to execute tasks, any inlining requests from that - thread should succeed. - - - If a scheduler decides to perform the inline execution, it should do so by calling to the base - TaskScheduler's - TryExecuteTask method with the provided task object, propagating - the return value. It may also be appropriate for the scheduler to remove an inlined task from its - internal data structures if it decides to honor the inlining request. Note, however, that under - some circumstances a scheduler may be asked to inline a task that was not previously provided to - it with the method. - - - The derived scheduler is responsible for making sure that the calling thread is suitable for - executing the given task as far as its own scheduling and execution policies are concerned. - - - The Task to be - executed. - A Boolean denoting whether or not task has previously been - queued. If this parameter is True, then the task may have been previously queued (scheduled); if - False, then the task is known not to have been queued, and this call is being made in order to - execute the task inline without queueing it. - A Boolean value indicating whether the task was executed inline. - The argument is - null. - The was already - executed. - - - - Generates an enumerable of Task instances - currently queued to the scheduler waiting to be executed. - - - - A class derived from implements this method in order to support - integration with debuggers. This method will only be invoked by the .NET Framework when the - debugger requests access to the data. The enumerable returned will be traversed by debugging - utilities to access the tasks currently queued to this scheduler, enabling the debugger to - provide a representation of this information in the user interface. - - - It is important to note that, when this method is called, all other threads in the process will - be frozen. Therefore, it's important to avoid synchronization with other threads that may lead to - blocking. If synchronization is necessary, the method should prefer to throw a - than to block, which could cause a debugger to experience delays. Additionally, this method and - the enumerable returned must not modify any globally visible state. - - - The returned enumerable should never be null. If there are currently no queued tasks, an empty - enumerable should be returned instead. - - - For developers implementing a custom debugger, this method shouldn't be called directly, but - rather this functionality should be accessed through the internal wrapper method - GetScheduledTasksForDebugger: - internal Task[] GetScheduledTasksForDebugger(). This method returns an array of tasks, - rather than an enumerable. In order to retrieve a list of active schedulers, a debugger may use - another internal method: internal static TaskScheduler[] GetTaskSchedulersForDebugger(). - This static method returns an array of all active TaskScheduler instances. - GetScheduledTasksForDebugger then may be used on each of these scheduler instances to retrieve - the list of scheduled tasks for each. - - - An enumerable that allows traversal of tasks currently queued to this scheduler. - - - This scheduler is unable to generate a list of queued tasks at this time. - - - - - Retrieves some thread static state that can be cached and passed to multiple - TryRunInline calls, avoiding superflous TLS fetches. - - A bag of TLS state (or null if none exists). - - - - Attempts to execute the target task synchronously. - - The task to run. - True if the task may have been previously queued, - false if the task was absolutely not previously queued. - The state retrieved from GetThreadStatics - True if it ran, false otherwise. - - - - Attempts to dequeue a Task that was previously queued to - this scheduler. - - The Task to be dequeued. - A Boolean denoting whether the argument was successfully dequeued. - The argument is null. - - - - Notifies the scheduler that a work item has made progress. - - - - - Initializes the . - - - - - Frees all resources associated with this scheduler. - - - - - Creates a - associated with the current . - - - All Task instances queued to - the returned scheduler will be executed through a call to the - Post method - on that context. - - - A associated with - the current SynchronizationContext, as - determined by SynchronizationContext.Current. - - - The current SynchronizationContext may not be used as a TaskScheduler. - - - - - Attempts to execute the provided Task - on this scheduler. - - - - Scheduler implementations are provided with Task - instances to be executed through either the method or the - method. When the scheduler deems it appropriate to run the - provided task, should be used to do so. TryExecuteTask handles all - aspects of executing a task, including action invocation, exception handling, state management, - and lifecycle control. - - - must only be used for tasks provided to this scheduler by the .NET - Framework infrastructure. It should not be used to execute arbitrary tasks obtained through - custom mechanisms. - - - - A Task object to be executed. - - The is not associated with this scheduler. - - A Boolean that is true if was successfully executed, false if it - was not. A common reason for execution failure is that the task had previously been executed or - is in the process of being executed by another thread. - - - - Provides an array of all queued Task instances - for the debugger. - - - The returned array is populated through a call to . - Note that this function is only meant to be invoked by a debugger remotely. - It should not be called by any other codepaths. - - An array of Task instances. - - This scheduler is unable to generate a list of queued tasks at this time. - - - - - Provides an array of all active TaskScheduler - instances for the debugger. - - - This function is only meant to be invoked by a debugger remotely. - It should not be called by any other codepaths. - - An array of TaskScheduler instances. - - - - Registers a new TaskScheduler instance in the global collection of schedulers. - - - - - Removes a TaskScheduler instance from the global collection of schedulers. - - - - - Indicates the maximum concurrency level this - is able to support. - - - - - Indicates whether this is a custom scheduler, in which case the safe code paths will be taken upon task entry - using a CAS to transition from queued state to executing. - - - - - Gets the default TaskScheduler instance. - - - - - Gets the TaskScheduler - associated with the currently executing task. - - - When not called from within a task, will return the scheduler. - - - - - Gets the unique ID for this . - - - - - Occurs when a faulted 's unobserved exception is about to trigger exception escalation - policy, which, by default, would terminate the process. - - - This AppDomain-wide event provides a mechanism to prevent exception - escalation policy (which, by default, terminates the process) from triggering. - Each handler is passed a - instance, which may be used to examine the exception and to mark it as observed. - - - - - Nested class that provides debugger view for TaskScheduler - - - - Default thread pool scheduler. - - - - A TaskScheduler implementation that executes all tasks queued to it through a call to - on the - that its associated with. The default constructor for this class binds to the current - - - - - Constructs a SynchronizationContextTaskScheduler associated with - - This constructor expects to be set. - - - - Implemetation of for this scheduler class. - - Simply posts the tasks to be executed on the associated . - - - - - - Implementation of for this scheduler class. - - The task will be executed inline only if the call happens within - the associated . - - - - - - - Implementes the property for - this scheduler class. - - By default it returns 1, because a based - scheduler only supports execution on a single thread. - - - - - Provides data for the event that is raised when a faulted 's - exception goes unobserved. - - - The Exception property is used to examine the exception without marking it - as observed, whereas the method is used to mark the exception - as observed. Marking the exception as observed prevents it from triggering exception escalation policy - which, by default, terminates the process. - - - - - Initializes a new instance of the class - with the unobserved exception. - - The Exception that has gone unobserved. - - - - Marks the as "observed," thus preventing it - from triggering exception escalation policy which, by default, terminates the process. - - - - - Gets whether this exception has been marked as "observed." - - - - - The Exception that went unobserved. - - - - - Represents an exception used to communicate an invalid operation by a - . - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the - class using the default error message and a reference to the inner exception that is the cause of - this exception. - - The exception that is the cause of the current exception. - - - - Initializes a new instance of the - class with a specified error message and a reference to the inner exception that is the cause of - this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/ensureRedirect.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8+wp8+wpa81/ensureRedirect.xml deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.IO.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.IO.dll deleted file mode 100644 index 32dd41b78..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.IO.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.IO.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.IO.xml deleted file mode 100644 index e83273427..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.IO.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - System.IO - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Found invalid data while decoding.. - - - - - The exception that is thrown when a data stream is in an invalid format. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a reference to the inner exception that is the cause of this exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. If the parameter is not null, the current exception is raised in a catch block that handles the inner exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.Runtime.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.Runtime.dll deleted file mode 100644 index 118fcce20..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.Runtime.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.Runtime.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.Runtime.xml deleted file mode 100644 index 93cb00d74..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.Runtime.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - System.Runtime - - - - Defines a provider for progress updates. - The type of progress update value. - - - Reports a progress update. - The value of the updated progress. - - - Identities the async state machine type for this method. - - - Identities the state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - Gets the type that implements the state machine. - - - Initializes the attribute. - The type that implements the state machine. - - - - Allows you to obtain the method or property name of the caller to the method. - - - - - Allows you to obtain the line number in the source file at which the method is called. - - - - - Allows you to obtain the full path of the source file that contains the caller. - This is the file path at the time of compile. - - - - Identities the iterator state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.Threading.Tasks.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.Threading.Tasks.dll deleted file mode 100644 index a089d474d..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.Threading.Tasks.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.Threading.Tasks.xml deleted file mode 100644 index 6c770122e..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/System.Threading.Tasks.xml +++ /dev/null @@ -1,8969 +0,0 @@ - - - - System.Threading.Tasks - - - - Represents one or more errors that occur during application execution. - - is used to consolidate multiple failures into a single, throwable - exception object. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with - a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a specified error - message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - The argument - is null. - - - - Initializes a new instance of the class with - references to the inner exceptions that are the cause of this exception. - - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with - references to the inner exceptions that are the cause of this exception. - - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with a specified error - message and references to the inner exceptions that are the cause of this exception. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with a specified error - message and references to the inner exceptions that are the cause of this exception. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Allocates a new aggregate exception with the specified message and list of inner exceptions. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Returns the that is the root cause of this exception. - - - - - Invokes a handler on each contained by this . - - The predicate to execute for each exception. The predicate accepts as an - argument the to be processed and returns a Boolean to indicate - whether the exception was handled. - - Each invocation of the returns true or false to indicate whether the - was handled. After all invocations, if any exceptions went - unhandled, all unhandled exceptions will be put into a new - which will be thrown. Otherwise, the method simply returns. If any - invocations of the throws an exception, it will halt the processing - of any more exceptions and immediately propagate the thrown exception as-is. - - An exception contained by this was not handled. - The argument is - null. - - - - Flattens an instances into a single, new instance. - - A new, flattened . - - If any inner exceptions are themselves instances of - , this method will recursively flatten all of them. The - inner exceptions returned in the new - will be the union of all of the the inner exceptions from exception tree rooted at the provided - instance. - - - - - Creates and returns a string representation of the current . - - A string representation of the current exception. - - - - Gets a read-only collection of the instances that caused the - current exception. - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to One or more errors occurred.. - - - - - Looks up a localized string similar to An element of innerExceptions was null.. - - - - - Looks up a localized string similar to {0}{1}---> (Inner Exception #{2}) {3}{4}{5}. - - - - - Looks up a localized string similar to No tokens were supplied.. - - - - - Looks up a localized string similar to The CancellationTokenSource associated with this CancellationToken has been disposed.. - - - - - Looks up a localized string similar to The CancellationTokenSource has been disposed.. - - - - - Looks up a localized string similar to The SyncRoot property may not be used for the synchronization of concurrent collections.. - - - - - Looks up a localized string similar to The array is multidimensional, or the type parameter for the set cannot be cast automatically to the type of the destination array.. - - - - - Looks up a localized string similar to The index is equal to or greater than the length of the array, or the number of elements in the dictionary is greater than the available space from index to the end of the destination array.. - - - - - Looks up a localized string similar to The capacity argument must be greater than or equal to zero.. - - - - - Looks up a localized string similar to The concurrencyLevel argument must be positive.. - - - - - Looks up a localized string similar to The index argument is less than zero.. - - - - - Looks up a localized string similar to TKey is a reference type and item.Key is null.. - - - - - Looks up a localized string similar to The key already existed in the dictionary.. - - - - - Looks up a localized string similar to The source argument contains duplicate keys.. - - - - - Looks up a localized string similar to The key was of an incorrect type for this dictionary.. - - - - - Looks up a localized string similar to The value was of an incorrect type for this dictionary.. - - - - - Looks up a localized string similar to The lazily-initialized type does not have a public, parameterless constructor.. - - - - - Looks up a localized string similar to ValueFactory returned null.. - - - - - Looks up a localized string similar to The spinCount argument must be in the range 0 to {0}, inclusive.. - - - - - Looks up a localized string similar to There are too many threads currently waiting on the event. A maximum of {0} waiting threads are supported.. - - - - - Looks up a localized string similar to The event has been disposed.. - - - - - Looks up a localized string similar to The operation was canceled.. - - - - - Looks up a localized string similar to The condition argument is null.. - - - - - Looks up a localized string similar to The timeout must represent a value between -1 and Int32.MaxValue, inclusive.. - - - - - Looks up a localized string similar to The specified TaskContinuationOptions combined LongRunning and ExecuteSynchronously. Synchronous continuations should not be long running.. - - - - - Looks up a localized string similar to The specified TaskContinuationOptions excluded all continuation kinds.. - - - - - Looks up a localized string similar to (Internal)An attempt was made to create a LongRunning SelfReplicating task.. - - - - - Looks up a localized string similar to The value needs to translate in milliseconds to -1 (signifying an infinite timeout), 0 or a positive integer less than or equal to Int32.MaxValue.. - - - - - Looks up a localized string similar to The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer.. - - - - - Looks up a localized string similar to A task may only be disposed if it is in a completion state (RanToCompletion, Faulted or Canceled).. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.LongRunning in calls to FromAsync.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.PreferFairness in calls to FromAsync.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.SelfReplicating in calls to FromAsync.. - - - - - Looks up a localized string similar to FromAsync was called with a TaskManager that had already shut down.. - - - - - Looks up a localized string similar to The tasks argument contains no tasks.. - - - - - Looks up a localized string similar to It is invalid to exclude specific continuation kinds for continuations off of multiple tasks.. - - - - - Looks up a localized string similar to The tasks argument included a null value.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task that was already started.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a continuation task.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task that has already completed.. - - - - - Looks up a localized string similar to Start may not be called on a task that was already started.. - - - - - Looks up a localized string similar to Start may not be called on a continuation task.. - - - - - Looks up a localized string similar to Start may not be called on a task with null action.. - - - - - Looks up a localized string similar to Start may not be called on a promise-style task.. - - - - - Looks up a localized string similar to Start may not be called on a task that has completed.. - - - - - Looks up a localized string similar to The task has been disposed.. - - - - - Looks up a localized string similar to The tasks array included at least one null element.. - - - - - Looks up a localized string similar to The awaited task has not yet completed.. - - - - - Looks up a localized string similar to A task was canceled.. - - - - - Looks up a localized string similar to The exceptions collection was empty.. - - - - - Looks up a localized string similar to The exceptions collection included at least one null element.. - - - - - Looks up a localized string similar to A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread.. - - - - - Looks up a localized string similar to (Internal)Expected an Exception or an IEnumerable<Exception>. - - - - - Looks up a localized string similar to ExecuteTask may not be called for a task which was already executed.. - - - - - Looks up a localized string similar to ExecuteTask may not be called for a task which was previously queued to a different TaskScheduler.. - - - - - Looks up a localized string similar to The current SynchronizationContext may not be used as a TaskScheduler.. - - - - - Looks up a localized string similar to The TryExecuteTaskInline call to the underlying scheduler succeeded, but the task body was not invoked.. - - - - - Looks up a localized string similar to An exception was thrown by a TaskScheduler.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.SelfReplicating for a Task<TResult>.. - - - - - Looks up a localized string similar to {Not yet computed}. - - - - - Looks up a localized string similar to A task's Exception may only be set directly if the task was created without a function.. - - - - - Looks up a localized string similar to An attempt was made to transition a task to a final state when it had already completed.. - - - - - Represents a thread-safe collection of keys and values. - - The type of the keys in the dictionary. - The type of the values in the dictionary. - - All public and protected members of are thread-safe and may be used - concurrently from multiple threads. - - - - - Initializes a new instance of the - class that is empty, has the default concurrency level, has the default initial capacity, and - uses the default comparer for the key type. - - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level and capacity, and uses the default - comparer for the key type. - - The estimated number of threads that will update the - concurrently. - The initial number of elements that the - can contain. - is - less than 1. - is less than - 0. - - - - Initializes a new instance of the - class that contains elements copied from the specified , has the default concurrency - level, has the default initial capacity, and uses the default comparer for the key type. - - The whose elements are copied to - the new - . - is a null reference - (Nothing in Visual Basic). - contains one or more - duplicate keys. - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level and capacity, and uses the specified - . - - The - implementation to use when comparing keys. - is a null reference - (Nothing in Visual Basic). - - - - Initializes a new instance of the - class that contains elements copied from the specified , has the default concurrency level, has the default - initial capacity, and uses the specified - . - - The whose elements are copied to - the new - . - The - implementation to use when comparing keys. - is a null reference - (Nothing in Visual Basic). -or- - is a null reference (Nothing in Visual Basic). - - - - - Initializes a new instance of the - class that contains elements copied from the specified , - has the specified concurrency level, has the specified initial capacity, and uses the specified - . - - The estimated number of threads that will update the - concurrently. - The whose elements are copied to the new - . - The implementation to use - when comparing keys. - - is a null reference (Nothing in Visual Basic). - -or- - is a null reference (Nothing in Visual Basic). - - - is less than 1. - - contains one or more duplicate keys. - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level, has the specified initial capacity, and - uses the specified . - - The estimated number of threads that will update the - concurrently. - The initial number of elements that the - can contain. - The - implementation to use when comparing keys. - - is less than 1. -or- - is less than 0. - - is a null reference - (Nothing in Visual Basic). - - - - Attempts to add the specified key and value to the . - - The key of the element to add. - The value of the element to add. The value can be a null reference (Nothing - in Visual Basic) for reference types. - true if the key/value pair was added to the - successfully; otherwise, false. - is null reference - (Nothing in Visual Basic). - The - contains too many elements. - - - - Determines whether the contains the specified - key. - - The key to locate in the . - true if the contains an element with - the specified key; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Attempts to remove and return the the value with the specified key from the - . - - The key of the element to remove and return. - When this method returns, contains the object removed from the - or the default value of - if the operation failed. - true if an object was removed successfully; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Removes the specified key from the dictionary if it exists and returns its associated value. - If matchValue flag is set, the key will be removed only if is associated with a particular - value. - - The key to search for and remove if it exists. - The variable into which the removed value, if found, is stored. - Whether removal of the key is conditional on its value. - The conditional value to compare against if is true - - - - - Attempts to get the value associated with the specified key from the . - - The key of the value to get. - When this method returns, contains the object from - the - with the spedified key or the default value of - , if the operation failed. - true if the key was found in the ; - otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Compares the existing value for the specified key with a specified value, and if they’re equal, - updates the key with a third value. - - The key whose value is compared with and - possibly replaced. - The value that replaces the value of the element with if the comparison results in equality. - The value that is compared to the value of the element with - . - true if the value with was equal to and replaced with ; otherwise, - false. - is a null - reference. - - - - Removes all keys and values from the . - - - - - Copies the elements of the to an array of - type , starting at the - specified array index. - - The one-dimensional array of type - that is the destination of the elements copied from the . The array must have zero-based indexing. - The zero-based index in at which copying - begins. - is a null reference - (Nothing in Visual Basic). - is less than - 0. - is equal to or greater than - the length of the . -or- The number of elements in the source - is greater than the available space from to the end of the destination - . - - - - Copies the key and value pairs stored in the to a - new array. - - A new array containing a snapshot of key and value pairs copied from the . - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToPairs. - - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToEntries. - - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToObjects. - - - - Returns an enumerator that iterates through the . - An enumerator for the . - - The enumerator returned from the dictionary is safe to use concurrently with - reads and writes to the dictionary, however it does not represent a moment-in-time snapshot - of the dictionary. The contents exposed through the enumerator may contain modifications - made to the dictionary after was called. - - - - - Shared internal implementation for inserts and updates. - If key exists, we always return false; and if updateIfExists == true we force update with value; - If key doesn't exist, we always add value and return true; - - - - - Adds a key/value pair to the - if the key does not already exist. - - The key of the element to add. - The function used to generate a value for the key - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The value for the key. This will be either the existing value for the key if the - key is already in the dictionary, or the new value for the key as returned by valueFactory - if the key was not in the dictionary. - - - - Adds a key/value pair to the - if the key does not already exist. - - The key of the element to add. - the value to be added, if the key does not already exist - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The value for the key. This will be either the existing value for the key if the - key is already in the dictionary, or the new value if the key was not in the dictionary. - - - - Adds a key/value pair to the if the key does not already - exist, or updates a key/value pair in the if the key - already exists. - - The key to be added or whose value should be updated - The function used to generate a value for an absent key - The function used to generate a new value for an existing key - based on the key's existing value - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The new value for the key. This will be either be the result of addValueFactory (if the key was - absent) or the result of updateValueFactory (if the key was present). - - - - Adds a key/value pair to the if the key does not already - exist, or updates a key/value pair in the if the key - already exists. - - The key to be added or whose value should be updated - The value to be added for an absent key - The function used to generate a new value for an existing key based on - the key's existing value - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The new value for the key. This will be either be the result of addValueFactory (if the key was - absent) or the result of updateValueFactory (if the key was present). - - - - Adds the specified key and value to the . - - The object to use as the key of the element to add. - The object to use as the value of the element to add. - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - - An element with the same key already exists in the . - - - - Removes the element with the specified key from the . - - The key of the element to remove. - true if the element is successfully remove; otherwise false. This method also returns - false if - was not found in the original . - - is a null reference - (Nothing in Visual Basic). - - - - Adds the specified value to the - with the specified key. - - The - structure representing the key and value to add to the . - The of is null. - The - contains too many elements. - An element with the same key already exists in the - - - - - Determines whether the - contains a specific key and value. - - The - structure to locate in the . - true if the is found in the ; otherwise, false. - - - - Removes a key and value from the dictionary. - - The - structure representing the key and value to remove from the . - true if the key and value represented by is successfully - found and removed; otherwise, false. - The Key property of is a null reference (Nothing in Visual Basic). - - - Returns an enumerator that iterates through the . - An enumerator for the . - - The enumerator returned from the dictionary is safe to use concurrently with - reads and writes to the dictionary, however it does not represent a moment-in-time snapshot - of the dictionary. The contents exposed through the enumerator may contain modifications - made to the dictionary after was called. - - - - - Adds the specified key and value to the dictionary. - - The object to use as the key. - The object to use as the value. - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - - is of a type that is not assignable to the key type of the . -or- - is of a type that is not assignable to , - the type of values in the . - -or- A value with the same key already exists in the . - - - - - Gets whether the contains an - element with the specified key. - - The key to locate in the . - true if the contains - an element with the specified key; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - Provides an for the - . - An for the . - - - - Removes the element with the specified key from the . - - The key of the element to remove. - is a null reference - (Nothing in Visual Basic). - - - - Copies the elements of the to an array, starting - at the specified array index. - - The one-dimensional array that is the destination of the elements copied from - the . The array must have zero-based - indexing. - The zero-based index in at which copying - begins. - is a null reference - (Nothing in Visual Basic). - is less than - 0. - is equal to or greater than - the length of the . -or- The number of elements in the source - is greater than the available space from to the end of the destination - . - - - - Replaces the internal table with a larger one. To prevent multiple threads from resizing the - table as a result of races, the table of buckets that was deemed too small is passed in as - an argument to GrowTable(). GrowTable() obtains a lock, and then checks whether the bucket - table has been replaced in the meantime or not. - - Reference to the bucket table that was deemed too small. - - - - Computes the bucket and lock number for a particular key. - - - - - Acquires all locks for this hash table, and increments locksAcquired by the number - of locks that were successfully acquired. The locks are acquired in an increasing - order. - - - - - Acquires a contiguous range of locks for this hash table, and increments locksAcquired - by the number of locks that were successfully acquired. The locks are acquired in an - increasing order. - - - - - Releases a contiguous range of locks. - - - - - Gets a collection containing the keys in the dictionary. - - - - - Gets a collection containing the values in the dictionary. - - - - - A helper method for asserts. - - - - - Get the data array to be serialized - - - - - Construct the dictionary from a previously seiralized one - - - - - Gets or sets the value associated with the specified key. - - The key of the value to get or set. - The value associated with the specified key. If the specified key is not found, a get - operation throws a - , and a set operation creates a new - element with the specified key. - is a null reference - (Nothing in Visual Basic). - The property is retrieved and - - does not exist in the collection. - - - - Gets the number of key/value pairs contained in the . - - The dictionary contains too many - elements. - The number of key/value paris contained in the . - Count has snapshot semantics and represents the number of items in the - at the moment when Count was accessed. - - - - Gets a value that indicates whether the is empty. - - true if the is empty; otherwise, - false. - - - - Gets a collection containing the keys in the . - - An containing the keys in the - . - - - - Gets a collection containing the values in the . - - An containing the values in - the - . - - - - Gets a value indicating whether the dictionary is read-only. - - true if the is - read-only; otherwise, false. For , this property always returns - false. - - - - Gets a value indicating whether the has a fixed size. - - true if the has a - fixed size; otherwise, false. For , this property always - returns false. - - - - Gets a value indicating whether the is read-only. - - true if the is - read-only; otherwise, false. For , this property always - returns false. - - - - Gets an containing the keys of the . - - An containing the keys of the . - - - - Gets an containing the values in the . - - An containing the values in the . - - - - Gets or sets the value associated with the specified key. - - The key of the value to get or set. - The value associated with the specified key, or a null reference (Nothing in Visual Basic) - if is not in the dictionary or is of a type that is - not assignable to the key type of the . - is a null reference - (Nothing in Visual Basic). - - A value is being assigned, and is of a type that is not assignable to the - key type of the . -or- A value is being - assigned, and is of a type that is not assignable to the value type - of the - - - - - Gets a value indicating whether access to the is - synchronized with the SyncRoot. - - true if access to the is synchronized - (thread safe); otherwise, false. For , this property always - returns false. - - - - Gets an object that can be used to synchronize access to the . This property is not supported. - - The SyncRoot property is not supported. - - - - The number of concurrent writes for which to optimize by default. - - - - - A node in a singly-linked list representing a particular hash table bucket. - - - - - A private class to represent enumeration over the dictionary that implements the - IDictionaryEnumerator interface. - - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - Represents an asynchronous method builder. - - - A cached VoidTaskResult task used for builders that complete synchronously. - - - The generic builder object to which this non-generic instance delegates. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state. - - The builder is not initialized. - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - - Gets the for this builder. - The representing the builder's asynchronous operation. - The builder is not initialized. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - Holds state related to the builder's IAsyncStateMachine. - This is a mutable struct. Be very delicate with it. - - - A reference to the heap-allocated state machine object associated with this builder. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument is null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - - Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method. - On first invocation, the supplied state machine will be boxed. - - Specifies the type of the method builder used. - Specifies the type of the state machine used. - The builder. - The state machine. - An Action to provide to the awaiter. - - - Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext. - - - The context with which to run MoveNext. - - - The state machine whose MoveNext method should be invoked. - - - Initializes the runner. - The context with which to run MoveNext. - - - Invokes MoveNext under the provided context. - - - Cached delegate used with ExecutionContext.Run. - - - Invokes the MoveNext method on the supplied IAsyncStateMachine. - The IAsyncStateMachine machine instance. - - - - Provides a builder for asynchronous methods that return void. - This type is intended for compiler use only. - - - - The synchronization context associated with this operation. - - - State related to the IAsyncStateMachine. - - - An object used by the debugger to uniquely identify this builder. Lazily initialized. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Registers with UnobservedTaskException to suppress exception crashing. - - - Non-zero if PreventUnobservedTaskExceptions has already been invoked. - - - Initializes a new . - The initialized . - - - Initializes the . - The synchronizationContext associated with this operation. This may be null. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument was null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - Completes the method builder successfully. - - - Faults the method builder with an exception. - The exception that is the cause of this fault. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - - - Notifies the current synchronization context that the operation completed. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger and only in a single-threaded manner. - - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - A cached task for default(TResult). - - - State related to the IAsyncStateMachine. - - - The lazily-initialized task. - Must be named m_task for debugger step-over to work correctly. - - - The lazily-initialized task completion source. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state with the specified result. - - The result to use to complete the task. - The task has already completed. - - - - Completes the builder by using either the supplied completed task, or by completing - the builder's previously accessed task using default(TResult). - - A task already completed with the value default(TResult). - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - This should only be invoked from within an asynchronous method, - and only by the debugger. - - - - - Gets a task for the specified result. This will either - be a cached or new task, never null. - - The result for which we need a task. - The completed task containing the result. - - - Gets the lazily-initialized TaskCompletionSource. - - - Gets the for this builder. - The representing the builder's asynchronous operation. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - Provides a base class used to cache tasks of a specific return type. - Specifies the type of results the cached tasks return. - - - - A singleton cache for this result type. - This may be null if there are no cached tasks for this TResult. - - - - Creates a non-disposable task. - The result for the task. - The cacheable task. - - - Creates a cache. - A task cache for this result type. - - - Gets a cached task if one exists. - The result for which we want a cached task. - A cached task if one exists; otherwise, null. - - - Provides a cache for Boolean tasks. - - - A true task. - - - A false task. - - - Gets a cached task for the Boolean result. - true or false - A cached task for the Boolean result. - - - Provides a cache for zero Int32 tasks. - - - The minimum value, inclusive, for which we want a cached task. - - - The maximum value, exclusive, for which we want a cached task. - - - The cache of Task{Int32}. - - - Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). - - - Gets a cached task for the zero Int32 result. - The integer value - A cached task for the Int32 result or null if not cached. - - - - Represents state machines generated for asynchronous methods. - This type is intended for compiler use only. - - - - Moves the state machine to its next state. - - - Configures the state machine with a heap-allocated replica. - The heap-allocated replica. - - - - Represents an awaiter used to schedule continuations when an await operation completes. - - - - - Represents an operation that will schedule continuations when the operation completes. - - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information. - - - Used with Task(of void) - - - - An interface similar to the one added in .NET 4.0. - - - - The exception that is thrown in a thread upon cancellation of an operation that the thread was executing. - - - Initializes the exception. - - - Initializes the exception. - The error message that explains the reason for the exception. - - - Initializes the exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - Initializes the exception. - A cancellation token associated with the operation that was canceled. - - - Initializes the exception. - The error message that explains the reason for the exception. - A cancellation token associated with the operation that was canceled. - - - Initializes the exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - A cancellation token associated with the operation that was canceled. - - - Gets a token associated with the operation that was canceled. - - - - A dummy replacement for the .NET internal class StackCrawlMark. - - - - - Propogates notification that operations should be canceled. - - - - A may be created directly in an unchangeable canceled or non-canceled state - using the CancellationToken's constructors. However, to have a CancellationToken that can change - from a non-canceled to a canceled state, - CancellationTokenSource must be used. - CancellationTokenSource exposes the associated CancellationToken that may be canceled by the source through its - Token property. - - - Once canceled, a token may not transition to a non-canceled state, and a token whose - is false will never change to one that can be canceled. - - - All members of this struct are thread-safe and may be used concurrently from multiple threads. - - - - - - Internal constructor only a CancellationTokenSource should create a CancellationToken - - - - - Initializes the CancellationToken. - - - The canceled state for the token. - - - Tokens created with this constructor will remain in the canceled state specified - by the parameter. If is false, - both and will be false. - If is true, - both and will be true. - - - - - Registers a delegate that will be called when this CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - A Boolean value that indicates whether to capture - the current SynchronizationContext and use it - when invoking the . - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The state to pass to the when the delegate is invoked. This may be null. - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The state to pass to the when the delegate is invoked. This may be null. - A Boolean value that indicates whether to capture - the current SynchronizationContext and use it - when invoking the . - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Determines whether the current CancellationToken instance is equal to the - specified token. - - The other CancellationToken to which to compare this - instance. - True if the instances are equal; otherwise, false. Two tokens are equal if they are associated - with the same CancellationTokenSource or if they were both constructed - from public CancellationToken constructors and their values are equal. - - - - Determines whether the current CancellationToken instance is equal to the - specified . - - The other object to which to compare this instance. - True if is a CancellationToken - and if the two instances are equal; otherwise, false. Two tokens are equal if they are associated - with the same CancellationTokenSource or if they were both constructed - from public CancellationToken constructors and their values are equal. - An associated CancellationTokenSource has been disposed. - - - - Serves as a hash function for a CancellationToken. - - A hash code for the current CancellationToken instance. - - - - Determines whether two CancellationToken instances are equal. - - The first instance. - The second instance. - True if the instances are equal; otherwise, false. - An associated CancellationTokenSource has been disposed. - - - - Determines whether two CancellationToken instances are not equal. - - The first instance. - The second instance. - True if the instances are not equal; otherwise, false. - An associated CancellationTokenSource has been disposed. - - - - Throws a OperationCanceledException if - this token has had cancellation requested. - - - This method provides functionality equivalent to: - - if (token.IsCancellationRequested) - throw new OperationCanceledException(token); - - - The token has had cancellation requested. - The associated CancellationTokenSource has been disposed. - - - - Returns an empty CancellationToken value. - - - The value returned by this property will be non-cancelable by default. - - - - - Gets whether cancellation has been requested for this token. - - Whether cancellation has been requested for this token. - - - This property indicates whether cancellation has been requested for this token, - either through the token initially being construted in a canceled state, or through - calling Cancel - on the token's associated . - - - If this property is true, it only guarantees that cancellation has been requested. - It does not guarantee that every registered handler - has finished executing, nor that cancellation requests have finished propagating - to all registered handlers. Additional synchronization may be required, - particularly in situations where related objects are being canceled concurrently. - - - - - - Gets whether this token is capable of being in the canceled state. - - - If CanBeCanceled returns false, it is guaranteed that the token will never transition - into a canceled state, meaning that will never - return true. - - - - - Gets a that is signaled when the token is canceled. - - Accessing this property causes a WaitHandle - to be instantiated. It is preferable to only use this property when necessary, and to then - dispose the associated instance at the earliest opportunity (disposing - the source will dispose of this allocated handle). The handle should not be closed or disposed directly. - - The associated CancellationTokenSource has been disposed. - - - - Represents a callback delegate that has been registered with a CancellationToken. - - - To unregister a callback, dispose the corresponding Registration instance. - - - - - Attempts to deregister the item. If it's already being run, this may fail. - Entails a full memory fence. - - True if the callback was found and deregistered, false otherwise. - - - - Disposes of the registration and unregisters the target callback from the associated - CancellationToken. - If the target callback is currently executing this method will wait until it completes, except - in the degenerate cases where a callback method deregisters itself. - - - - - Determines whether two CancellationTokenRegistration - instances are equal. - - The first instance. - The second instance. - True if the instances are equal; otherwise, false. - - - - Determines whether two CancellationTokenRegistration instances are not equal. - - The first instance. - The second instance. - True if the instances are not equal; otherwise, false. - - - - Determines whether the current CancellationTokenRegistration instance is equal to the - specified . - - The other object to which to compare this instance. - True, if both this and are equal. False, otherwise. - Two CancellationTokenRegistration instances are equal if - they both refer to the output of a single call to the same Register method of a - CancellationToken. - - - - - Determines whether the current CancellationToken instance is equal to the - specified . - - The other CancellationTokenRegistration to which to compare this instance. - True, if both this and are equal. False, otherwise. - Two CancellationTokenRegistration instances are equal if - they both refer to the output of a single call to the same Register method of a - CancellationToken. - - - - - Serves as a hash function for a CancellationTokenRegistration.. - - A hash code for the current CancellationTokenRegistration instance. - - - - Signals to a that it should be canceled. - - - - is used to instantiate a - (via the source's Token property) - that can be handed to operations that wish to be notified of cancellation or that can be used to - register asynchronous operations for cancellation. That token may have cancellation requested by - calling to the source's Cancel - method. - - - All members of this class, except Dispose, are thread-safe and may be used - concurrently from multiple threads. - - - - - The ID of the thread currently executing the main body of CTS.Cancel() - this helps us to know if a call to ctr.Dispose() is running 'within' a cancellation callback. - This is updated as we move between the main thread calling cts.Cancel() and any syncContexts that are used to - actually run the callbacks. - - - - Initializes the . - - - - - Communicates a request for cancellation. - - - - The associated will be - notified of the cancellation and will transition to a state where - IsCancellationRequested returns true. - Any callbacks or cancelable operations - registered with the will be executed. - - - Cancelable operations and callbacks registered with the token should not throw exceptions. - However, this overload of Cancel will aggregate any exceptions thrown into a , - such that one callback throwing an exception will not prevent other registered callbacks from being executed. - - - The that was captured when each callback was registered - will be reestablished when the callback is invoked. - - - An aggregate exception containing all the exceptions thrown - by the registered callbacks on the associated . - This has been disposed. - - - - Communicates a request for cancellation. - - - - The associated will be - notified of the cancellation and will transition to a state where - IsCancellationRequested returns true. - Any callbacks or cancelable operations - registered with the will be executed. - - - Cancelable operations and callbacks registered with the token should not throw exceptions. - If is true, an exception will immediately propagate out of the - call to Cancel, preventing the remaining callbacks and cancelable operations from being processed. - If is false, this overload will aggregate any - exceptions thrown into a , - such that one callback throwing an exception will not prevent other registered callbacks from being executed. - - - The that was captured when each callback was registered - will be reestablished when the callback is invoked. - - - Specifies whether exceptions should immediately propagate. - An aggregate exception containing all the exceptions thrown - by the registered callbacks on the associated . - This has been disposed. - - - - Releases the resources used by this . - - - This method is not thread-safe for any other concurrent calls. - - - - - Throws an exception if the source has been disposed. - - - - - InternalGetStaticSource() - - Whether the source should be set. - A static source to be shared among multiple tokens. - - - - Registers a callback object. If cancellation has already occurred, the - callback will have been run by the time this method returns. - - - - - - - - - - Invoke the Canceled event. - - - The handlers are invoked synchronously in LIFO order. - - - - - Creates a CancellationTokenSource that will be in the canceled state - when any of the source tokens are in the canceled state. - - The first CancellationToken to observe. - The second CancellationToken to observe. - A CancellationTokenSource that is linked - to the source tokens. - A CancellationTokenSource associated with - one of the source tokens has been disposed. - - - - Creates a CancellationTokenSource that will be in the canceled state - when any of the source tokens are in the canceled state. - - The CancellationToken instances to observe. - A CancellationTokenSource that is linked - to the source tokens. - is null. - A CancellationTokenSource associated with - one of the source tokens has been disposed. - - - - Gets whether cancellation has been requested for this CancellationTokenSource. - - Whether cancellation has been requested for this CancellationTokenSource. - - - This property indicates whether cancellation has been requested for this token source, such as - due to a call to its - Cancel method. - - - If this property returns true, it only guarantees that cancellation has been requested. It does not - guarantee that every handler registered with the corresponding token has finished executing, nor - that cancellation requests have finished propagating to all registered handlers. Additional - synchronization may be required, particularly in situations where related objects are being - canceled concurrently. - - - - - - A simple helper to determine whether cancellation has finished. - - - - - A simple helper to determine whether disposal has occured. - - - - - The ID of the thread that is running callbacks. - - - - - Gets the CancellationToken - associated with this . - - The CancellationToken - associated with this . - The token source has been - disposed. - - - - - - - - - - - - - - The currently executing callback - - - - - A helper class for collating the various bits of information required to execute - cancellation callbacks. - - - - - InternalExecuteCallbackSynchronously_GeneralPath - This will be called on the target synchronization context, however, we still need to restore the required execution context - - - - - A sparsely populated array. Elements can be sparse and some null, but this allows for - lock-free additions and growth, and also for constant time removal (by nulling out). - - The kind of elements contained within. - - - - Allocates a new array with the given initial size. - - How many array slots to pre-allocate. - - - - Adds an element in the first available slot, beginning the search from the tail-to-head. - If no slots are available, the array is grown. The method doesn't return until successful. - - The element to add. - Information about where the add happened, to enable O(1) deregistration. - - - - The tail of the doubly linked list. - - - - - A struct to hold a link to the exact spot in an array an element was inserted, enabling - constant time removal later on. - - - - - A fragment of a sparsely populated array, doubly linked. - - The kind of elements contained within. - - - - Provides lazy initialization routines. - - - These routines avoid needing to allocate a dedicated, lazy-initialization instance, instead using - references to ensure targets have been initialized as they are accessed. - - - - - Initializes a target reference type with the type's default constructor if the target has not - already been initialized. - - The refence type of the reference to be initialized. - A reference of type to initialize if it has not - already been initialized. - The initialized reference of type . - Type does not have a default - constructor. - - Permissions to access the constructor of type were missing. - - - - This method may only be used on reference types. To ensure initialization of value - types, see other overloads of EnsureInitialized. - - - This method may be used concurrently by multiple threads to initialize . - In the event that multiple threads access this method concurrently, multiple instances of - may be created, but only one will be stored into . In such an occurrence, this method will not dispose of the - objects that were not stored. If such objects must be disposed, it is up to the caller to determine - if an object was not used and to then dispose of the object appropriately. - - - - - - Initializes a target reference type using the specified function if it has not already been - initialized. - - The reference type of the reference to be initialized. - The reference of type to initialize if it has not - already been initialized. - The invoked to initialize the - reference. - The initialized reference of type . - Type does not have a - default constructor. - returned - null. - - - This method may only be used on reference types, and may - not return a null reference (Nothing in Visual Basic). To ensure initialization of value types or - to allow null reference types, see other overloads of EnsureInitialized. - - - This method may be used concurrently by multiple threads to initialize . - In the event that multiple threads access this method concurrently, multiple instances of - may be created, but only one will be stored into . In such an occurrence, this method will not dispose of the - objects that were not stored. If such objects must be disposed, it is up to the caller to determine - if an object was not used and to then dispose of the object appropriately. - - - - - - Initialize the target using the given delegate (slow path). - - The reference type of the reference to be initialized. - The variable that need to be initialized - The delegate that will be executed to initialize the target - The initialized variable - - - - Initializes a target reference or value type with its default constructor if it has not already - been initialized. - - The type of the reference to be initialized. - A reference or value of type to initialize if it - has not already been initialized. - A reference to a boolean that determines whether the target has already - been initialized. - A reference to an object used as the mutually exclusive lock for initializing - . - The initialized value of type . - - - - Initializes a target reference or value type with a specified function if it has not already been - initialized. - - The type of the reference to be initialized. - A reference or value of type to initialize if it - has not already been initialized. - A reference to a boolean that determines whether the target has already - been initialized. - A reference to an object used as the mutually exclusive lock for initializing - . - The invoked to initialize the - reference or value. - The initialized value of type . - - - - Ensure the target is initialized and return the value (slow path). This overload permits nulls - and also works for value type targets. Uses the supplied function to create the value. - - The type of target. - A reference to the target to be initialized. - A reference to a location tracking whether the target has been initialized. - A reference to a location containing a mutual exclusive lock. - - The to invoke in order to produce the lazily-initialized value. - - The initialized object. - - - - Provides a slimmed down version of . - - - All public and protected members of are thread-safe and may be used - concurrently from multiple threads, with the exception of Dispose, which - must only be used when all other operations on the have - completed, and Reset, which should only be used when no other threads are - accessing the event. - - - - - Initializes a new instance of the - class with an initial state of nonsignaled. - - - - - Initializes a new instance of the - class with a Boolen value indicating whether to set the intial state to signaled. - - true to set the initial state signaled; false to set the initial state - to nonsignaled. - - - - Initializes a new instance of the - class with a Boolen value indicating whether to set the intial state to signaled and a specified - spin count. - - true to set the initial state to signaled; false to set the initial state - to nonsignaled. - The number of spin waits that will occur before falling back to a true - wait. - is less than - 0 or greater than the maximum allowed value. - - - - Initializes the internal state of the event. - - Whether the event is set initially or not. - The spin count that decides when the event will block. - - - - Helper to ensure the lock object is created before first use. - - - - - This method lazily initializes the event object. It uses CAS to guarantee that - many threads racing to call this at once don't result in more than one event - being stored and used. The event will be signaled or unsignaled depending on - the state of the thin-event itself, with synchronization taken into account. - - True if a new event was created and stored, false otherwise. - - - - Sets the state of the event to signaled, which allows one or more threads waiting on the event to - proceed. - - - - - Private helper to actually perform the Set. - - Indicates whether we are calling Set() during cancellation. - The object has been canceled. - - - - Sets the state of the event to nonsignaled, which causes threads to block. - - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - Blocks the current thread until the current is set. - - - The maximum number of waiters has been exceeded. - - - The caller of this method blocks indefinitely until the current instance is set. The caller will - return immediately if the event is currently in a set state. - - - - - Blocks the current thread until the current receives a signal, - while observing a . - - The to - observe. - - The maximum number of waiters has been exceeded. - - was - canceled. - - The caller of this method blocks indefinitely until the current instance is set. The caller will - return immediately if the event is currently in a set state. - - - - - Blocks the current thread until the current is set, using a - to measure the time interval. - - A that represents the number of milliseconds - to wait, or a that represents -1 milliseconds to wait indefinitely. - - true if the was set; otherwise, - false. - is a negative - number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater - than . - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - to measure the time interval, while observing a . - - A that represents the number of milliseconds - to wait, or a that represents -1 milliseconds to wait indefinitely. - - The to - observe. - true if the was set; otherwise, - false. - is a negative - number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater - than . - was canceled. - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - 32-bit signed integer to measure the time interval. - - The number of milliseconds to wait, or (-1) to wait indefinitely. - true if the was set; otherwise, - false. - is a - negative number other than -1, which represents an infinite time-out. - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - 32-bit signed integer to measure the time interval, while observing a . - - The number of milliseconds to wait, or (-1) to wait indefinitely. - The to - observe. - true if the was set; otherwise, - false. - is a - negative number other than -1, which represents an infinite time-out. - - The maximum number of waiters has been exceeded. - - was canceled. - - - - Releases all resources used by the current instance of . - - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - When overridden in a derived class, releases the unmanaged resources used by the - , and optionally releases the managed resources. - - true to release both managed and unmanaged resources; - false to release only unmanaged resources. - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - Throw ObjectDisposedException if the MRES is disposed - - - - - Private helper method to wake up waiters when a cancellationToken gets canceled. - - - - - Private helper method for updating parts of a bit-string state value. - Mainly called from the IsSet and Waiters properties setters - - - Note: the parameter types must be int as CompareExchange cannot take a Uint - - The new value - The mask used to set the bits - - - - Private helper method - performs Mask and shift, particular helpful to extract a field from a packed word. - eg ExtractStatePortionAndShiftRight(0x12345678, 0xFF000000, 24) => 0x12, ie extracting the top 8-bits as a simple integer - - ?? is there a common place to put this rather than being private to MRES? - - - - - - - - - Performs a Mask operation, but does not perform the shift. - This is acceptable for boolean values for which the shift is unnecessary - eg (val & Mask) != 0 is an appropriate way to extract a boolean rather than using - ((val & Mask) >> shiftAmount) == 1 - - ?? is there a common place to put this rather than being private to MRES? - - - - - - - Helper function to measure and update the wait time - - The first time (in Ticks) observed when the wait started. - The orginal wait timeoutout in milliseconds. - The new wait time in milliseconds, -1 if the time expired, -2 if overflow in counters - has occurred. - - - - Gets the underlying object for this . - - The underlying event object fore this . - - Accessing this property forces initialization of an underlying event object if one hasn't - already been created. To simply wait on this , - the public Wait methods should be preferred. - - - - - Gets whether the event is set. - - true if the event has is set; otherwise, false. - - - - Gets the number of spin waits that will be occur before falling back to a true wait. - - - - - How many threads are waiting. - - - - - Provides support for spin-based waiting. - - - - encapsulates common spinning logic. On single-processor machines, yields are - always used instead of busy waits, and on computers with Intel™ processors employing Hyper-Threading™ - technology, it helps to prevent hardware thread starvation. SpinWait encapsulates a good mixture of - spinning and true yielding. - - - is a value type, which means that low-level code can utilize SpinWait without - fear of unnecessary allocation overheads. SpinWait is not generally useful for ordinary applications. - In most cases, you should use the synchronization classes provided by the .NET Framework, such as - . For most purposes where spin waiting is required, however, - the type should be preferred over the System.Threading.Thread.SpinWait method. - - - While SpinWait is designed to be used in concurrent applications, it is not designed to be - used from multiple threads concurrently. SpinWait's members are not thread-safe. If multiple - threads must spin, each should use its own instance of SpinWait. - - - - - - Performs a single spin. - - - This is typically called in a loop, and may change in behavior based on the number of times a - has been called thus far on this instance. - - - - - Resets the spin counter. - - - This makes and behave as though no calls - to had been issued on this instance. If a instance - is reused many times, it may be useful to reset it to avoid yielding too soon. - - - - - Spins until the specified condition is satisfied. - - A delegate to be executed over and over until it returns true. - The argument is null. - - - - Spins until the specified condition is satisfied or until the specified timeout is expired. - - A delegate to be executed over and over until it returns true. - - A that represents the number of milliseconds to wait, - or a TimeSpan that represents -1 milliseconds to wait indefinitely. - True if the condition is satisfied within the timeout; otherwise, false - The argument is null. - is a negative number - other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than - . - - - - Spins until the specified condition is satisfied or until the specified timeout is expired. - - A delegate to be executed over and over until it returns true. - The number of milliseconds to wait, or (-1) to wait indefinitely. - True if the condition is satisfied within the timeout; otherwise, false - The argument is null. - is a - negative number other than -1, which represents an infinite time-out. - - - - Gets the number of times has been called on this instance. - - - - - Gets whether the next call to will yield the processor, triggering a - forced context switch. - - Whether the next call to will yield the processor, triggering a - forced context switch. - - On a single-CPU machine, always yields the processor. On machines with - multiple CPUs, may yield after an unspecified number of calls. - - - - - A helper class to get the number of preocessors, it updates the numbers of processors every sampling interval - - - - - Gets the number of available processors - - - - - Gets whether the current machine has only a single processor. - - - - - Represents an asynchronous operation that produces a result at some time in the future. - - - The type of the result produced by this . - - - - instances may be created in a variety of ways. The most common approach is by - using the task's property to retrieve a instance that can be used to create tasks for several - purposes. For example, to create a that runs a function, the factory's StartNew - method may be used: - - // C# - var t = Task<int>.Factory.StartNew(() => GenerateResult()); - - or - - var t = Task.Factory.StartNew(() => GenerateResult()); - - ' Visual Basic - Dim t = Task<int>.Factory.StartNew(Function() GenerateResult()) - - or - - Dim t = Task.Factory.StartNew(Function() GenerateResult()) - - - - The class also provides constructors that initialize the task but that do not - schedule it for execution. For performance reasons, the StartNew method should be the - preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation - and scheduling must be separated, the constructors may be used, and the task's - Start - method may then be used to schedule the task for execution at a later time. - - - All members of , except for - Dispose, are thread-safe - and may be used from multiple threads concurrently. - - - - - - Represents an asynchronous operation. - - - - instances may be created in a variety of ways. The most common approach is by - using the Task type's property to retrieve a instance that can be used to create tasks for several - purposes. For example, to create a that runs an action, the factory's StartNew - method may be used: - - // C# - var t = Task.Factory.StartNew(() => DoAction()); - - ' Visual Basic - Dim t = Task.Factory.StartNew(Function() DoAction()) - - - - The class also provides constructors that initialize the Task but that do not - schedule it for execution. For performance reasons, TaskFactory's StartNew method should be the - preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation - and scheduling must be separated, the constructors may be used, and the task's - method may then be used to schedule the task for execution at a later time. - - - All members of , except for , are thread-safe - and may be used from multiple threads concurrently. - - - For operations that return values, the class - should be used. - - - For developers implementing custom debuggers, several internal and private members of Task may be - useful (these may change from release to release). The Int32 m_taskId field serves as the backing - store for the property, however accessing this field directly from a debugger may be - more efficient than accessing the same value through the property's getter method (the - s_taskIdCounter Int32 counter is used to retrieve the next available ID for a Task). Similarly, the - Int32 m_stateFlags field stores information about the current lifecycle stage of the Task, - information also accessible through the property. The m_action System.Object - field stores a reference to the Task's delegate, and the m_stateObject System.Object field stores the - async state passed to the Task by the developer. Finally, for debuggers that parse stack frames, the - InternalWait method serves a potential marker for when a Task is entering a wait operation. - - - - - - A type initializer that runs with the appropriate permissions. - - - - - Initializes a new with the specified action. - - The delegate that represents the code to execute in the Task. - The argument is null. - - - - Initializes a new with the specified action and CancellationToken. - - The delegate that represents the code to execute in the Task. - The CancellationToken - that will be assigned to the new Task. - The argument is null. - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action and creation options. - - The delegate that represents the code to execute in the task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action and creation options. - - The delegate that represents the code to execute in the task. - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action and state. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - - The argument is null. - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - The that will be assigned to the new task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - An internal constructor used by the factory methods on task and its descendent(s). - This variant does not capture the ExecutionContext; it is up to the caller to do that. - - An action to execute. - Optional state to pass to the action. - Parent of Task. - A CancellationToken for the task. - A task scheduler under which the task will run. - Options to control its execution. - Internal options to control its execution - - - - Common logic used by the following internal ctors: - Task() - Task(object action, object state, Task parent, TaskCreationOptions options, TaskScheduler taskScheduler) - - ASSUMES THAT m_creatingTask IS ALREADY SET. - - - Action for task to execute. - Object to which to pass to action (may be null) - Task scheduler on which to run thread (only used by continuation tasks). - A CancellationToken for the Task. - Options to customize behavior of Task. - Internal options to customize behavior of Task. - - - - Checks if we registered a CT callback during construction, and deregisters it. - This should be called when we know the registration isn't useful anymore. Specifically from Finish() if the task has completed - successfully or with an exception. - - - - - Captures the ExecutionContext so long as flow isn't suppressed. - - A stack crawl mark pointing to the frame of the caller. - - - - Internal function that will be called by a new child task to add itself to - the children list of the parent (this). - - Since a child task can only be created from the thread executing the action delegate - of this task, reentrancy is neither required nor supported. This should not be called from - anywhere other than the task construction/initialization codepaths. - - - - - Starts the , scheduling it for execution to the current TaskScheduler. - - - A task may only be started and run only once. Any attempts to schedule a task a second time - will result in an exception. - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Starts the , scheduling it for execution to the specified TaskScheduler. - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - The TaskScheduler with which to associate - and execute this task. - - - The argument is null. - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Runs the synchronously on the current TaskScheduler. - - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - Tasks executed with will be associated with the current TaskScheduler. - - - If the target scheduler does not support running this Task on the current thread, the Task will - be scheduled for execution on the scheduler, and the current thread will block until the - Task has completed execution. - - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Runs the synchronously on the scheduler provided. - - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - If the target scheduler does not support running this Task on the current thread, the Task will - be scheduled for execution on the scheduler, and the current thread will block until the - Task has completed execution. - - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - The parameter - is null. - The scheduler on which to attempt to run this task inline. - - - - Throws an exception if the task has been disposed, and hence can no longer be accessed. - - The task has been disposed. - - - - Sets the internal completion event. - - - - - Disposes the , releasing all of its unmanaged resources. - - - Unlike most of the members of , this method is not thread-safe. - Also, may only be called on a that is in one of - the final states: RanToCompletion, - Faulted, or - Canceled. - - - The exception that is thrown if the is not in - one of the final states: RanToCompletion, - Faulted, or - Canceled. - - - - - Disposes the , releasing all of its unmanaged resources. - - - A Boolean value that indicates whether this method is being called due to a call to . - - - Unlike most of the members of , this method is not thread-safe. - - - - - Schedules the task for execution. - - If true, TASK_STATE_STARTED bit is turned on in - an atomic fashion, making sure that TASK_STATE_CANCELED does not get set - underneath us. If false, TASK_STATE_STARTED bit is OR-ed right in. This - allows us to streamline things a bit for StartNew(), where competing cancellations - are not a problem. - - - - Adds an exception to the list of exceptions this task has thrown. - - An object representing either an Exception or a collection of Exceptions. - - - - Returns a list of exceptions by aggregating the holder's contents. Or null if - no exceptions have been thrown. - - Whether to include a TCE if cancelled. - An aggregate exception, or null if no exceptions have been caught. - - - - Throws an aggregate exception if the task contains exceptions. - - - - - Checks whether this is an attached task, and whether we are being called by the parent task. - And sets the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag based on that. - - This is meant to be used internally when throwing an exception, and when WaitAll is gathering - exceptions for tasks it waited on. If this flag gets set, the implicit wait on children - will skip exceptions to prevent duplication. - - This should only be called when this task has completed with an exception - - - - - - Signals completion of this particular task. - - The bUserDelegateExecuted parameter indicates whether this Finish() call comes following the - full execution of the user delegate. - - If bUserDelegateExecuted is false, it mean user delegate wasn't invoked at all (either due to - a cancellation request, or because this task is a promise style Task). In this case, the steps - involving child tasks (i.e. WaitForChildren) will be skipped. - - - - - - FinishStageTwo is to be executed as soon as we known there are no more children to complete. - It can happen i) either on the thread that originally executed this task (if no children were spawned, or they all completed by the time this task's delegate quit) - ii) or on the thread that executed the last child. - - - - - Final stage of the task completion code path. Notifies the parent (if any) that another of its childre are done, and runs continuations. - This function is only separated out from FinishStageTwo because these two operations are also needed to be called from CancellationCleanupLogic() - - - - - This is called by children of this task when they are completed. - - - - - This is to be called just before the task does its final state transition. - It traverses the list of exceptional children, and appends their aggregate exceptions into this one's exception list - - - - - Special purpose Finish() entry point to be used when the task delegate throws a ThreadAbortedException - This makes a note in the state flags so that we avoid any costly synchronous operations in the finish codepath - such as inlined continuations - - - Indicates whether the ThreadAbortException was added to this task's exception holder. - This should always be true except for the case of non-root self replicating task copies. - - Whether the delegate was executed. - - - - Executes the task. This method will only be called once, and handles bookeeping associated with - self-replicating tasks, in addition to performing necessary exception marshaling. - - The task has already been disposed. - - - - IThreadPoolWorkItem override, which is the entry function for this task when the TP scheduler decides to run it. - - - - - - Outermost entry function to execute this task. Handles all aspects of executing a task on the caller thread. - Currently this is called by IThreadPoolWorkItem.ExecuteWorkItem(), and TaskManager.TryExecuteInline. - - - Performs atomic updates to prevent double execution. Should only be set to true - in codepaths servicing user provided TaskSchedulers. The ConcRT or ThreadPool schedulers don't need this. - - - - The actual code which invokes the body of the task. This can be overriden in derived types. - - - - - Alternate InnerInvoke prototype to be called from ExecuteSelfReplicating() so that - the Parallel Debugger can discover the actual task being invoked. - Details: Here, InnerInvoke is actually being called on the rootTask object while we are actually executing the - childTask. And the debugger needs to discover the childTask, so we pass that down as an argument. - The NoOptimization and NoInlining flags ensure that the childTask pointer is retained, and that this - function appears on the callstack. - - - - - - Performs whatever handling is necessary for an unhandled exception. Normally - this just entails adding the exception to the holder object. - - The exception that went unhandled. - - - - Waits for the to complete execution. - - - The was canceled -or- an exception was thrown during - the execution of the . - - - The has been disposed. - - - - - Waits for the to complete execution. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - true if the completed execution within the allotted time; otherwise, false. - - - The was canceled -or- an exception was thrown during the execution of the . - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - The has been disposed. - - - - - Waits for the to complete execution. - - - A to observe while waiting for the task to complete. - - - The was canceled. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - - - Waits for the to complete execution. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - true if the completed execution within the allotted time; otherwise, - false. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - - - Waits for the to complete execution. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for the task to complete. - - - true if the completed execution within the allotted time; otherwise, false. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - The core wait function, which is only accesible internally. It's meant to be used in places in TPL code where - the current context is known or cached. - - - - - Cancels the . - - Indiactes whether we should only cancel non-invoked tasks. - For the default scheduler this option will only be serviced through TryDequeue. - For custom schedulers we also attempt an atomic state transition. - true if the task was successfully canceled; otherwise, false. - The - has been disposed. - - - - Sets the task's cancellation acknowledged flag. - - - - - Runs all of the continuations, as appropriate. - - - - - Helper function to determine whether the current task is in the state desired by the - continuation kind under evaluation. Three possibilities exist: the task failed with - an unhandled exception (OnFailed), the task was canceled before running (OnAborted), - or the task completed successfully (OnCompletedSuccessfully). Note that the last - one includes completing due to cancellation. - - The continuation options under evaluation. - True if the continuation should be run given the task's current state. - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - The that will be assigned to the new continuation task. - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Converts TaskContinuationOptions to TaskCreationOptions, and also does - some validity checking along the way. - - Incoming TaskContinuationOptions - Outgoing TaskCreationOptions - Outgoing InternalTaskOptions - - - - Registers the continuation and possibly runs it (if the task is already finished). - - The continuation task itself. - TaskScheduler with which to associate continuation task. - Restrictions on when the continuation becomes active. - - - - Waits for all of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - An array of instances on which to wait. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - A to observe while waiting for the tasks to complete. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The was canceled. - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for the tasks to complete. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - Waits for a set of handles in a STA-aware way. In other words, it will wait for each - of the events individually if we're on a STA thread, because MsgWaitForMultipleObjectsEx - can't do a true wait-all due to its hidden message queue event. This is not atomic, - of course, but we only wait on one-way (MRE) events anyway so this is OK. - - An array of wait handles to wait on. - The timeout to use during waits. - The cancellationToken that enables a wait to be canceled. - True if all waits succeeded, false if a timeout occurred. - - - - Internal WaitAll implementation which is meant to be used with small number of tasks, - optimized for Parallel.Invoke and other structured primitives. - - - - - This internal function is only meant to be called by WaitAll() - If the completed task is canceled or it has other exceptions, here we will add those - into the passed in exception list (which will be lazily initialized here). - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - The index of the completed task in the array argument. - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - A to observe while waiting for a task to complete. - - - The index of the completed task in the array argument. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - The was canceled. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for a task to complete. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - Gets a unique ID for this Task instance. - - - Task IDs are assigned on-demand and do not necessarily represent the order in the which Task - instances were created. - - - - - Returns the unique ID of the currently executing Task. - - - - - Gets the Task instance currently executing, or - null if none exists. - - - - - Gets the Exception that caused the Task to end prematurely. If the Task completed successfully or has not yet thrown any - exceptions, this will return null. - - - Tasks that throw unhandled exceptions store the resulting exception and propagate it wrapped in a - in calls to Wait - or in accesses to the property. Any exceptions not observed by the time - the Task instance is garbage collected will be propagated on the finalizer thread. - - - The Task - has been disposed. - - - - - Gets the TaskStatus of this Task. - - - - - Gets whether this Task instance has completed - execution due to being canceled. - - - A Task will complete in Canceled state either if its CancellationToken - was marked for cancellation before the task started executing, or if the task acknowledged the cancellation request on - its already signaled CancellationToken by throwing an - OperationCanceledException2 that bears the same - CancellationToken. - - - - - Returns true if this task has a cancellation token and it was signaled. - To be used internally in execute entry codepaths. - - - - - This internal property provides access to the CancellationToken that was set on the task - when it was constructed. - - - - - Gets whether this threw an OperationCanceledException2 while its CancellationToken was signaled. - - - - - Gets whether this Task has completed. - - - will return true when the Task is in one of the three - final states: RanToCompletion, - Faulted, or - Canceled. - - - - - Checks whether this task has been disposed. - - - - - Gets the TaskCreationOptions used - to create this task. - - - - - Gets a that can be used to wait for the task to - complete. - - - Using the wait functionality provided by - should be preferred over using for similar - functionality. - - - The has been disposed. - - - - - Gets the state object supplied when the Task was created, - or null if none was supplied. - - - - - Gets an indication of whether the asynchronous operation completed synchronously. - - true if the asynchronous operation completed synchronously; otherwise, false. - - - - Provides access to the TaskScheduler responsible for executing this Task. - - - - - Provides access to factory methods for creating and instances. - - - The factory returned from is a default instance - of , as would result from using - the default constructor on TaskFactory. - - - - - Provides an event that can be used to wait for completion. - Only called by Wait*(), which means that we really do need to instantiate a completion event. - - - - - Determines whether this is the root task of a self replicating group. - - - - - Determines whether the task is a replica itself. - - - - - The property formerly known as IsFaulted. - - - - - Gets whether the completed due to an unhandled exception. - - - If is true, the Task's will be equal to - TaskStatus.Faulted, and its - property will be non-null. - - - - - Checks whether the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag is set, - This will only be used by the implicit wait to prevent double throws - - - - - - Checks whether the body was ever invoked. Used by task scheduler code to verify custom schedulers actually ran the task. - - - - - A structure to hold continuation information. - - - - - Constructs a new continuation structure. - - The task to be activated. - The continuation options. - The scheduler to use for the continuation. - - - - Invokes the continuation for the target completion task. - - The completed task. - Whether the continuation can be inlined. - - - - Initializes a new with the specified function. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - - The argument is null. - - - - - Initializes a new with the specified function. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - The to be assigned to this task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified function and creation options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified function and creation options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified function and state. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the action. - - The argument is null. - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - The to be assigned to the new task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - The to be assigned to the new task. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Creates a new future object. - - The parent task for this future. - A function that yields the future value. - The task scheduler which will be used to execute the future. - The CancellationToken for the task. - Options to control the future's behavior. - Internal options to control the future's behavior. - The argument specifies - a SelfReplicating , which is illegal."/>. - - - - Creates a new future object. - - The parent task for this future. - An object containing data to be used by the action; may be null. - A function that yields the future value. - The CancellationToken for the task. - The task scheduler which will be used to execute the future. - Options to control the future's behavior. - Internal options to control the future's behavior. - The argument specifies - a SelfReplicating , which is illegal."/>. - - - - Evaluates the value selector of the Task which is passed in as an object and stores the result. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new task. - A new continuation . - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The , when executed, should return a . This task's completion state will be transferred to the task returned - from the ContinueWith call. - - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be passed as - an argument this completed task. - - The that will be assigned to the new task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The , when executed, should return a . - This task's completion state will be transferred to the task returned from the - ContinueWith call. - - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Gets the result value of this . - - - The get accessor for this property ensures that the asynchronous operation is complete before - returning. Once the result of the computation is available, it is stored and will be returned - immediately on later calls to . - - - - - Provides access to factory methods for creating instances. - - - The factory returned from is a default instance - of , as would result from using - the default constructor on the factory type. - - - - - Provides support for creating and scheduling - Task{TResult} objects. - - The type of the results that are available though - the Task{TResult} objects that are associated with - the methods in this class. - - - There are many common patterns for which tasks are relevant. The - class encodes some of these patterns into methods that pick up default settings, which are - configurable through its constructors. - - - A default instance of is available through the - Task{TResult}.Factory property. - - - - - - Initializes a instance with the default configuration. - - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the default configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The - TaskScheduler to use to schedule any tasks created with this TaskFactory{TResult}. A null value - indicates that the current TaskScheduler should be used. - - - With this constructor, the - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to , unless it's null, in which case the property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory{TResult}. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory{TResult}. - - - The exception that is thrown when the - argument or the - argument specifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory{TResult}. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory{TResult}. - - - The default - TaskScheduler to use to schedule any Tasks created with this TaskFactory{TResult}. A null value - indicates that TaskScheduler.Current should be used. - - - The exception that is thrown when the - argument or the - argumentspecifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to - , unless it's null, in which case the property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new task. - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The that will be assigned to the new task. - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Gets the default CancellationToken of this - TaskFactory. - - - This property returns the default that will be assigned to all - tasks created by this factory unless another CancellationToken value is explicitly specified - during the call to the factory methods. - - - - - Gets the TaskScheduler of this - TaskFactory{TResult}. - - - This property returns the default scheduler for this factory. It will be used to schedule all - tasks unless another scheduler is explicitly specified during calls to this factory's methods. - If null, TaskScheduler.Current - will be used. - - - - - Gets the TaskCreationOptions - value of this TaskFactory{TResult}. - - - This property returns the default creation options for this factory. They will be used to create all - tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Gets the TaskContinuationOptions - value of this TaskFactory{TResult}. - - - This property returns the default continuation options for this factory. They will be used to create - all continuation tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Represents the current stage in the lifecycle of a . - - - - - The task has been initialized but has not yet been scheduled. - - - - - The task is waiting to be activated and scheduled internally by the .NET Framework infrastructure. - - - - - The task has been scheduled for execution but has not yet begun executing. - - - - - The task is running but has not yet completed. - - - - - The task has finished executing and is implicitly waiting for - attached child tasks to complete. - - - - - The task completed execution successfully. - - - - - The task acknowledged cancellation by throwing an OperationCanceledException2 with its own CancellationToken - while the token was in signaled state, or the task's CancellationToken was already signaled before the - task started executing. - - - - - The task completed due to an unhandled exception. - - - - - Specifies flags that control optional behavior for the creation and execution of tasks. - - - - - Specifies that the default behavior should be used. - - - - - A hint to a TaskScheduler to schedule a - task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to - be run sooner, and tasks scheduled later will be more likely to be run later. - - - - - Specifies that a task will be a long-running, course-grained operation. It provides a hint to the - TaskScheduler that oversubscription may be - warranted. - - - - - Specifies that a task is attached to a parent in the task hierarchy. - - - - - Task creation flags which are only used internally. - - - - Specifies "No internal task options" - - - Used to filter out internal vs. public task creation options. - - - Specifies that the task will be queued by the runtime before handing it over to the user. - This flag will be used to skip the cancellationtoken registration step, which is only meant for unstarted tasks. - - - - Specifies flags that control optional behavior for the creation and execution of continuation tasks. - - - - - Default = "Continue on any, no task options, run asynchronously" - Specifies that the default behavior should be used. Continuations, by default, will - be scheduled when the antecedent task completes, regardless of the task's final TaskStatus. - - - - - A hint to a TaskScheduler to schedule a - task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to - be run sooner, and tasks scheduled later will be more likely to be run later. - - - - - Specifies that a task will be a long-running, course-grained operation. It provides - a hint to the TaskScheduler that - oversubscription may be warranted. - - - - - Specifies that a task is attached to a parent in the task hierarchy. - - - - - Specifies that the continuation task should not be scheduled if its antecedent ran to completion. - This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should not be scheduled if its antecedent threw an unhandled - exception. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should not be scheduled if its antecedent was canceled. This - option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent ran to - completion. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent threw an - unhandled exception. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent was canceled. - This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be executed synchronously. With this option - specified, the continuation will be run on the same thread that causes the antecedent task to - transition into its final state. If the antecedent is already complete when the continuation is - created, the continuation will run on the thread creating the continuation. Only very - short-running continuations should be executed synchronously. - - - - - Represents an exception used to communicate task cancellation. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the - class with a specified error message and a reference to the inner exception that is the cause of - this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - - Initializes a new instance of the class - with a reference to the that has been canceled. - - A task that has been canceled. - - - - Gets the task associated with this exception. - - - It is permissible for no Task to be associated with a - , in which case - this property will return null. - - - - - Represents the producer side of a unbound to a - delegate, providing access to the consumer side through the property. - - - - It is often the case that a is desired to - represent another asynchronous operation. - TaskCompletionSource is provided for this purpose. It enables - the creation of a task that can be handed out to consumers, and those consumers can use the members - of the task as they would any other. However, unlike most tasks, the state of a task created by a - TaskCompletionSource is controlled explicitly by the methods on TaskCompletionSource. This enables the - completion of the external asynchronous operation to be propagated to the underlying Task. The - separation also ensures that consumers are not able to transition the state without access to the - corresponding TaskCompletionSource. - - - All members of are thread-safe - and may be used from multiple threads concurrently. - - - The type of the result value assocatied with this . - - - - Creates a . - - - - - Creates a - with the specified options. - - - The created - by this instance and accessible through its property - will be instantiated using the specified . - - The options to use when creating the underlying - . - - The represent options invalid for use - with a . - - - - - Creates a - with the specified state. - - The state to use as the underlying - 's AsyncState. - - - - Creates a with - the specified state and options. - - The options to use when creating the underlying - . - The state to use as the underlying - 's AsyncState. - - The represent options invalid for use - with a . - - - - - Attempts to transition the underlying - into the - Faulted - state. - - The exception to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The argument is null. - The was disposed. - - - - Attempts to transition the underlying - into the - Faulted - state. - - The collection of exceptions to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The argument is null. - There are one or more null elements in . - The collection is empty. - The was disposed. - - - - Transitions the underlying - into the - Faulted - state. - - The exception to bind to this . - The argument is null. - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - Faulted - state. - - The collection of exceptions to bind to this . - The argument is null. - There are one or more null elements in . - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Attempts to transition the underlying - into the - RanToCompletion - state. - - The result value to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - RanToCompletion - state. - - The result value to bind to this . - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - Canceled - state. - - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Attempts to transition the underlying - into the - Canceled - state. - - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Gets the created - by this . - - - This property enables a consumer access to the that is controlled by this instance. - The , , - , and - methods (and their "Try" variants) on this instance all result in the relevant state - transitions on this underlying Task. - - - - - An exception holder manages a list of exceptions for one particular task. - It offers the ability to aggregate, but more importantly, also offers intrinsic - support for propagating unhandled exceptions that are never observed. It does - this by aggregating and throwing if the holder is ever GC'd without the holder's - contents ever having been requested (e.g. by a Task.Wait, Task.get_Exception, etc). - - - - - Creates a new holder; it will be registered for finalization. - - The task this holder belongs to. - - - - A finalizer that repropagates unhandled exceptions. - - - - - Add an exception to the internal list. This will ensure the holder is - in the proper state (handled/unhandled) depending on the list's contents. - - An exception object (either an Exception or an - IEnumerable{Exception}) to add to the list. - - - - A private helper method that ensures the holder is considered - unhandled, i.e. it is registered for finalization. - - - - - A private helper method that ensures the holder is considered - handled, i.e. it is not registered for finalization. - - Whether this is called from the finalizer thread. - - - - Allocates a new aggregate exception and adds the contents of the list to - it. By calling this method, the holder assumes exceptions to have been - "observed", such that the finalization check will be subsequently skipped. - - Whether this is being called from a finalizer. - An extra exception to be included (optionally). - The aggregate exception to throw. - - - - Provides a set of static (Shared in Visual Basic) methods for working with specific kinds of - instances. - - - - - Creates a proxy Task that represents the - asynchronous operation of a Task{Task}. - - - It is often useful to be able to return a Task from a - Task{TResult}, where the inner Task represents work done as part of the outer Task{TResult}. However, - doing so results in a Task{Task}, which, if not dealt with carefully, could produce unexpected behavior. Unwrap - solves this problem by creating a proxy Task that represents the entire asynchronous operation of such a Task{Task}. - - The Task{Task} to unwrap. - The exception that is thrown if the - argument is null. - A Task that represents the asynchronous operation of the provided Task{Task}. - - - - Creates a proxy Task{TResult} that represents the - asynchronous operation of a Task{Task{TResult}}. - - - It is often useful to be able to return a Task{TResult} from a Task{TResult}, where the inner Task{TResult} - represents work done as part of the outer Task{TResult}. However, doing so results in a Task{Task{TResult}}, - which, if not dealt with carefully, could produce unexpected behavior. Unwrap solves this problem by - creating a proxy Task{TResult} that represents the entire asynchronous operation of such a Task{Task{TResult}}. - - The Task{Task{TResult}} to unwrap. - The exception that is thrown if the - argument is null. - A Task{TResult} that represents the asynchronous operation of the provided Task{Task{TResult}}. /// Unwraps a Task that returns another Task. - - - - Provides support for creating and scheduling - Tasks. - - - - There are many common patterns for which tasks are relevant. The - class encodes some of these patterns into methods that pick up default settings, which are - configurable through its constructors. - - - A default instance of is available through the - Task.Factory property. - - - - - - Initializes a instance with the default configuration. - - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The - TaskScheduler to use to schedule any tasks created with this TaskFactory. A null value - indicates that the current TaskScheduler should be used. - - - With this constructor, the - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to , unless it's null, in which case the property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory. - - - The exception that is thrown when the - argument or the - argument specifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory. - - - The default - TaskScheduler to use to schedule any Tasks created with this TaskFactory. A null value - indicates that TaskScheduler.Current should be used. - - - The exception that is thrown when the - argument or the - argumentspecifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to - , unless it's null, in which case the property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The started Task. - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors - and then calling - Start to schedule it for execution. However, - unless creation and scheduling must be separated, StartNew is the recommended - approach for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The that will be assigned to the new task. - The started Task. - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors - and then calling - Start to schedule it for execution. However, - unless creation and scheduling must be separated, StartNew is the recommended - approach for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The that will be assigned to the new - A TaskCreationOptions value that controls the behavior of the - created - Task. - The TaskScheduler - that is used to schedule the created Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The started Task. - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The that will be assigned to the new - The started Task. - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The TaskScheduler - that is used to schedule the created Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the asynchronous - operation. - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the asynchronous - operation. - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the asynchronous - operation. - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Check validity of options passed to FromAsync method - - The options to be validated. - determines type of FromAsync method that called this method - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Gets the default CancellationToken of this - TaskFactory. - - - This property returns the default that will be assigned to all - tasks created by this factory unless another CancellationToken value is explicitly specified - during the call to the factory methods. - - - - - Gets the TaskScheduler of this - TaskFactory. - - - This property returns the default scheduler for this factory. It will be used to schedule all - tasks unless another scheduler is explicitly specified during calls to this factory's methods. - If null, TaskScheduler.Current - will be used. - - - - - Gets the TaskCreationOptions - value of this TaskFactory. - - - This property returns the default creation options for this factory. They will be used to create all - tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Gets the TaskContinuationOptions - value of this TaskFactory. - - - This property returns the default continuation options for this factory. They will be used to create - all continuation tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Represents an abstract scheduler for tasks. - - - - TaskScheduler acts as the extension point for all - pluggable scheduling logic. This includes mechanisms such as how to schedule a task for execution, and - how scheduled tasks should be exposed to debuggers. - - - All members of the abstract type are thread-safe - and may be used from multiple threads concurrently. - - - - - - Queues a Task to the scheduler. - - - - A class derived from TaskScheduler - implements this method to accept tasks being scheduled on the scheduler. - A typical implementation would store the task in an internal data structure, which would - be serviced by threads that would execute those tasks at some time in the future. - - - This method is only meant to be called by the .NET Framework and - should not be called directly by the derived class. This is necessary - for maintaining the consistency of the system. - - - The Task to be queued. - The argument is null. - - - - Determines whether the provided Task - can be executed synchronously in this call, and if it can, executes it. - - - - A class derived from TaskScheduler implements this function to - support inline execution of a task on a thread that initiates a wait on that task object. Inline - execution is optional, and the request may be rejected by returning false. However, better - scalability typically results the more tasks that can be inlined, and in fact a scheduler that - inlines too little may be prone to deadlocks. A proper implementation should ensure that a - request executing under the policies guaranteed by the scheduler can successfully inline. For - example, if a scheduler uses a dedicated thread to execute tasks, any inlining requests from that - thread should succeed. - - - If a scheduler decides to perform the inline execution, it should do so by calling to the base - TaskScheduler's - TryExecuteTask method with the provided task object, propagating - the return value. It may also be appropriate for the scheduler to remove an inlined task from its - internal data structures if it decides to honor the inlining request. Note, however, that under - some circumstances a scheduler may be asked to inline a task that was not previously provided to - it with the method. - - - The derived scheduler is responsible for making sure that the calling thread is suitable for - executing the given task as far as its own scheduling and execution policies are concerned. - - - The Task to be - executed. - A Boolean denoting whether or not task has previously been - queued. If this parameter is True, then the task may have been previously queued (scheduled); if - False, then the task is known not to have been queued, and this call is being made in order to - execute the task inline without queueing it. - A Boolean value indicating whether the task was executed inline. - The argument is - null. - The was already - executed. - - - - Generates an enumerable of Task instances - currently queued to the scheduler waiting to be executed. - - - - A class derived from implements this method in order to support - integration with debuggers. This method will only be invoked by the .NET Framework when the - debugger requests access to the data. The enumerable returned will be traversed by debugging - utilities to access the tasks currently queued to this scheduler, enabling the debugger to - provide a representation of this information in the user interface. - - - It is important to note that, when this method is called, all other threads in the process will - be frozen. Therefore, it's important to avoid synchronization with other threads that may lead to - blocking. If synchronization is necessary, the method should prefer to throw a - than to block, which could cause a debugger to experience delays. Additionally, this method and - the enumerable returned must not modify any globally visible state. - - - The returned enumerable should never be null. If there are currently no queued tasks, an empty - enumerable should be returned instead. - - - For developers implementing a custom debugger, this method shouldn't be called directly, but - rather this functionality should be accessed through the internal wrapper method - GetScheduledTasksForDebugger: - internal Task[] GetScheduledTasksForDebugger(). This method returns an array of tasks, - rather than an enumerable. In order to retrieve a list of active schedulers, a debugger may use - another internal method: internal static TaskScheduler[] GetTaskSchedulersForDebugger(). - This static method returns an array of all active TaskScheduler instances. - GetScheduledTasksForDebugger then may be used on each of these scheduler instances to retrieve - the list of scheduled tasks for each. - - - An enumerable that allows traversal of tasks currently queued to this scheduler. - - - This scheduler is unable to generate a list of queued tasks at this time. - - - - - Retrieves some thread static state that can be cached and passed to multiple - TryRunInline calls, avoiding superflous TLS fetches. - - A bag of TLS state (or null if none exists). - - - - Attempts to execute the target task synchronously. - - The task to run. - True if the task may have been previously queued, - false if the task was absolutely not previously queued. - The state retrieved from GetThreadStatics - True if it ran, false otherwise. - - - - Attempts to dequeue a Task that was previously queued to - this scheduler. - - The Task to be dequeued. - A Boolean denoting whether the argument was successfully dequeued. - The argument is null. - - - - Notifies the scheduler that a work item has made progress. - - - - - Initializes the . - - - - - Frees all resources associated with this scheduler. - - - - - Creates a - associated with the current . - - - All Task instances queued to - the returned scheduler will be executed through a call to the - Post method - on that context. - - - A associated with - the current SynchronizationContext, as - determined by SynchronizationContext.Current. - - - The current SynchronizationContext may not be used as a TaskScheduler. - - - - - Attempts to execute the provided Task - on this scheduler. - - - - Scheduler implementations are provided with Task - instances to be executed through either the method or the - method. When the scheduler deems it appropriate to run the - provided task, should be used to do so. TryExecuteTask handles all - aspects of executing a task, including action invocation, exception handling, state management, - and lifecycle control. - - - must only be used for tasks provided to this scheduler by the .NET - Framework infrastructure. It should not be used to execute arbitrary tasks obtained through - custom mechanisms. - - - - A Task object to be executed. - - The is not associated with this scheduler. - - A Boolean that is true if was successfully executed, false if it - was not. A common reason for execution failure is that the task had previously been executed or - is in the process of being executed by another thread. - - - - Provides an array of all queued Task instances - for the debugger. - - - The returned array is populated through a call to . - Note that this function is only meant to be invoked by a debugger remotely. - It should not be called by any other codepaths. - - An array of Task instances. - - This scheduler is unable to generate a list of queued tasks at this time. - - - - - Provides an array of all active TaskScheduler - instances for the debugger. - - - This function is only meant to be invoked by a debugger remotely. - It should not be called by any other codepaths. - - An array of TaskScheduler instances. - - - - Registers a new TaskScheduler instance in the global collection of schedulers. - - - - - Removes a TaskScheduler instance from the global collection of schedulers. - - - - - Indicates the maximum concurrency level this - is able to support. - - - - - Indicates whether this is a custom scheduler, in which case the safe code paths will be taken upon task entry - using a CAS to transition from queued state to executing. - - - - - Gets the default TaskScheduler instance. - - - - - Gets the TaskScheduler - associated with the currently executing task. - - - When not called from within a task, will return the scheduler. - - - - - Gets the unique ID for this . - - - - - Occurs when a faulted 's unobserved exception is about to trigger exception escalation - policy, which, by default, would terminate the process. - - - This AppDomain-wide event provides a mechanism to prevent exception - escalation policy (which, by default, terminates the process) from triggering. - Each handler is passed a - instance, which may be used to examine the exception and to mark it as observed. - - - - - Nested class that provides debugger view for TaskScheduler - - - - Default thread pool scheduler. - - - - A TaskScheduler implementation that executes all tasks queued to it through a call to - on the - that its associated with. The default constructor for this class binds to the current - - - - - Constructs a SynchronizationContextTaskScheduler associated with - - This constructor expects to be set. - - - - Implemetation of for this scheduler class. - - Simply posts the tasks to be executed on the associated . - - - - - - Implementation of for this scheduler class. - - The task will be executed inline only if the call happens within - the associated . - - - - - - - Implementes the property for - this scheduler class. - - By default it returns 1, because a based - scheduler only supports execution on a single thread. - - - - - Provides data for the event that is raised when a faulted 's - exception goes unobserved. - - - The Exception property is used to examine the exception without marking it - as observed, whereas the method is used to mark the exception - as observed. Marking the exception as observed prevents it from triggering exception escalation policy - which, by default, terminates the process. - - - - - Initializes a new instance of the class - with the unobserved exception. - - The Exception that has gone unobserved. - - - - Marks the as "observed," thus preventing it - from triggering exception escalation policy which, by default, terminates the process. - - - - - Gets whether this exception has been marked as "observed." - - - - - The Exception that went unobserved. - - - - - Represents an exception used to communicate an invalid operation by a - . - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the - class using the default error message and a reference to the inner exception that is the cause of - this exception. - - The exception that is the cause of the current exception. - - - - Initializes a new instance of the - class with a specified error message and a reference to the inner exception that is the cause of - this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/ensureRedirect.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl4+win8/ensureRedirect.xml deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.IO.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.IO.dll deleted file mode 100644 index 32dd41b78..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.IO.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.IO.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.IO.xml deleted file mode 100644 index e83273427..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.IO.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - System.IO - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Found invalid data while decoding.. - - - - - The exception that is thrown when a data stream is in an invalid format. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a reference to the inner exception that is the cause of this exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. If the parameter is not null, the current exception is raised in a catch block that handles the inner exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.Runtime.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.Runtime.dll deleted file mode 100644 index 118fcce20..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.Runtime.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.Runtime.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.Runtime.xml deleted file mode 100644 index 93cb00d74..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.Runtime.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - System.Runtime - - - - Defines a provider for progress updates. - The type of progress update value. - - - Reports a progress update. - The value of the updated progress. - - - Identities the async state machine type for this method. - - - Identities the state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - Gets the type that implements the state machine. - - - Initializes the attribute. - The type that implements the state machine. - - - - Allows you to obtain the method or property name of the caller to the method. - - - - - Allows you to obtain the line number in the source file at which the method is called. - - - - - Allows you to obtain the full path of the source file that contains the caller. - This is the file path at the time of compile. - - - - Identities the iterator state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.Threading.Tasks.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.Threading.Tasks.dll deleted file mode 100644 index a60ab2657..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.Threading.Tasks.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.Threading.Tasks.xml deleted file mode 100644 index b47921e5d..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/System.Threading.Tasks.xml +++ /dev/null @@ -1,475 +0,0 @@ - - - - System.Threading.Tasks - - - - Holds state related to the builder's IAsyncStateMachine. - This is a mutable struct. Be very delicate with it. - - - A reference to the heap-allocated state machine object associated with this builder. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument is null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - - Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method. - On first invocation, the supplied state machine will be boxed. - - Specifies the type of the method builder used. - Specifies the type of the state machine used. - The builder. - The state machine. - An Action to provide to the awaiter. - - - Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext. - - - The context with which to run MoveNext. - - - The state machine whose MoveNext method should be invoked. - - - Initializes the runner. - The context with which to run MoveNext. - - - Invokes MoveNext under the provided context. - - - Cached delegate used with ExecutionContext.Run. - - - Invokes the MoveNext method on the supplied IAsyncStateMachine. - The IAsyncStateMachine machine instance. - - - Provides a base class used to cache tasks of a specific return type. - Specifies the type of results the cached tasks return. - - - - A singleton cache for this result type. - This may be null if there are no cached tasks for this TResult. - - - - Creates a non-disposable task. - The result for the task. - The cacheable task. - - - Creates a cache. - A task cache for this result type. - - - Gets a cached task if one exists. - The result for which we want a cached task. - A cached task if one exists; otherwise, null. - - - Provides a cache for Boolean tasks. - - - A true task. - - - A false task. - - - Gets a cached task for the Boolean result. - true or false - A cached task for the Boolean result. - - - Provides a cache for zero Int32 tasks. - - - The minimum value, inclusive, for which we want a cached task. - - - The maximum value, exclusive, for which we want a cached task. - - - The cache of Task{Int32}. - - - Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). - - - Gets a cached task for the zero Int32 result. - The integer value - A cached task for the Int32 result or null if not cached. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - Represents an asynchronous method builder. - - - A cached VoidTaskResult task used for builders that complete synchronously. - - - The generic builder object to which this non-generic instance delegates. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state. - - The builder is not initialized. - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - - Gets the for this builder. - The representing the builder's asynchronous operation. - The builder is not initialized. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - A cached task for default(TResult). - - - State related to the IAsyncStateMachine. - - - The lazily-initialized task. - Must be named m_task for debugger step-over to work correctly. - - - The lazily-initialized task completion source. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state with the specified result. - - The result to use to complete the task. - The task has already completed. - - - - Completes the builder by using either the supplied completed task, or by completing - the builder's previously accessed task using default(TResult). - - A task already completed with the value default(TResult). - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - This should only be invoked from within an asynchronous method, - and only by the debugger. - - - - - Gets a task for the specified result. This will either - be a cached or new task, never null. - - The result for which we need a task. - The completed task containing the result. - - - Gets the lazily-initialized TaskCompletionSource. - - - Gets the for this builder. - The representing the builder's asynchronous operation. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - - Provides a builder for asynchronous methods that return void. - This type is intended for compiler use only. - - - - The synchronization context associated with this operation. - - - State related to the IAsyncStateMachine. - - - An object used by the debugger to uniquely identify this builder. Lazily initialized. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Registers with UnobservedTaskException to suppress exception crashing. - - - Non-zero if PreventUnobservedTaskExceptions has already been invoked. - - - Initializes a new . - The initialized . - - - Initializes the . - The synchronizationContext associated with this operation. This may be null. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument was null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - Completes the method builder successfully. - - - Faults the method builder with an exception. - The exception that is the cause of this fault. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - - - Notifies the current synchronization context that the operation completed. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger and only in a single-threaded manner. - - - - - Represents state machines generated for asynchronous methods. - This type is intended for compiler use only. - - - - Moves the state machine to its next state. - - - Configures the state machine with a heap-allocated replica. - The heap-allocated replica. - - - - Represents an awaiter used to schedule continuations when an await operation completes. - - - - - Represents an operation that will schedule continuations when the operation completes. - - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information. - - - Used with Task(of void) - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/ensureRedirect.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+sl5+win8+wp8+wpa81/ensureRedirect.xml deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.IO.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.IO.dll deleted file mode 100644 index 32dd41b78..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.IO.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.IO.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.IO.xml deleted file mode 100644 index e83273427..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.IO.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - System.IO - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Found invalid data while decoding.. - - - - - The exception that is thrown when a data stream is in an invalid format. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a reference to the inner exception that is the cause of this exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. If the parameter is not null, the current exception is raised in a catch block that handles the inner exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.Runtime.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.Runtime.dll deleted file mode 100644 index 118fcce20..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.Runtime.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.Runtime.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.Runtime.xml deleted file mode 100644 index 93cb00d74..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.Runtime.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - System.Runtime - - - - Defines a provider for progress updates. - The type of progress update value. - - - Reports a progress update. - The value of the updated progress. - - - Identities the async state machine type for this method. - - - Identities the state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - Gets the type that implements the state machine. - - - Initializes the attribute. - The type that implements the state machine. - - - - Allows you to obtain the method or property name of the caller to the method. - - - - - Allows you to obtain the line number in the source file at which the method is called. - - - - - Allows you to obtain the full path of the source file that contains the caller. - This is the file path at the time of compile. - - - - Identities the iterator state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.Threading.Tasks.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.Threading.Tasks.dll deleted file mode 100644 index a60ab2657..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.Threading.Tasks.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.Threading.Tasks.xml deleted file mode 100644 index b47921e5d..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/System.Threading.Tasks.xml +++ /dev/null @@ -1,475 +0,0 @@ - - - - System.Threading.Tasks - - - - Holds state related to the builder's IAsyncStateMachine. - This is a mutable struct. Be very delicate with it. - - - A reference to the heap-allocated state machine object associated with this builder. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument is null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - - Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method. - On first invocation, the supplied state machine will be boxed. - - Specifies the type of the method builder used. - Specifies the type of the state machine used. - The builder. - The state machine. - An Action to provide to the awaiter. - - - Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext. - - - The context with which to run MoveNext. - - - The state machine whose MoveNext method should be invoked. - - - Initializes the runner. - The context with which to run MoveNext. - - - Invokes MoveNext under the provided context. - - - Cached delegate used with ExecutionContext.Run. - - - Invokes the MoveNext method on the supplied IAsyncStateMachine. - The IAsyncStateMachine machine instance. - - - Provides a base class used to cache tasks of a specific return type. - Specifies the type of results the cached tasks return. - - - - A singleton cache for this result type. - This may be null if there are no cached tasks for this TResult. - - - - Creates a non-disposable task. - The result for the task. - The cacheable task. - - - Creates a cache. - A task cache for this result type. - - - Gets a cached task if one exists. - The result for which we want a cached task. - A cached task if one exists; otherwise, null. - - - Provides a cache for Boolean tasks. - - - A true task. - - - A false task. - - - Gets a cached task for the Boolean result. - true or false - A cached task for the Boolean result. - - - Provides a cache for zero Int32 tasks. - - - The minimum value, inclusive, for which we want a cached task. - - - The maximum value, exclusive, for which we want a cached task. - - - The cache of Task{Int32}. - - - Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). - - - Gets a cached task for the zero Int32 result. - The integer value - A cached task for the Int32 result or null if not cached. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - Represents an asynchronous method builder. - - - A cached VoidTaskResult task used for builders that complete synchronously. - - - The generic builder object to which this non-generic instance delegates. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state. - - The builder is not initialized. - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - - Gets the for this builder. - The representing the builder's asynchronous operation. - The builder is not initialized. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - A cached task for default(TResult). - - - State related to the IAsyncStateMachine. - - - The lazily-initialized task. - Must be named m_task for debugger step-over to work correctly. - - - The lazily-initialized task completion source. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state with the specified result. - - The result to use to complete the task. - The task has already completed. - - - - Completes the builder by using either the supplied completed task, or by completing - the builder's previously accessed task using default(TResult). - - A task already completed with the value default(TResult). - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - This should only be invoked from within an asynchronous method, - and only by the debugger. - - - - - Gets a task for the specified result. This will either - be a cached or new task, never null. - - The result for which we need a task. - The completed task containing the result. - - - Gets the lazily-initialized TaskCompletionSource. - - - Gets the for this builder. - The representing the builder's asynchronous operation. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - - Provides a builder for asynchronous methods that return void. - This type is intended for compiler use only. - - - - The synchronization context associated with this operation. - - - State related to the IAsyncStateMachine. - - - An object used by the debugger to uniquely identify this builder. Lazily initialized. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Registers with UnobservedTaskException to suppress exception crashing. - - - Non-zero if PreventUnobservedTaskExceptions has already been invoked. - - - Initializes a new . - The initialized . - - - Initializes the . - The synchronizationContext associated with this operation. This may be null. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument was null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - Completes the method builder successfully. - - - Faults the method builder with an exception. - The exception that is the cause of this fault. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - - - Notifies the current synchronization context that the operation completed. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger and only in a single-threaded manner. - - - - - Represents state machines generated for asynchronous methods. - This type is intended for compiler use only. - - - - Moves the state machine to its next state. - - - Configures the state machine with a heap-allocated replica. - The heap-allocated replica. - - - - Represents an awaiter used to schedule continuations when an await operation completes. - - - - - Represents an operation that will schedule continuations when the operation completes. - - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information. - - - Used with Task(of void) - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/ensureRedirect.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8+wp8+wpa81/ensureRedirect.xml deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.IO.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.IO.dll deleted file mode 100644 index 26cd551c3..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.IO.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.IO.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.IO.xml deleted file mode 100644 index 865aa1a4f..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.IO.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - System.IO - - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.Runtime.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.Runtime.dll deleted file mode 100644 index 118fcce20..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.Runtime.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.Runtime.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.Runtime.xml deleted file mode 100644 index 93cb00d74..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.Runtime.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - System.Runtime - - - - Defines a provider for progress updates. - The type of progress update value. - - - Reports a progress update. - The value of the updated progress. - - - Identities the async state machine type for this method. - - - Identities the state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - Gets the type that implements the state machine. - - - Initializes the attribute. - The type that implements the state machine. - - - - Allows you to obtain the method or property name of the caller to the method. - - - - - Allows you to obtain the line number in the source file at which the method is called. - - - - - Allows you to obtain the full path of the source file that contains the caller. - This is the file path at the time of compile. - - - - Identities the iterator state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.Threading.Tasks.dll b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.Threading.Tasks.dll deleted file mode 100644 index a60ab2657..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.Threading.Tasks.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.Threading.Tasks.xml deleted file mode 100644 index b47921e5d..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/System.Threading.Tasks.xml +++ /dev/null @@ -1,475 +0,0 @@ - - - - System.Threading.Tasks - - - - Holds state related to the builder's IAsyncStateMachine. - This is a mutable struct. Be very delicate with it. - - - A reference to the heap-allocated state machine object associated with this builder. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument is null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - - Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method. - On first invocation, the supplied state machine will be boxed. - - Specifies the type of the method builder used. - Specifies the type of the state machine used. - The builder. - The state machine. - An Action to provide to the awaiter. - - - Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext. - - - The context with which to run MoveNext. - - - The state machine whose MoveNext method should be invoked. - - - Initializes the runner. - The context with which to run MoveNext. - - - Invokes MoveNext under the provided context. - - - Cached delegate used with ExecutionContext.Run. - - - Invokes the MoveNext method on the supplied IAsyncStateMachine. - The IAsyncStateMachine machine instance. - - - Provides a base class used to cache tasks of a specific return type. - Specifies the type of results the cached tasks return. - - - - A singleton cache for this result type. - This may be null if there are no cached tasks for this TResult. - - - - Creates a non-disposable task. - The result for the task. - The cacheable task. - - - Creates a cache. - A task cache for this result type. - - - Gets a cached task if one exists. - The result for which we want a cached task. - A cached task if one exists; otherwise, null. - - - Provides a cache for Boolean tasks. - - - A true task. - - - A false task. - - - Gets a cached task for the Boolean result. - true or false - A cached task for the Boolean result. - - - Provides a cache for zero Int32 tasks. - - - The minimum value, inclusive, for which we want a cached task. - - - The maximum value, exclusive, for which we want a cached task. - - - The cache of Task{Int32}. - - - Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). - - - Gets a cached task for the zero Int32 result. - The integer value - A cached task for the Int32 result or null if not cached. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - Represents an asynchronous method builder. - - - A cached VoidTaskResult task used for builders that complete synchronously. - - - The generic builder object to which this non-generic instance delegates. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state. - - The builder is not initialized. - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - - Gets the for this builder. - The representing the builder's asynchronous operation. - The builder is not initialized. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - A cached task for default(TResult). - - - State related to the IAsyncStateMachine. - - - The lazily-initialized task. - Must be named m_task for debugger step-over to work correctly. - - - The lazily-initialized task completion source. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state with the specified result. - - The result to use to complete the task. - The task has already completed. - - - - Completes the builder by using either the supplied completed task, or by completing - the builder's previously accessed task using default(TResult). - - A task already completed with the value default(TResult). - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - This should only be invoked from within an asynchronous method, - and only by the debugger. - - - - - Gets a task for the specified result. This will either - be a cached or new task, never null. - - The result for which we need a task. - The completed task containing the result. - - - Gets the lazily-initialized TaskCompletionSource. - - - Gets the for this builder. - The representing the builder's asynchronous operation. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - - Provides a builder for asynchronous methods that return void. - This type is intended for compiler use only. - - - - The synchronization context associated with this operation. - - - State related to the IAsyncStateMachine. - - - An object used by the debugger to uniquely identify this builder. Lazily initialized. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Registers with UnobservedTaskException to suppress exception crashing. - - - Non-zero if PreventUnobservedTaskExceptions has already been invoked. - - - Initializes a new . - The initialized . - - - Initializes the . - The synchronizationContext associated with this operation. This may be null. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument was null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - Completes the method builder successfully. - - - Faults the method builder with an exception. - The exception that is the cause of this fault. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - - - Notifies the current synchronization context that the operation completed. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger and only in a single-threaded manner. - - - - - Represents state machines generated for asynchronous methods. - This type is intended for compiler use only. - - - - Moves the state machine to its next state. - - - Configures the state machine with a heap-allocated replica. - The heap-allocated replica. - - - - Represents an awaiter used to schedule continuations when an await operation completes. - - - - - Represents an operation that will schedule continuations when the operation completes. - - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information. - - - Used with Task(of void) - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/ensureRedirect.xml b/packages/Microsoft.Bcl.1.1.10/lib/portable-net40+win8/ensureRedirect.xml deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net45+win8+wp8+wpa81/_._ b/packages/Microsoft.Bcl.1.1.10/lib/portable-net45+win8+wp8+wpa81/_._ deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net45+win8+wpa81/_._ b/packages/Microsoft.Bcl.1.1.10/lib/portable-net45+win8+wpa81/_._ deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net451+win81+wpa81/_._ b/packages/Microsoft.Bcl.1.1.10/lib/portable-net451+win81+wpa81/_._ deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-net451+win81/_._ b/packages/Microsoft.Bcl.1.1.10/lib/portable-net451+win81/_._ deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/portable-win81+wp81+wpa81/_._ b/packages/Microsoft.Bcl.1.1.10/lib/portable-win81+wp81+wpa81/_._ deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.IO.dll b/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.IO.dll deleted file mode 100644 index 32dd41b78..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.IO.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.IO.xml b/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.IO.xml deleted file mode 100644 index e83273427..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.IO.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - System.IO - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Found invalid data while decoding.. - - - - - The exception that is thrown when a data stream is in an invalid format. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a reference to the inner exception that is the cause of this exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. If the parameter is not null, the current exception is raised in a catch block that handles the inner exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.Runtime.dll b/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.Runtime.dll deleted file mode 100644 index 18e255b25..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.Runtime.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.Runtime.xml b/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.Runtime.xml deleted file mode 100644 index 53f5bef44..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.Runtime.xml +++ /dev/null @@ -1,860 +0,0 @@ - - - - System.Runtime - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Argument must be of type {0}.. - - - - - Looks up a localized string similar to The last element of an eight element tuple must be a Tuple.. - - - - - Defines methods to support the comparison of objects for structural equality. - - - - - Determines whether an object is structurally equal to the current instance. - - The object to compare with the current instance. - An object that determines whether the current instance and other are equal. - true if the two objects are equal; otherwise, false. - - - - Returns a hash code for the current instance. - - An object that computes the hash code of the current object. - The hash code for the current instance. - - - - Supports the structural comparison of collection objects. - - - - - Determines whether the current collection object precedes, occurs in the same position as, or follows another object in the sort order. - - The object to compare with the current instance. - An object that compares members of the current collection object with the corresponding members of other. - An integer that indicates the relationship of the current collection object to other. - - This instance and other are not the same type. - - - - - Encapsulates a method that has five parameters and returns a value of the type specified by the TResult parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - Defines a provider for progress updates. - The type of progress update value. - - - Reports a progress update. - The value of the updated progress. - - - Identities the async state machine type for this method. - - - Identities the state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - Gets the type that implements the state machine. - - - Initializes the attribute. - The type that implements the state machine. - - - - Allows you to obtain the method or property name of the caller to the method. - - - - - Allows you to obtain the line number in the source file at which the method is called. - - - - - Allows you to obtain the full path of the source file that contains the caller. - This is the file path at the time of compile. - - - - Identities the iterator state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - - Helper so we can call some tuple methods recursively without knowing the underlying types. - - - - - Provides static methods for creating tuple objects. - - - - - Creates a new 1-tuple, or singleton. - - The type of the only component of the tuple. - The value of the only component of the tuple. - A tuple whose value is (item1). - - - - Creates a new 3-tuple, or pair. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - An 2-tuple (pair) whose value is (item1, item2). - - - - Creates a new 3-tuple, or triple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - An 3-tuple (triple) whose value is (item1, item2, item3). - - - - Creates a new 4-tuple, or quadruple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - An 4-tuple (quadruple) whose value is (item1, item2, item3, item4). - - - - Creates a new 5-tuple, or quintuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - An 5-tuple (quintuple) whose value is (item1, item2, item3, item4, item5). - - - - Creates a new 6-tuple, or sextuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The type of the sixth component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - The value of the sixth component of the tuple. - An 6-tuple (sextuple) whose value is (item1, item2, item3, item4, item5, item6). - - - - Creates a new 7-tuple, or septuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The type of the sixth component of the tuple. - The type of the seventh component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - The value of the sixth component of the tuple. - The value of the seventh component of the tuple. - An 7-tuple (septuple) whose value is (item1, item2, item3, item4, item5, item6, item7). - - - - Creates a new 8-tuple, or octuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The type of the sixth component of the tuple. - The type of the seventh component of the tuple. - The type of the eighth component of the tuple. - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - The value of the sixth component of the tuple. - The value of the seventh component of the tuple. - The value of the eighth component of the tuple. - An 8-tuple (octuple) whose value is (item1, item2, item3, item4, item5, item6, item7, item8). - - - - Represents a 1-tuple, or singleton. - - The type of the tuple's only component. - - - - Initializes a new instance of the class. - - The value of the current tuple object's single component. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the tuple object's single component. - - - The value of the current tuple object's single component. - - - - - Represents an 2-tuple, or pair. - - The type of the first component of the tuple. - The type of the second component of the tuple. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Represents an 3-tuple, or triple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Gets the value of the current tuple object's third component. - - - The value of the current tuple object's third component. - - - - - Represents an 4-tuple, or quadruple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Gets the value of the current tuple object's third component. - - - The value of the current tuple object's third component. - - - - - Gets the value of the current tuple object's fourth component. - - - The value of the current tuple object's fourth component. - - - - - Represents an 5-tuple, or quintuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Gets the value of the current tuple object's third component. - - - The value of the current tuple object's third component. - - - - - Gets the value of the current tuple object's fourth component. - - - The value of the current tuple object's fourth component. - - - - - Gets the value of the current tuple object's fifth component. - - - The value of the current tuple object's fifth component. - - - - - Represents an 6-tuple, or sextuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The type of the sixth component of the tuple. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - The value of the sixth component of the tuple. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Gets the value of the current tuple object's third component. - - - The value of the current tuple object's third component. - - - - - Gets the value of the current tuple object's fourth component. - - - The value of the current tuple object's fourth component. - - - - - Gets the value of the current tuple object's fifth component. - - - The value of the current tuple object's fifth component. - - - - - Gets the value of the current tuple object's sixth component. - - - The value of the current tuple object's sixth component. - - - - - Represents an 7-tuple, or septuple. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The type of the sixth component of the tuple. - The type of the seventh component of the tuple. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - The value of the sixth component of the tuple. - The value of the seventh component of the tuple. - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Gets the value of the current tuple object's third component. - - - The value of the current tuple object's third component. - - - - - Gets the value of the current tuple object's fourth component. - - - The value of the current tuple object's fourth component. - - - - - Gets the value of the current tuple object's fifth component. - - - The value of the current tuple object's fifth component. - - - - - Gets the value of the current tuple object's sixth component. - - - The value of the current tuple object's sixth component. - - - - - Gets the value of the current tuple object's seventh component. - - - The value of the current tuple object's seventh component. - - - - - Represents an n-tuple, where n is 8 or greater. - - The type of the first component of the tuple. - The type of the second component of the tuple. - The type of the third component of the tuple. - The type of the fourth component of the tuple. - The type of the fifth component of the tuple. - The type of the sixth component of the tuple. - The type of the seventh component of the tuple. - Any generic Tuple object that defines the types of the tuple's remaining components. - - - - Initializes a new instance of the class. - - The value of the first component of the tuple. - The value of the second component of the tuple. - The value of the third component of the tuple. - The value of the fourth component of the tuple. - The value of the fifth component of the tuple. - The value of the sixth component of the tuple. - The value of the seventh component of the tuple. - Any generic Tuple object that contains the values of the tuple's remaining components. - - rest is not a generic Tuple object. - - - - - Returns a value that indicates whether the current tuple object is equal to a specified object. - - The object to compare with this instance. - true if the current instance is equal to the specified object; otherwise, false. - - - - Calculates the hash code for the current tuple object. - - A 32-bit signed integer hash code. - - - - Returns a string that represents the value of this tuple instance. - - The string representation of this tuple object. - - - - Gets the value of the current tuple object's first component. - - - The value of the current tuple object's first component. - - - - - Gets the value of the current tuple object's second component. - - - The value of the current tuple object's second component. - - - - - Gets the value of the current tuple object's third component. - - - The value of the current tuple object's third component. - - - - - Gets the value of the current tuple object's fourth component. - - - The value of the current tuple object's fourth component. - - - - - Gets the value of the current tuple object's fifth component. - - - The value of the current tuple object's fifth component. - - - - - Gets the value of the current tuple object's sixth component. - - - The value of the current tuple object's sixth component. - - - - - Gets the value of the current tuple object's seventh component. - - - The value of the current tuple object's seventh component. - - - - - Gets the current tuple object's remaining components. - - - The value of the current tuple object's remaining components. - - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.Threading.Tasks.dll b/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.Threading.Tasks.dll deleted file mode 100644 index a089d474d..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.Threading.Tasks.xml b/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.Threading.Tasks.xml deleted file mode 100644 index 6c770122e..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/System.Threading.Tasks.xml +++ /dev/null @@ -1,8969 +0,0 @@ - - - - System.Threading.Tasks - - - - Represents one or more errors that occur during application execution. - - is used to consolidate multiple failures into a single, throwable - exception object. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with - a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a specified error - message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - The argument - is null. - - - - Initializes a new instance of the class with - references to the inner exceptions that are the cause of this exception. - - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with - references to the inner exceptions that are the cause of this exception. - - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with a specified error - message and references to the inner exceptions that are the cause of this exception. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with a specified error - message and references to the inner exceptions that are the cause of this exception. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Allocates a new aggregate exception with the specified message and list of inner exceptions. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Returns the that is the root cause of this exception. - - - - - Invokes a handler on each contained by this . - - The predicate to execute for each exception. The predicate accepts as an - argument the to be processed and returns a Boolean to indicate - whether the exception was handled. - - Each invocation of the returns true or false to indicate whether the - was handled. After all invocations, if any exceptions went - unhandled, all unhandled exceptions will be put into a new - which will be thrown. Otherwise, the method simply returns. If any - invocations of the throws an exception, it will halt the processing - of any more exceptions and immediately propagate the thrown exception as-is. - - An exception contained by this was not handled. - The argument is - null. - - - - Flattens an instances into a single, new instance. - - A new, flattened . - - If any inner exceptions are themselves instances of - , this method will recursively flatten all of them. The - inner exceptions returned in the new - will be the union of all of the the inner exceptions from exception tree rooted at the provided - instance. - - - - - Creates and returns a string representation of the current . - - A string representation of the current exception. - - - - Gets a read-only collection of the instances that caused the - current exception. - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to One or more errors occurred.. - - - - - Looks up a localized string similar to An element of innerExceptions was null.. - - - - - Looks up a localized string similar to {0}{1}---> (Inner Exception #{2}) {3}{4}{5}. - - - - - Looks up a localized string similar to No tokens were supplied.. - - - - - Looks up a localized string similar to The CancellationTokenSource associated with this CancellationToken has been disposed.. - - - - - Looks up a localized string similar to The CancellationTokenSource has been disposed.. - - - - - Looks up a localized string similar to The SyncRoot property may not be used for the synchronization of concurrent collections.. - - - - - Looks up a localized string similar to The array is multidimensional, or the type parameter for the set cannot be cast automatically to the type of the destination array.. - - - - - Looks up a localized string similar to The index is equal to or greater than the length of the array, or the number of elements in the dictionary is greater than the available space from index to the end of the destination array.. - - - - - Looks up a localized string similar to The capacity argument must be greater than or equal to zero.. - - - - - Looks up a localized string similar to The concurrencyLevel argument must be positive.. - - - - - Looks up a localized string similar to The index argument is less than zero.. - - - - - Looks up a localized string similar to TKey is a reference type and item.Key is null.. - - - - - Looks up a localized string similar to The key already existed in the dictionary.. - - - - - Looks up a localized string similar to The source argument contains duplicate keys.. - - - - - Looks up a localized string similar to The key was of an incorrect type for this dictionary.. - - - - - Looks up a localized string similar to The value was of an incorrect type for this dictionary.. - - - - - Looks up a localized string similar to The lazily-initialized type does not have a public, parameterless constructor.. - - - - - Looks up a localized string similar to ValueFactory returned null.. - - - - - Looks up a localized string similar to The spinCount argument must be in the range 0 to {0}, inclusive.. - - - - - Looks up a localized string similar to There are too many threads currently waiting on the event. A maximum of {0} waiting threads are supported.. - - - - - Looks up a localized string similar to The event has been disposed.. - - - - - Looks up a localized string similar to The operation was canceled.. - - - - - Looks up a localized string similar to The condition argument is null.. - - - - - Looks up a localized string similar to The timeout must represent a value between -1 and Int32.MaxValue, inclusive.. - - - - - Looks up a localized string similar to The specified TaskContinuationOptions combined LongRunning and ExecuteSynchronously. Synchronous continuations should not be long running.. - - - - - Looks up a localized string similar to The specified TaskContinuationOptions excluded all continuation kinds.. - - - - - Looks up a localized string similar to (Internal)An attempt was made to create a LongRunning SelfReplicating task.. - - - - - Looks up a localized string similar to The value needs to translate in milliseconds to -1 (signifying an infinite timeout), 0 or a positive integer less than or equal to Int32.MaxValue.. - - - - - Looks up a localized string similar to The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer.. - - - - - Looks up a localized string similar to A task may only be disposed if it is in a completion state (RanToCompletion, Faulted or Canceled).. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.LongRunning in calls to FromAsync.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.PreferFairness in calls to FromAsync.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.SelfReplicating in calls to FromAsync.. - - - - - Looks up a localized string similar to FromAsync was called with a TaskManager that had already shut down.. - - - - - Looks up a localized string similar to The tasks argument contains no tasks.. - - - - - Looks up a localized string similar to It is invalid to exclude specific continuation kinds for continuations off of multiple tasks.. - - - - - Looks up a localized string similar to The tasks argument included a null value.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task that was already started.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a continuation task.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task that has already completed.. - - - - - Looks up a localized string similar to Start may not be called on a task that was already started.. - - - - - Looks up a localized string similar to Start may not be called on a continuation task.. - - - - - Looks up a localized string similar to Start may not be called on a task with null action.. - - - - - Looks up a localized string similar to Start may not be called on a promise-style task.. - - - - - Looks up a localized string similar to Start may not be called on a task that has completed.. - - - - - Looks up a localized string similar to The task has been disposed.. - - - - - Looks up a localized string similar to The tasks array included at least one null element.. - - - - - Looks up a localized string similar to The awaited task has not yet completed.. - - - - - Looks up a localized string similar to A task was canceled.. - - - - - Looks up a localized string similar to The exceptions collection was empty.. - - - - - Looks up a localized string similar to The exceptions collection included at least one null element.. - - - - - Looks up a localized string similar to A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread.. - - - - - Looks up a localized string similar to (Internal)Expected an Exception or an IEnumerable<Exception>. - - - - - Looks up a localized string similar to ExecuteTask may not be called for a task which was already executed.. - - - - - Looks up a localized string similar to ExecuteTask may not be called for a task which was previously queued to a different TaskScheduler.. - - - - - Looks up a localized string similar to The current SynchronizationContext may not be used as a TaskScheduler.. - - - - - Looks up a localized string similar to The TryExecuteTaskInline call to the underlying scheduler succeeded, but the task body was not invoked.. - - - - - Looks up a localized string similar to An exception was thrown by a TaskScheduler.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.SelfReplicating for a Task<TResult>.. - - - - - Looks up a localized string similar to {Not yet computed}. - - - - - Looks up a localized string similar to A task's Exception may only be set directly if the task was created without a function.. - - - - - Looks up a localized string similar to An attempt was made to transition a task to a final state when it had already completed.. - - - - - Represents a thread-safe collection of keys and values. - - The type of the keys in the dictionary. - The type of the values in the dictionary. - - All public and protected members of are thread-safe and may be used - concurrently from multiple threads. - - - - - Initializes a new instance of the - class that is empty, has the default concurrency level, has the default initial capacity, and - uses the default comparer for the key type. - - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level and capacity, and uses the default - comparer for the key type. - - The estimated number of threads that will update the - concurrently. - The initial number of elements that the - can contain. - is - less than 1. - is less than - 0. - - - - Initializes a new instance of the - class that contains elements copied from the specified , has the default concurrency - level, has the default initial capacity, and uses the default comparer for the key type. - - The whose elements are copied to - the new - . - is a null reference - (Nothing in Visual Basic). - contains one or more - duplicate keys. - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level and capacity, and uses the specified - . - - The - implementation to use when comparing keys. - is a null reference - (Nothing in Visual Basic). - - - - Initializes a new instance of the - class that contains elements copied from the specified , has the default concurrency level, has the default - initial capacity, and uses the specified - . - - The whose elements are copied to - the new - . - The - implementation to use when comparing keys. - is a null reference - (Nothing in Visual Basic). -or- - is a null reference (Nothing in Visual Basic). - - - - - Initializes a new instance of the - class that contains elements copied from the specified , - has the specified concurrency level, has the specified initial capacity, and uses the specified - . - - The estimated number of threads that will update the - concurrently. - The whose elements are copied to the new - . - The implementation to use - when comparing keys. - - is a null reference (Nothing in Visual Basic). - -or- - is a null reference (Nothing in Visual Basic). - - - is less than 1. - - contains one or more duplicate keys. - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level, has the specified initial capacity, and - uses the specified . - - The estimated number of threads that will update the - concurrently. - The initial number of elements that the - can contain. - The - implementation to use when comparing keys. - - is less than 1. -or- - is less than 0. - - is a null reference - (Nothing in Visual Basic). - - - - Attempts to add the specified key and value to the . - - The key of the element to add. - The value of the element to add. The value can be a null reference (Nothing - in Visual Basic) for reference types. - true if the key/value pair was added to the - successfully; otherwise, false. - is null reference - (Nothing in Visual Basic). - The - contains too many elements. - - - - Determines whether the contains the specified - key. - - The key to locate in the . - true if the contains an element with - the specified key; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Attempts to remove and return the the value with the specified key from the - . - - The key of the element to remove and return. - When this method returns, contains the object removed from the - or the default value of - if the operation failed. - true if an object was removed successfully; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Removes the specified key from the dictionary if it exists and returns its associated value. - If matchValue flag is set, the key will be removed only if is associated with a particular - value. - - The key to search for and remove if it exists. - The variable into which the removed value, if found, is stored. - Whether removal of the key is conditional on its value. - The conditional value to compare against if is true - - - - - Attempts to get the value associated with the specified key from the . - - The key of the value to get. - When this method returns, contains the object from - the - with the spedified key or the default value of - , if the operation failed. - true if the key was found in the ; - otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Compares the existing value for the specified key with a specified value, and if they’re equal, - updates the key with a third value. - - The key whose value is compared with and - possibly replaced. - The value that replaces the value of the element with if the comparison results in equality. - The value that is compared to the value of the element with - . - true if the value with was equal to and replaced with ; otherwise, - false. - is a null - reference. - - - - Removes all keys and values from the . - - - - - Copies the elements of the to an array of - type , starting at the - specified array index. - - The one-dimensional array of type - that is the destination of the elements copied from the . The array must have zero-based indexing. - The zero-based index in at which copying - begins. - is a null reference - (Nothing in Visual Basic). - is less than - 0. - is equal to or greater than - the length of the . -or- The number of elements in the source - is greater than the available space from to the end of the destination - . - - - - Copies the key and value pairs stored in the to a - new array. - - A new array containing a snapshot of key and value pairs copied from the . - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToPairs. - - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToEntries. - - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToObjects. - - - - Returns an enumerator that iterates through the . - An enumerator for the . - - The enumerator returned from the dictionary is safe to use concurrently with - reads and writes to the dictionary, however it does not represent a moment-in-time snapshot - of the dictionary. The contents exposed through the enumerator may contain modifications - made to the dictionary after was called. - - - - - Shared internal implementation for inserts and updates. - If key exists, we always return false; and if updateIfExists == true we force update with value; - If key doesn't exist, we always add value and return true; - - - - - Adds a key/value pair to the - if the key does not already exist. - - The key of the element to add. - The function used to generate a value for the key - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The value for the key. This will be either the existing value for the key if the - key is already in the dictionary, or the new value for the key as returned by valueFactory - if the key was not in the dictionary. - - - - Adds a key/value pair to the - if the key does not already exist. - - The key of the element to add. - the value to be added, if the key does not already exist - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The value for the key. This will be either the existing value for the key if the - key is already in the dictionary, or the new value if the key was not in the dictionary. - - - - Adds a key/value pair to the if the key does not already - exist, or updates a key/value pair in the if the key - already exists. - - The key to be added or whose value should be updated - The function used to generate a value for an absent key - The function used to generate a new value for an existing key - based on the key's existing value - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The new value for the key. This will be either be the result of addValueFactory (if the key was - absent) or the result of updateValueFactory (if the key was present). - - - - Adds a key/value pair to the if the key does not already - exist, or updates a key/value pair in the if the key - already exists. - - The key to be added or whose value should be updated - The value to be added for an absent key - The function used to generate a new value for an existing key based on - the key's existing value - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The new value for the key. This will be either be the result of addValueFactory (if the key was - absent) or the result of updateValueFactory (if the key was present). - - - - Adds the specified key and value to the . - - The object to use as the key of the element to add. - The object to use as the value of the element to add. - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - - An element with the same key already exists in the . - - - - Removes the element with the specified key from the . - - The key of the element to remove. - true if the element is successfully remove; otherwise false. This method also returns - false if - was not found in the original . - - is a null reference - (Nothing in Visual Basic). - - - - Adds the specified value to the - with the specified key. - - The - structure representing the key and value to add to the . - The of is null. - The - contains too many elements. - An element with the same key already exists in the - - - - - Determines whether the - contains a specific key and value. - - The - structure to locate in the . - true if the is found in the ; otherwise, false. - - - - Removes a key and value from the dictionary. - - The - structure representing the key and value to remove from the . - true if the key and value represented by is successfully - found and removed; otherwise, false. - The Key property of is a null reference (Nothing in Visual Basic). - - - Returns an enumerator that iterates through the . - An enumerator for the . - - The enumerator returned from the dictionary is safe to use concurrently with - reads and writes to the dictionary, however it does not represent a moment-in-time snapshot - of the dictionary. The contents exposed through the enumerator may contain modifications - made to the dictionary after was called. - - - - - Adds the specified key and value to the dictionary. - - The object to use as the key. - The object to use as the value. - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - - is of a type that is not assignable to the key type of the . -or- - is of a type that is not assignable to , - the type of values in the . - -or- A value with the same key already exists in the . - - - - - Gets whether the contains an - element with the specified key. - - The key to locate in the . - true if the contains - an element with the specified key; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - Provides an for the - . - An for the . - - - - Removes the element with the specified key from the . - - The key of the element to remove. - is a null reference - (Nothing in Visual Basic). - - - - Copies the elements of the to an array, starting - at the specified array index. - - The one-dimensional array that is the destination of the elements copied from - the . The array must have zero-based - indexing. - The zero-based index in at which copying - begins. - is a null reference - (Nothing in Visual Basic). - is less than - 0. - is equal to or greater than - the length of the . -or- The number of elements in the source - is greater than the available space from to the end of the destination - . - - - - Replaces the internal table with a larger one. To prevent multiple threads from resizing the - table as a result of races, the table of buckets that was deemed too small is passed in as - an argument to GrowTable(). GrowTable() obtains a lock, and then checks whether the bucket - table has been replaced in the meantime or not. - - Reference to the bucket table that was deemed too small. - - - - Computes the bucket and lock number for a particular key. - - - - - Acquires all locks for this hash table, and increments locksAcquired by the number - of locks that were successfully acquired. The locks are acquired in an increasing - order. - - - - - Acquires a contiguous range of locks for this hash table, and increments locksAcquired - by the number of locks that were successfully acquired. The locks are acquired in an - increasing order. - - - - - Releases a contiguous range of locks. - - - - - Gets a collection containing the keys in the dictionary. - - - - - Gets a collection containing the values in the dictionary. - - - - - A helper method for asserts. - - - - - Get the data array to be serialized - - - - - Construct the dictionary from a previously seiralized one - - - - - Gets or sets the value associated with the specified key. - - The key of the value to get or set. - The value associated with the specified key. If the specified key is not found, a get - operation throws a - , and a set operation creates a new - element with the specified key. - is a null reference - (Nothing in Visual Basic). - The property is retrieved and - - does not exist in the collection. - - - - Gets the number of key/value pairs contained in the . - - The dictionary contains too many - elements. - The number of key/value paris contained in the . - Count has snapshot semantics and represents the number of items in the - at the moment when Count was accessed. - - - - Gets a value that indicates whether the is empty. - - true if the is empty; otherwise, - false. - - - - Gets a collection containing the keys in the . - - An containing the keys in the - . - - - - Gets a collection containing the values in the . - - An containing the values in - the - . - - - - Gets a value indicating whether the dictionary is read-only. - - true if the is - read-only; otherwise, false. For , this property always returns - false. - - - - Gets a value indicating whether the has a fixed size. - - true if the has a - fixed size; otherwise, false. For , this property always - returns false. - - - - Gets a value indicating whether the is read-only. - - true if the is - read-only; otherwise, false. For , this property always - returns false. - - - - Gets an containing the keys of the . - - An containing the keys of the . - - - - Gets an containing the values in the . - - An containing the values in the . - - - - Gets or sets the value associated with the specified key. - - The key of the value to get or set. - The value associated with the specified key, or a null reference (Nothing in Visual Basic) - if is not in the dictionary or is of a type that is - not assignable to the key type of the . - is a null reference - (Nothing in Visual Basic). - - A value is being assigned, and is of a type that is not assignable to the - key type of the . -or- A value is being - assigned, and is of a type that is not assignable to the value type - of the - - - - - Gets a value indicating whether access to the is - synchronized with the SyncRoot. - - true if access to the is synchronized - (thread safe); otherwise, false. For , this property always - returns false. - - - - Gets an object that can be used to synchronize access to the . This property is not supported. - - The SyncRoot property is not supported. - - - - The number of concurrent writes for which to optimize by default. - - - - - A node in a singly-linked list representing a particular hash table bucket. - - - - - A private class to represent enumeration over the dictionary that implements the - IDictionaryEnumerator interface. - - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - Represents an asynchronous method builder. - - - A cached VoidTaskResult task used for builders that complete synchronously. - - - The generic builder object to which this non-generic instance delegates. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state. - - The builder is not initialized. - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - - Gets the for this builder. - The representing the builder's asynchronous operation. - The builder is not initialized. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - Holds state related to the builder's IAsyncStateMachine. - This is a mutable struct. Be very delicate with it. - - - A reference to the heap-allocated state machine object associated with this builder. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument is null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - - Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method. - On first invocation, the supplied state machine will be boxed. - - Specifies the type of the method builder used. - Specifies the type of the state machine used. - The builder. - The state machine. - An Action to provide to the awaiter. - - - Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext. - - - The context with which to run MoveNext. - - - The state machine whose MoveNext method should be invoked. - - - Initializes the runner. - The context with which to run MoveNext. - - - Invokes MoveNext under the provided context. - - - Cached delegate used with ExecutionContext.Run. - - - Invokes the MoveNext method on the supplied IAsyncStateMachine. - The IAsyncStateMachine machine instance. - - - - Provides a builder for asynchronous methods that return void. - This type is intended for compiler use only. - - - - The synchronization context associated with this operation. - - - State related to the IAsyncStateMachine. - - - An object used by the debugger to uniquely identify this builder. Lazily initialized. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Registers with UnobservedTaskException to suppress exception crashing. - - - Non-zero if PreventUnobservedTaskExceptions has already been invoked. - - - Initializes a new . - The initialized . - - - Initializes the . - The synchronizationContext associated with this operation. This may be null. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument was null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - Completes the method builder successfully. - - - Faults the method builder with an exception. - The exception that is the cause of this fault. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - - - Notifies the current synchronization context that the operation completed. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger and only in a single-threaded manner. - - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - A cached task for default(TResult). - - - State related to the IAsyncStateMachine. - - - The lazily-initialized task. - Must be named m_task for debugger step-over to work correctly. - - - The lazily-initialized task completion source. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state with the specified result. - - The result to use to complete the task. - The task has already completed. - - - - Completes the builder by using either the supplied completed task, or by completing - the builder's previously accessed task using default(TResult). - - A task already completed with the value default(TResult). - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - This should only be invoked from within an asynchronous method, - and only by the debugger. - - - - - Gets a task for the specified result. This will either - be a cached or new task, never null. - - The result for which we need a task. - The completed task containing the result. - - - Gets the lazily-initialized TaskCompletionSource. - - - Gets the for this builder. - The representing the builder's asynchronous operation. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - Provides a base class used to cache tasks of a specific return type. - Specifies the type of results the cached tasks return. - - - - A singleton cache for this result type. - This may be null if there are no cached tasks for this TResult. - - - - Creates a non-disposable task. - The result for the task. - The cacheable task. - - - Creates a cache. - A task cache for this result type. - - - Gets a cached task if one exists. - The result for which we want a cached task. - A cached task if one exists; otherwise, null. - - - Provides a cache for Boolean tasks. - - - A true task. - - - A false task. - - - Gets a cached task for the Boolean result. - true or false - A cached task for the Boolean result. - - - Provides a cache for zero Int32 tasks. - - - The minimum value, inclusive, for which we want a cached task. - - - The maximum value, exclusive, for which we want a cached task. - - - The cache of Task{Int32}. - - - Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). - - - Gets a cached task for the zero Int32 result. - The integer value - A cached task for the Int32 result or null if not cached. - - - - Represents state machines generated for asynchronous methods. - This type is intended for compiler use only. - - - - Moves the state machine to its next state. - - - Configures the state machine with a heap-allocated replica. - The heap-allocated replica. - - - - Represents an awaiter used to schedule continuations when an await operation completes. - - - - - Represents an operation that will schedule continuations when the operation completes. - - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information. - - - Used with Task(of void) - - - - An interface similar to the one added in .NET 4.0. - - - - The exception that is thrown in a thread upon cancellation of an operation that the thread was executing. - - - Initializes the exception. - - - Initializes the exception. - The error message that explains the reason for the exception. - - - Initializes the exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - Initializes the exception. - A cancellation token associated with the operation that was canceled. - - - Initializes the exception. - The error message that explains the reason for the exception. - A cancellation token associated with the operation that was canceled. - - - Initializes the exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - A cancellation token associated with the operation that was canceled. - - - Gets a token associated with the operation that was canceled. - - - - A dummy replacement for the .NET internal class StackCrawlMark. - - - - - Propogates notification that operations should be canceled. - - - - A may be created directly in an unchangeable canceled or non-canceled state - using the CancellationToken's constructors. However, to have a CancellationToken that can change - from a non-canceled to a canceled state, - CancellationTokenSource must be used. - CancellationTokenSource exposes the associated CancellationToken that may be canceled by the source through its - Token property. - - - Once canceled, a token may not transition to a non-canceled state, and a token whose - is false will never change to one that can be canceled. - - - All members of this struct are thread-safe and may be used concurrently from multiple threads. - - - - - - Internal constructor only a CancellationTokenSource should create a CancellationToken - - - - - Initializes the CancellationToken. - - - The canceled state for the token. - - - Tokens created with this constructor will remain in the canceled state specified - by the parameter. If is false, - both and will be false. - If is true, - both and will be true. - - - - - Registers a delegate that will be called when this CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - A Boolean value that indicates whether to capture - the current SynchronizationContext and use it - when invoking the . - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The state to pass to the when the delegate is invoked. This may be null. - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The state to pass to the when the delegate is invoked. This may be null. - A Boolean value that indicates whether to capture - the current SynchronizationContext and use it - when invoking the . - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Determines whether the current CancellationToken instance is equal to the - specified token. - - The other CancellationToken to which to compare this - instance. - True if the instances are equal; otherwise, false. Two tokens are equal if they are associated - with the same CancellationTokenSource or if they were both constructed - from public CancellationToken constructors and their values are equal. - - - - Determines whether the current CancellationToken instance is equal to the - specified . - - The other object to which to compare this instance. - True if is a CancellationToken - and if the two instances are equal; otherwise, false. Two tokens are equal if they are associated - with the same CancellationTokenSource or if they were both constructed - from public CancellationToken constructors and their values are equal. - An associated CancellationTokenSource has been disposed. - - - - Serves as a hash function for a CancellationToken. - - A hash code for the current CancellationToken instance. - - - - Determines whether two CancellationToken instances are equal. - - The first instance. - The second instance. - True if the instances are equal; otherwise, false. - An associated CancellationTokenSource has been disposed. - - - - Determines whether two CancellationToken instances are not equal. - - The first instance. - The second instance. - True if the instances are not equal; otherwise, false. - An associated CancellationTokenSource has been disposed. - - - - Throws a OperationCanceledException if - this token has had cancellation requested. - - - This method provides functionality equivalent to: - - if (token.IsCancellationRequested) - throw new OperationCanceledException(token); - - - The token has had cancellation requested. - The associated CancellationTokenSource has been disposed. - - - - Returns an empty CancellationToken value. - - - The value returned by this property will be non-cancelable by default. - - - - - Gets whether cancellation has been requested for this token. - - Whether cancellation has been requested for this token. - - - This property indicates whether cancellation has been requested for this token, - either through the token initially being construted in a canceled state, or through - calling Cancel - on the token's associated . - - - If this property is true, it only guarantees that cancellation has been requested. - It does not guarantee that every registered handler - has finished executing, nor that cancellation requests have finished propagating - to all registered handlers. Additional synchronization may be required, - particularly in situations where related objects are being canceled concurrently. - - - - - - Gets whether this token is capable of being in the canceled state. - - - If CanBeCanceled returns false, it is guaranteed that the token will never transition - into a canceled state, meaning that will never - return true. - - - - - Gets a that is signaled when the token is canceled. - - Accessing this property causes a WaitHandle - to be instantiated. It is preferable to only use this property when necessary, and to then - dispose the associated instance at the earliest opportunity (disposing - the source will dispose of this allocated handle). The handle should not be closed or disposed directly. - - The associated CancellationTokenSource has been disposed. - - - - Represents a callback delegate that has been registered with a CancellationToken. - - - To unregister a callback, dispose the corresponding Registration instance. - - - - - Attempts to deregister the item. If it's already being run, this may fail. - Entails a full memory fence. - - True if the callback was found and deregistered, false otherwise. - - - - Disposes of the registration and unregisters the target callback from the associated - CancellationToken. - If the target callback is currently executing this method will wait until it completes, except - in the degenerate cases where a callback method deregisters itself. - - - - - Determines whether two CancellationTokenRegistration - instances are equal. - - The first instance. - The second instance. - True if the instances are equal; otherwise, false. - - - - Determines whether two CancellationTokenRegistration instances are not equal. - - The first instance. - The second instance. - True if the instances are not equal; otherwise, false. - - - - Determines whether the current CancellationTokenRegistration instance is equal to the - specified . - - The other object to which to compare this instance. - True, if both this and are equal. False, otherwise. - Two CancellationTokenRegistration instances are equal if - they both refer to the output of a single call to the same Register method of a - CancellationToken. - - - - - Determines whether the current CancellationToken instance is equal to the - specified . - - The other CancellationTokenRegistration to which to compare this instance. - True, if both this and are equal. False, otherwise. - Two CancellationTokenRegistration instances are equal if - they both refer to the output of a single call to the same Register method of a - CancellationToken. - - - - - Serves as a hash function for a CancellationTokenRegistration.. - - A hash code for the current CancellationTokenRegistration instance. - - - - Signals to a that it should be canceled. - - - - is used to instantiate a - (via the source's Token property) - that can be handed to operations that wish to be notified of cancellation or that can be used to - register asynchronous operations for cancellation. That token may have cancellation requested by - calling to the source's Cancel - method. - - - All members of this class, except Dispose, are thread-safe and may be used - concurrently from multiple threads. - - - - - The ID of the thread currently executing the main body of CTS.Cancel() - this helps us to know if a call to ctr.Dispose() is running 'within' a cancellation callback. - This is updated as we move between the main thread calling cts.Cancel() and any syncContexts that are used to - actually run the callbacks. - - - - Initializes the . - - - - - Communicates a request for cancellation. - - - - The associated will be - notified of the cancellation and will transition to a state where - IsCancellationRequested returns true. - Any callbacks or cancelable operations - registered with the will be executed. - - - Cancelable operations and callbacks registered with the token should not throw exceptions. - However, this overload of Cancel will aggregate any exceptions thrown into a , - such that one callback throwing an exception will not prevent other registered callbacks from being executed. - - - The that was captured when each callback was registered - will be reestablished when the callback is invoked. - - - An aggregate exception containing all the exceptions thrown - by the registered callbacks on the associated . - This has been disposed. - - - - Communicates a request for cancellation. - - - - The associated will be - notified of the cancellation and will transition to a state where - IsCancellationRequested returns true. - Any callbacks or cancelable operations - registered with the will be executed. - - - Cancelable operations and callbacks registered with the token should not throw exceptions. - If is true, an exception will immediately propagate out of the - call to Cancel, preventing the remaining callbacks and cancelable operations from being processed. - If is false, this overload will aggregate any - exceptions thrown into a , - such that one callback throwing an exception will not prevent other registered callbacks from being executed. - - - The that was captured when each callback was registered - will be reestablished when the callback is invoked. - - - Specifies whether exceptions should immediately propagate. - An aggregate exception containing all the exceptions thrown - by the registered callbacks on the associated . - This has been disposed. - - - - Releases the resources used by this . - - - This method is not thread-safe for any other concurrent calls. - - - - - Throws an exception if the source has been disposed. - - - - - InternalGetStaticSource() - - Whether the source should be set. - A static source to be shared among multiple tokens. - - - - Registers a callback object. If cancellation has already occurred, the - callback will have been run by the time this method returns. - - - - - - - - - - Invoke the Canceled event. - - - The handlers are invoked synchronously in LIFO order. - - - - - Creates a CancellationTokenSource that will be in the canceled state - when any of the source tokens are in the canceled state. - - The first CancellationToken to observe. - The second CancellationToken to observe. - A CancellationTokenSource that is linked - to the source tokens. - A CancellationTokenSource associated with - one of the source tokens has been disposed. - - - - Creates a CancellationTokenSource that will be in the canceled state - when any of the source tokens are in the canceled state. - - The CancellationToken instances to observe. - A CancellationTokenSource that is linked - to the source tokens. - is null. - A CancellationTokenSource associated with - one of the source tokens has been disposed. - - - - Gets whether cancellation has been requested for this CancellationTokenSource. - - Whether cancellation has been requested for this CancellationTokenSource. - - - This property indicates whether cancellation has been requested for this token source, such as - due to a call to its - Cancel method. - - - If this property returns true, it only guarantees that cancellation has been requested. It does not - guarantee that every handler registered with the corresponding token has finished executing, nor - that cancellation requests have finished propagating to all registered handlers. Additional - synchronization may be required, particularly in situations where related objects are being - canceled concurrently. - - - - - - A simple helper to determine whether cancellation has finished. - - - - - A simple helper to determine whether disposal has occured. - - - - - The ID of the thread that is running callbacks. - - - - - Gets the CancellationToken - associated with this . - - The CancellationToken - associated with this . - The token source has been - disposed. - - - - - - - - - - - - - - The currently executing callback - - - - - A helper class for collating the various bits of information required to execute - cancellation callbacks. - - - - - InternalExecuteCallbackSynchronously_GeneralPath - This will be called on the target synchronization context, however, we still need to restore the required execution context - - - - - A sparsely populated array. Elements can be sparse and some null, but this allows for - lock-free additions and growth, and also for constant time removal (by nulling out). - - The kind of elements contained within. - - - - Allocates a new array with the given initial size. - - How many array slots to pre-allocate. - - - - Adds an element in the first available slot, beginning the search from the tail-to-head. - If no slots are available, the array is grown. The method doesn't return until successful. - - The element to add. - Information about where the add happened, to enable O(1) deregistration. - - - - The tail of the doubly linked list. - - - - - A struct to hold a link to the exact spot in an array an element was inserted, enabling - constant time removal later on. - - - - - A fragment of a sparsely populated array, doubly linked. - - The kind of elements contained within. - - - - Provides lazy initialization routines. - - - These routines avoid needing to allocate a dedicated, lazy-initialization instance, instead using - references to ensure targets have been initialized as they are accessed. - - - - - Initializes a target reference type with the type's default constructor if the target has not - already been initialized. - - The refence type of the reference to be initialized. - A reference of type to initialize if it has not - already been initialized. - The initialized reference of type . - Type does not have a default - constructor. - - Permissions to access the constructor of type were missing. - - - - This method may only be used on reference types. To ensure initialization of value - types, see other overloads of EnsureInitialized. - - - This method may be used concurrently by multiple threads to initialize . - In the event that multiple threads access this method concurrently, multiple instances of - may be created, but only one will be stored into . In such an occurrence, this method will not dispose of the - objects that were not stored. If such objects must be disposed, it is up to the caller to determine - if an object was not used and to then dispose of the object appropriately. - - - - - - Initializes a target reference type using the specified function if it has not already been - initialized. - - The reference type of the reference to be initialized. - The reference of type to initialize if it has not - already been initialized. - The invoked to initialize the - reference. - The initialized reference of type . - Type does not have a - default constructor. - returned - null. - - - This method may only be used on reference types, and may - not return a null reference (Nothing in Visual Basic). To ensure initialization of value types or - to allow null reference types, see other overloads of EnsureInitialized. - - - This method may be used concurrently by multiple threads to initialize . - In the event that multiple threads access this method concurrently, multiple instances of - may be created, but only one will be stored into . In such an occurrence, this method will not dispose of the - objects that were not stored. If such objects must be disposed, it is up to the caller to determine - if an object was not used and to then dispose of the object appropriately. - - - - - - Initialize the target using the given delegate (slow path). - - The reference type of the reference to be initialized. - The variable that need to be initialized - The delegate that will be executed to initialize the target - The initialized variable - - - - Initializes a target reference or value type with its default constructor if it has not already - been initialized. - - The type of the reference to be initialized. - A reference or value of type to initialize if it - has not already been initialized. - A reference to a boolean that determines whether the target has already - been initialized. - A reference to an object used as the mutually exclusive lock for initializing - . - The initialized value of type . - - - - Initializes a target reference or value type with a specified function if it has not already been - initialized. - - The type of the reference to be initialized. - A reference or value of type to initialize if it - has not already been initialized. - A reference to a boolean that determines whether the target has already - been initialized. - A reference to an object used as the mutually exclusive lock for initializing - . - The invoked to initialize the - reference or value. - The initialized value of type . - - - - Ensure the target is initialized and return the value (slow path). This overload permits nulls - and also works for value type targets. Uses the supplied function to create the value. - - The type of target. - A reference to the target to be initialized. - A reference to a location tracking whether the target has been initialized. - A reference to a location containing a mutual exclusive lock. - - The to invoke in order to produce the lazily-initialized value. - - The initialized object. - - - - Provides a slimmed down version of . - - - All public and protected members of are thread-safe and may be used - concurrently from multiple threads, with the exception of Dispose, which - must only be used when all other operations on the have - completed, and Reset, which should only be used when no other threads are - accessing the event. - - - - - Initializes a new instance of the - class with an initial state of nonsignaled. - - - - - Initializes a new instance of the - class with a Boolen value indicating whether to set the intial state to signaled. - - true to set the initial state signaled; false to set the initial state - to nonsignaled. - - - - Initializes a new instance of the - class with a Boolen value indicating whether to set the intial state to signaled and a specified - spin count. - - true to set the initial state to signaled; false to set the initial state - to nonsignaled. - The number of spin waits that will occur before falling back to a true - wait. - is less than - 0 or greater than the maximum allowed value. - - - - Initializes the internal state of the event. - - Whether the event is set initially or not. - The spin count that decides when the event will block. - - - - Helper to ensure the lock object is created before first use. - - - - - This method lazily initializes the event object. It uses CAS to guarantee that - many threads racing to call this at once don't result in more than one event - being stored and used. The event will be signaled or unsignaled depending on - the state of the thin-event itself, with synchronization taken into account. - - True if a new event was created and stored, false otherwise. - - - - Sets the state of the event to signaled, which allows one or more threads waiting on the event to - proceed. - - - - - Private helper to actually perform the Set. - - Indicates whether we are calling Set() during cancellation. - The object has been canceled. - - - - Sets the state of the event to nonsignaled, which causes threads to block. - - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - Blocks the current thread until the current is set. - - - The maximum number of waiters has been exceeded. - - - The caller of this method blocks indefinitely until the current instance is set. The caller will - return immediately if the event is currently in a set state. - - - - - Blocks the current thread until the current receives a signal, - while observing a . - - The to - observe. - - The maximum number of waiters has been exceeded. - - was - canceled. - - The caller of this method blocks indefinitely until the current instance is set. The caller will - return immediately if the event is currently in a set state. - - - - - Blocks the current thread until the current is set, using a - to measure the time interval. - - A that represents the number of milliseconds - to wait, or a that represents -1 milliseconds to wait indefinitely. - - true if the was set; otherwise, - false. - is a negative - number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater - than . - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - to measure the time interval, while observing a . - - A that represents the number of milliseconds - to wait, or a that represents -1 milliseconds to wait indefinitely. - - The to - observe. - true if the was set; otherwise, - false. - is a negative - number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater - than . - was canceled. - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - 32-bit signed integer to measure the time interval. - - The number of milliseconds to wait, or (-1) to wait indefinitely. - true if the was set; otherwise, - false. - is a - negative number other than -1, which represents an infinite time-out. - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - 32-bit signed integer to measure the time interval, while observing a . - - The number of milliseconds to wait, or (-1) to wait indefinitely. - The to - observe. - true if the was set; otherwise, - false. - is a - negative number other than -1, which represents an infinite time-out. - - The maximum number of waiters has been exceeded. - - was canceled. - - - - Releases all resources used by the current instance of . - - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - When overridden in a derived class, releases the unmanaged resources used by the - , and optionally releases the managed resources. - - true to release both managed and unmanaged resources; - false to release only unmanaged resources. - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - Throw ObjectDisposedException if the MRES is disposed - - - - - Private helper method to wake up waiters when a cancellationToken gets canceled. - - - - - Private helper method for updating parts of a bit-string state value. - Mainly called from the IsSet and Waiters properties setters - - - Note: the parameter types must be int as CompareExchange cannot take a Uint - - The new value - The mask used to set the bits - - - - Private helper method - performs Mask and shift, particular helpful to extract a field from a packed word. - eg ExtractStatePortionAndShiftRight(0x12345678, 0xFF000000, 24) => 0x12, ie extracting the top 8-bits as a simple integer - - ?? is there a common place to put this rather than being private to MRES? - - - - - - - - - Performs a Mask operation, but does not perform the shift. - This is acceptable for boolean values for which the shift is unnecessary - eg (val & Mask) != 0 is an appropriate way to extract a boolean rather than using - ((val & Mask) >> shiftAmount) == 1 - - ?? is there a common place to put this rather than being private to MRES? - - - - - - - Helper function to measure and update the wait time - - The first time (in Ticks) observed when the wait started. - The orginal wait timeoutout in milliseconds. - The new wait time in milliseconds, -1 if the time expired, -2 if overflow in counters - has occurred. - - - - Gets the underlying object for this . - - The underlying event object fore this . - - Accessing this property forces initialization of an underlying event object if one hasn't - already been created. To simply wait on this , - the public Wait methods should be preferred. - - - - - Gets whether the event is set. - - true if the event has is set; otherwise, false. - - - - Gets the number of spin waits that will be occur before falling back to a true wait. - - - - - How many threads are waiting. - - - - - Provides support for spin-based waiting. - - - - encapsulates common spinning logic. On single-processor machines, yields are - always used instead of busy waits, and on computers with Intel™ processors employing Hyper-Threading™ - technology, it helps to prevent hardware thread starvation. SpinWait encapsulates a good mixture of - spinning and true yielding. - - - is a value type, which means that low-level code can utilize SpinWait without - fear of unnecessary allocation overheads. SpinWait is not generally useful for ordinary applications. - In most cases, you should use the synchronization classes provided by the .NET Framework, such as - . For most purposes where spin waiting is required, however, - the type should be preferred over the System.Threading.Thread.SpinWait method. - - - While SpinWait is designed to be used in concurrent applications, it is not designed to be - used from multiple threads concurrently. SpinWait's members are not thread-safe. If multiple - threads must spin, each should use its own instance of SpinWait. - - - - - - Performs a single spin. - - - This is typically called in a loop, and may change in behavior based on the number of times a - has been called thus far on this instance. - - - - - Resets the spin counter. - - - This makes and behave as though no calls - to had been issued on this instance. If a instance - is reused many times, it may be useful to reset it to avoid yielding too soon. - - - - - Spins until the specified condition is satisfied. - - A delegate to be executed over and over until it returns true. - The argument is null. - - - - Spins until the specified condition is satisfied or until the specified timeout is expired. - - A delegate to be executed over and over until it returns true. - - A that represents the number of milliseconds to wait, - or a TimeSpan that represents -1 milliseconds to wait indefinitely. - True if the condition is satisfied within the timeout; otherwise, false - The argument is null. - is a negative number - other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than - . - - - - Spins until the specified condition is satisfied or until the specified timeout is expired. - - A delegate to be executed over and over until it returns true. - The number of milliseconds to wait, or (-1) to wait indefinitely. - True if the condition is satisfied within the timeout; otherwise, false - The argument is null. - is a - negative number other than -1, which represents an infinite time-out. - - - - Gets the number of times has been called on this instance. - - - - - Gets whether the next call to will yield the processor, triggering a - forced context switch. - - Whether the next call to will yield the processor, triggering a - forced context switch. - - On a single-CPU machine, always yields the processor. On machines with - multiple CPUs, may yield after an unspecified number of calls. - - - - - A helper class to get the number of preocessors, it updates the numbers of processors every sampling interval - - - - - Gets the number of available processors - - - - - Gets whether the current machine has only a single processor. - - - - - Represents an asynchronous operation that produces a result at some time in the future. - - - The type of the result produced by this . - - - - instances may be created in a variety of ways. The most common approach is by - using the task's property to retrieve a instance that can be used to create tasks for several - purposes. For example, to create a that runs a function, the factory's StartNew - method may be used: - - // C# - var t = Task<int>.Factory.StartNew(() => GenerateResult()); - - or - - var t = Task.Factory.StartNew(() => GenerateResult()); - - ' Visual Basic - Dim t = Task<int>.Factory.StartNew(Function() GenerateResult()) - - or - - Dim t = Task.Factory.StartNew(Function() GenerateResult()) - - - - The class also provides constructors that initialize the task but that do not - schedule it for execution. For performance reasons, the StartNew method should be the - preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation - and scheduling must be separated, the constructors may be used, and the task's - Start - method may then be used to schedule the task for execution at a later time. - - - All members of , except for - Dispose, are thread-safe - and may be used from multiple threads concurrently. - - - - - - Represents an asynchronous operation. - - - - instances may be created in a variety of ways. The most common approach is by - using the Task type's property to retrieve a instance that can be used to create tasks for several - purposes. For example, to create a that runs an action, the factory's StartNew - method may be used: - - // C# - var t = Task.Factory.StartNew(() => DoAction()); - - ' Visual Basic - Dim t = Task.Factory.StartNew(Function() DoAction()) - - - - The class also provides constructors that initialize the Task but that do not - schedule it for execution. For performance reasons, TaskFactory's StartNew method should be the - preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation - and scheduling must be separated, the constructors may be used, and the task's - method may then be used to schedule the task for execution at a later time. - - - All members of , except for , are thread-safe - and may be used from multiple threads concurrently. - - - For operations that return values, the class - should be used. - - - For developers implementing custom debuggers, several internal and private members of Task may be - useful (these may change from release to release). The Int32 m_taskId field serves as the backing - store for the property, however accessing this field directly from a debugger may be - more efficient than accessing the same value through the property's getter method (the - s_taskIdCounter Int32 counter is used to retrieve the next available ID for a Task). Similarly, the - Int32 m_stateFlags field stores information about the current lifecycle stage of the Task, - information also accessible through the property. The m_action System.Object - field stores a reference to the Task's delegate, and the m_stateObject System.Object field stores the - async state passed to the Task by the developer. Finally, for debuggers that parse stack frames, the - InternalWait method serves a potential marker for when a Task is entering a wait operation. - - - - - - A type initializer that runs with the appropriate permissions. - - - - - Initializes a new with the specified action. - - The delegate that represents the code to execute in the Task. - The argument is null. - - - - Initializes a new with the specified action and CancellationToken. - - The delegate that represents the code to execute in the Task. - The CancellationToken - that will be assigned to the new Task. - The argument is null. - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action and creation options. - - The delegate that represents the code to execute in the task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action and creation options. - - The delegate that represents the code to execute in the task. - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action and state. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - - The argument is null. - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - The that will be assigned to the new task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - An internal constructor used by the factory methods on task and its descendent(s). - This variant does not capture the ExecutionContext; it is up to the caller to do that. - - An action to execute. - Optional state to pass to the action. - Parent of Task. - A CancellationToken for the task. - A task scheduler under which the task will run. - Options to control its execution. - Internal options to control its execution - - - - Common logic used by the following internal ctors: - Task() - Task(object action, object state, Task parent, TaskCreationOptions options, TaskScheduler taskScheduler) - - ASSUMES THAT m_creatingTask IS ALREADY SET. - - - Action for task to execute. - Object to which to pass to action (may be null) - Task scheduler on which to run thread (only used by continuation tasks). - A CancellationToken for the Task. - Options to customize behavior of Task. - Internal options to customize behavior of Task. - - - - Checks if we registered a CT callback during construction, and deregisters it. - This should be called when we know the registration isn't useful anymore. Specifically from Finish() if the task has completed - successfully or with an exception. - - - - - Captures the ExecutionContext so long as flow isn't suppressed. - - A stack crawl mark pointing to the frame of the caller. - - - - Internal function that will be called by a new child task to add itself to - the children list of the parent (this). - - Since a child task can only be created from the thread executing the action delegate - of this task, reentrancy is neither required nor supported. This should not be called from - anywhere other than the task construction/initialization codepaths. - - - - - Starts the , scheduling it for execution to the current TaskScheduler. - - - A task may only be started and run only once. Any attempts to schedule a task a second time - will result in an exception. - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Starts the , scheduling it for execution to the specified TaskScheduler. - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - The TaskScheduler with which to associate - and execute this task. - - - The argument is null. - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Runs the synchronously on the current TaskScheduler. - - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - Tasks executed with will be associated with the current TaskScheduler. - - - If the target scheduler does not support running this Task on the current thread, the Task will - be scheduled for execution on the scheduler, and the current thread will block until the - Task has completed execution. - - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Runs the synchronously on the scheduler provided. - - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - If the target scheduler does not support running this Task on the current thread, the Task will - be scheduled for execution on the scheduler, and the current thread will block until the - Task has completed execution. - - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - The parameter - is null. - The scheduler on which to attempt to run this task inline. - - - - Throws an exception if the task has been disposed, and hence can no longer be accessed. - - The task has been disposed. - - - - Sets the internal completion event. - - - - - Disposes the , releasing all of its unmanaged resources. - - - Unlike most of the members of , this method is not thread-safe. - Also, may only be called on a that is in one of - the final states: RanToCompletion, - Faulted, or - Canceled. - - - The exception that is thrown if the is not in - one of the final states: RanToCompletion, - Faulted, or - Canceled. - - - - - Disposes the , releasing all of its unmanaged resources. - - - A Boolean value that indicates whether this method is being called due to a call to . - - - Unlike most of the members of , this method is not thread-safe. - - - - - Schedules the task for execution. - - If true, TASK_STATE_STARTED bit is turned on in - an atomic fashion, making sure that TASK_STATE_CANCELED does not get set - underneath us. If false, TASK_STATE_STARTED bit is OR-ed right in. This - allows us to streamline things a bit for StartNew(), where competing cancellations - are not a problem. - - - - Adds an exception to the list of exceptions this task has thrown. - - An object representing either an Exception or a collection of Exceptions. - - - - Returns a list of exceptions by aggregating the holder's contents. Or null if - no exceptions have been thrown. - - Whether to include a TCE if cancelled. - An aggregate exception, or null if no exceptions have been caught. - - - - Throws an aggregate exception if the task contains exceptions. - - - - - Checks whether this is an attached task, and whether we are being called by the parent task. - And sets the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag based on that. - - This is meant to be used internally when throwing an exception, and when WaitAll is gathering - exceptions for tasks it waited on. If this flag gets set, the implicit wait on children - will skip exceptions to prevent duplication. - - This should only be called when this task has completed with an exception - - - - - - Signals completion of this particular task. - - The bUserDelegateExecuted parameter indicates whether this Finish() call comes following the - full execution of the user delegate. - - If bUserDelegateExecuted is false, it mean user delegate wasn't invoked at all (either due to - a cancellation request, or because this task is a promise style Task). In this case, the steps - involving child tasks (i.e. WaitForChildren) will be skipped. - - - - - - FinishStageTwo is to be executed as soon as we known there are no more children to complete. - It can happen i) either on the thread that originally executed this task (if no children were spawned, or they all completed by the time this task's delegate quit) - ii) or on the thread that executed the last child. - - - - - Final stage of the task completion code path. Notifies the parent (if any) that another of its childre are done, and runs continuations. - This function is only separated out from FinishStageTwo because these two operations are also needed to be called from CancellationCleanupLogic() - - - - - This is called by children of this task when they are completed. - - - - - This is to be called just before the task does its final state transition. - It traverses the list of exceptional children, and appends their aggregate exceptions into this one's exception list - - - - - Special purpose Finish() entry point to be used when the task delegate throws a ThreadAbortedException - This makes a note in the state flags so that we avoid any costly synchronous operations in the finish codepath - such as inlined continuations - - - Indicates whether the ThreadAbortException was added to this task's exception holder. - This should always be true except for the case of non-root self replicating task copies. - - Whether the delegate was executed. - - - - Executes the task. This method will only be called once, and handles bookeeping associated with - self-replicating tasks, in addition to performing necessary exception marshaling. - - The task has already been disposed. - - - - IThreadPoolWorkItem override, which is the entry function for this task when the TP scheduler decides to run it. - - - - - - Outermost entry function to execute this task. Handles all aspects of executing a task on the caller thread. - Currently this is called by IThreadPoolWorkItem.ExecuteWorkItem(), and TaskManager.TryExecuteInline. - - - Performs atomic updates to prevent double execution. Should only be set to true - in codepaths servicing user provided TaskSchedulers. The ConcRT or ThreadPool schedulers don't need this. - - - - The actual code which invokes the body of the task. This can be overriden in derived types. - - - - - Alternate InnerInvoke prototype to be called from ExecuteSelfReplicating() so that - the Parallel Debugger can discover the actual task being invoked. - Details: Here, InnerInvoke is actually being called on the rootTask object while we are actually executing the - childTask. And the debugger needs to discover the childTask, so we pass that down as an argument. - The NoOptimization and NoInlining flags ensure that the childTask pointer is retained, and that this - function appears on the callstack. - - - - - - Performs whatever handling is necessary for an unhandled exception. Normally - this just entails adding the exception to the holder object. - - The exception that went unhandled. - - - - Waits for the to complete execution. - - - The was canceled -or- an exception was thrown during - the execution of the . - - - The has been disposed. - - - - - Waits for the to complete execution. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - true if the completed execution within the allotted time; otherwise, false. - - - The was canceled -or- an exception was thrown during the execution of the . - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - The has been disposed. - - - - - Waits for the to complete execution. - - - A to observe while waiting for the task to complete. - - - The was canceled. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - - - Waits for the to complete execution. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - true if the completed execution within the allotted time; otherwise, - false. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - - - Waits for the to complete execution. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for the task to complete. - - - true if the completed execution within the allotted time; otherwise, false. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - The core wait function, which is only accesible internally. It's meant to be used in places in TPL code where - the current context is known or cached. - - - - - Cancels the . - - Indiactes whether we should only cancel non-invoked tasks. - For the default scheduler this option will only be serviced through TryDequeue. - For custom schedulers we also attempt an atomic state transition. - true if the task was successfully canceled; otherwise, false. - The - has been disposed. - - - - Sets the task's cancellation acknowledged flag. - - - - - Runs all of the continuations, as appropriate. - - - - - Helper function to determine whether the current task is in the state desired by the - continuation kind under evaluation. Three possibilities exist: the task failed with - an unhandled exception (OnFailed), the task was canceled before running (OnAborted), - or the task completed successfully (OnCompletedSuccessfully). Note that the last - one includes completing due to cancellation. - - The continuation options under evaluation. - True if the continuation should be run given the task's current state. - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - The that will be assigned to the new continuation task. - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Converts TaskContinuationOptions to TaskCreationOptions, and also does - some validity checking along the way. - - Incoming TaskContinuationOptions - Outgoing TaskCreationOptions - Outgoing InternalTaskOptions - - - - Registers the continuation and possibly runs it (if the task is already finished). - - The continuation task itself. - TaskScheduler with which to associate continuation task. - Restrictions on when the continuation becomes active. - - - - Waits for all of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - An array of instances on which to wait. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - A to observe while waiting for the tasks to complete. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The was canceled. - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for the tasks to complete. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - Waits for a set of handles in a STA-aware way. In other words, it will wait for each - of the events individually if we're on a STA thread, because MsgWaitForMultipleObjectsEx - can't do a true wait-all due to its hidden message queue event. This is not atomic, - of course, but we only wait on one-way (MRE) events anyway so this is OK. - - An array of wait handles to wait on. - The timeout to use during waits. - The cancellationToken that enables a wait to be canceled. - True if all waits succeeded, false if a timeout occurred. - - - - Internal WaitAll implementation which is meant to be used with small number of tasks, - optimized for Parallel.Invoke and other structured primitives. - - - - - This internal function is only meant to be called by WaitAll() - If the completed task is canceled or it has other exceptions, here we will add those - into the passed in exception list (which will be lazily initialized here). - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - The index of the completed task in the array argument. - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - A to observe while waiting for a task to complete. - - - The index of the completed task in the array argument. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - The was canceled. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for a task to complete. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - Gets a unique ID for this Task instance. - - - Task IDs are assigned on-demand and do not necessarily represent the order in the which Task - instances were created. - - - - - Returns the unique ID of the currently executing Task. - - - - - Gets the Task instance currently executing, or - null if none exists. - - - - - Gets the Exception that caused the Task to end prematurely. If the Task completed successfully or has not yet thrown any - exceptions, this will return null. - - - Tasks that throw unhandled exceptions store the resulting exception and propagate it wrapped in a - in calls to Wait - or in accesses to the property. Any exceptions not observed by the time - the Task instance is garbage collected will be propagated on the finalizer thread. - - - The Task - has been disposed. - - - - - Gets the TaskStatus of this Task. - - - - - Gets whether this Task instance has completed - execution due to being canceled. - - - A Task will complete in Canceled state either if its CancellationToken - was marked for cancellation before the task started executing, or if the task acknowledged the cancellation request on - its already signaled CancellationToken by throwing an - OperationCanceledException2 that bears the same - CancellationToken. - - - - - Returns true if this task has a cancellation token and it was signaled. - To be used internally in execute entry codepaths. - - - - - This internal property provides access to the CancellationToken that was set on the task - when it was constructed. - - - - - Gets whether this threw an OperationCanceledException2 while its CancellationToken was signaled. - - - - - Gets whether this Task has completed. - - - will return true when the Task is in one of the three - final states: RanToCompletion, - Faulted, or - Canceled. - - - - - Checks whether this task has been disposed. - - - - - Gets the TaskCreationOptions used - to create this task. - - - - - Gets a that can be used to wait for the task to - complete. - - - Using the wait functionality provided by - should be preferred over using for similar - functionality. - - - The has been disposed. - - - - - Gets the state object supplied when the Task was created, - or null if none was supplied. - - - - - Gets an indication of whether the asynchronous operation completed synchronously. - - true if the asynchronous operation completed synchronously; otherwise, false. - - - - Provides access to the TaskScheduler responsible for executing this Task. - - - - - Provides access to factory methods for creating and instances. - - - The factory returned from is a default instance - of , as would result from using - the default constructor on TaskFactory. - - - - - Provides an event that can be used to wait for completion. - Only called by Wait*(), which means that we really do need to instantiate a completion event. - - - - - Determines whether this is the root task of a self replicating group. - - - - - Determines whether the task is a replica itself. - - - - - The property formerly known as IsFaulted. - - - - - Gets whether the completed due to an unhandled exception. - - - If is true, the Task's will be equal to - TaskStatus.Faulted, and its - property will be non-null. - - - - - Checks whether the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag is set, - This will only be used by the implicit wait to prevent double throws - - - - - - Checks whether the body was ever invoked. Used by task scheduler code to verify custom schedulers actually ran the task. - - - - - A structure to hold continuation information. - - - - - Constructs a new continuation structure. - - The task to be activated. - The continuation options. - The scheduler to use for the continuation. - - - - Invokes the continuation for the target completion task. - - The completed task. - Whether the continuation can be inlined. - - - - Initializes a new with the specified function. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - - The argument is null. - - - - - Initializes a new with the specified function. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - The to be assigned to this task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified function and creation options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified function and creation options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified function and state. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the action. - - The argument is null. - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - The to be assigned to the new task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - The to be assigned to the new task. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Creates a new future object. - - The parent task for this future. - A function that yields the future value. - The task scheduler which will be used to execute the future. - The CancellationToken for the task. - Options to control the future's behavior. - Internal options to control the future's behavior. - The argument specifies - a SelfReplicating , which is illegal."/>. - - - - Creates a new future object. - - The parent task for this future. - An object containing data to be used by the action; may be null. - A function that yields the future value. - The CancellationToken for the task. - The task scheduler which will be used to execute the future. - Options to control the future's behavior. - Internal options to control the future's behavior. - The argument specifies - a SelfReplicating , which is illegal."/>. - - - - Evaluates the value selector of the Task which is passed in as an object and stores the result. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new task. - A new continuation . - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The , when executed, should return a . This task's completion state will be transferred to the task returned - from the ContinueWith call. - - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be passed as - an argument this completed task. - - The that will be assigned to the new task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The , when executed, should return a . - This task's completion state will be transferred to the task returned from the - ContinueWith call. - - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Gets the result value of this . - - - The get accessor for this property ensures that the asynchronous operation is complete before - returning. Once the result of the computation is available, it is stored and will be returned - immediately on later calls to . - - - - - Provides access to factory methods for creating instances. - - - The factory returned from is a default instance - of , as would result from using - the default constructor on the factory type. - - - - - Provides support for creating and scheduling - Task{TResult} objects. - - The type of the results that are available though - the Task{TResult} objects that are associated with - the methods in this class. - - - There are many common patterns for which tasks are relevant. The - class encodes some of these patterns into methods that pick up default settings, which are - configurable through its constructors. - - - A default instance of is available through the - Task{TResult}.Factory property. - - - - - - Initializes a instance with the default configuration. - - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the default configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The - TaskScheduler to use to schedule any tasks created with this TaskFactory{TResult}. A null value - indicates that the current TaskScheduler should be used. - - - With this constructor, the - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to , unless it's null, in which case the property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory{TResult}. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory{TResult}. - - - The exception that is thrown when the - argument or the - argument specifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory{TResult}. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory{TResult}. - - - The default - TaskScheduler to use to schedule any Tasks created with this TaskFactory{TResult}. A null value - indicates that TaskScheduler.Current should be used. - - - The exception that is thrown when the - argument or the - argumentspecifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to - , unless it's null, in which case the property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new task. - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The that will be assigned to the new task. - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Gets the default CancellationToken of this - TaskFactory. - - - This property returns the default that will be assigned to all - tasks created by this factory unless another CancellationToken value is explicitly specified - during the call to the factory methods. - - - - - Gets the TaskScheduler of this - TaskFactory{TResult}. - - - This property returns the default scheduler for this factory. It will be used to schedule all - tasks unless another scheduler is explicitly specified during calls to this factory's methods. - If null, TaskScheduler.Current - will be used. - - - - - Gets the TaskCreationOptions - value of this TaskFactory{TResult}. - - - This property returns the default creation options for this factory. They will be used to create all - tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Gets the TaskContinuationOptions - value of this TaskFactory{TResult}. - - - This property returns the default continuation options for this factory. They will be used to create - all continuation tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Represents the current stage in the lifecycle of a . - - - - - The task has been initialized but has not yet been scheduled. - - - - - The task is waiting to be activated and scheduled internally by the .NET Framework infrastructure. - - - - - The task has been scheduled for execution but has not yet begun executing. - - - - - The task is running but has not yet completed. - - - - - The task has finished executing and is implicitly waiting for - attached child tasks to complete. - - - - - The task completed execution successfully. - - - - - The task acknowledged cancellation by throwing an OperationCanceledException2 with its own CancellationToken - while the token was in signaled state, or the task's CancellationToken was already signaled before the - task started executing. - - - - - The task completed due to an unhandled exception. - - - - - Specifies flags that control optional behavior for the creation and execution of tasks. - - - - - Specifies that the default behavior should be used. - - - - - A hint to a TaskScheduler to schedule a - task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to - be run sooner, and tasks scheduled later will be more likely to be run later. - - - - - Specifies that a task will be a long-running, course-grained operation. It provides a hint to the - TaskScheduler that oversubscription may be - warranted. - - - - - Specifies that a task is attached to a parent in the task hierarchy. - - - - - Task creation flags which are only used internally. - - - - Specifies "No internal task options" - - - Used to filter out internal vs. public task creation options. - - - Specifies that the task will be queued by the runtime before handing it over to the user. - This flag will be used to skip the cancellationtoken registration step, which is only meant for unstarted tasks. - - - - Specifies flags that control optional behavior for the creation and execution of continuation tasks. - - - - - Default = "Continue on any, no task options, run asynchronously" - Specifies that the default behavior should be used. Continuations, by default, will - be scheduled when the antecedent task completes, regardless of the task's final TaskStatus. - - - - - A hint to a TaskScheduler to schedule a - task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to - be run sooner, and tasks scheduled later will be more likely to be run later. - - - - - Specifies that a task will be a long-running, course-grained operation. It provides - a hint to the TaskScheduler that - oversubscription may be warranted. - - - - - Specifies that a task is attached to a parent in the task hierarchy. - - - - - Specifies that the continuation task should not be scheduled if its antecedent ran to completion. - This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should not be scheduled if its antecedent threw an unhandled - exception. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should not be scheduled if its antecedent was canceled. This - option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent ran to - completion. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent threw an - unhandled exception. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent was canceled. - This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be executed synchronously. With this option - specified, the continuation will be run on the same thread that causes the antecedent task to - transition into its final state. If the antecedent is already complete when the continuation is - created, the continuation will run on the thread creating the continuation. Only very - short-running continuations should be executed synchronously. - - - - - Represents an exception used to communicate task cancellation. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the - class with a specified error message and a reference to the inner exception that is the cause of - this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - - Initializes a new instance of the class - with a reference to the that has been canceled. - - A task that has been canceled. - - - - Gets the task associated with this exception. - - - It is permissible for no Task to be associated with a - , in which case - this property will return null. - - - - - Represents the producer side of a unbound to a - delegate, providing access to the consumer side through the property. - - - - It is often the case that a is desired to - represent another asynchronous operation. - TaskCompletionSource is provided for this purpose. It enables - the creation of a task that can be handed out to consumers, and those consumers can use the members - of the task as they would any other. However, unlike most tasks, the state of a task created by a - TaskCompletionSource is controlled explicitly by the methods on TaskCompletionSource. This enables the - completion of the external asynchronous operation to be propagated to the underlying Task. The - separation also ensures that consumers are not able to transition the state without access to the - corresponding TaskCompletionSource. - - - All members of are thread-safe - and may be used from multiple threads concurrently. - - - The type of the result value assocatied with this . - - - - Creates a . - - - - - Creates a - with the specified options. - - - The created - by this instance and accessible through its property - will be instantiated using the specified . - - The options to use when creating the underlying - . - - The represent options invalid for use - with a . - - - - - Creates a - with the specified state. - - The state to use as the underlying - 's AsyncState. - - - - Creates a with - the specified state and options. - - The options to use when creating the underlying - . - The state to use as the underlying - 's AsyncState. - - The represent options invalid for use - with a . - - - - - Attempts to transition the underlying - into the - Faulted - state. - - The exception to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The argument is null. - The was disposed. - - - - Attempts to transition the underlying - into the - Faulted - state. - - The collection of exceptions to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The argument is null. - There are one or more null elements in . - The collection is empty. - The was disposed. - - - - Transitions the underlying - into the - Faulted - state. - - The exception to bind to this . - The argument is null. - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - Faulted - state. - - The collection of exceptions to bind to this . - The argument is null. - There are one or more null elements in . - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Attempts to transition the underlying - into the - RanToCompletion - state. - - The result value to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - RanToCompletion - state. - - The result value to bind to this . - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - Canceled - state. - - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Attempts to transition the underlying - into the - Canceled - state. - - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Gets the created - by this . - - - This property enables a consumer access to the that is controlled by this instance. - The , , - , and - methods (and their "Try" variants) on this instance all result in the relevant state - transitions on this underlying Task. - - - - - An exception holder manages a list of exceptions for one particular task. - It offers the ability to aggregate, but more importantly, also offers intrinsic - support for propagating unhandled exceptions that are never observed. It does - this by aggregating and throwing if the holder is ever GC'd without the holder's - contents ever having been requested (e.g. by a Task.Wait, Task.get_Exception, etc). - - - - - Creates a new holder; it will be registered for finalization. - - The task this holder belongs to. - - - - A finalizer that repropagates unhandled exceptions. - - - - - Add an exception to the internal list. This will ensure the holder is - in the proper state (handled/unhandled) depending on the list's contents. - - An exception object (either an Exception or an - IEnumerable{Exception}) to add to the list. - - - - A private helper method that ensures the holder is considered - unhandled, i.e. it is registered for finalization. - - - - - A private helper method that ensures the holder is considered - handled, i.e. it is not registered for finalization. - - Whether this is called from the finalizer thread. - - - - Allocates a new aggregate exception and adds the contents of the list to - it. By calling this method, the holder assumes exceptions to have been - "observed", such that the finalization check will be subsequently skipped. - - Whether this is being called from a finalizer. - An extra exception to be included (optionally). - The aggregate exception to throw. - - - - Provides a set of static (Shared in Visual Basic) methods for working with specific kinds of - instances. - - - - - Creates a proxy Task that represents the - asynchronous operation of a Task{Task}. - - - It is often useful to be able to return a Task from a - Task{TResult}, where the inner Task represents work done as part of the outer Task{TResult}. However, - doing so results in a Task{Task}, which, if not dealt with carefully, could produce unexpected behavior. Unwrap - solves this problem by creating a proxy Task that represents the entire asynchronous operation of such a Task{Task}. - - The Task{Task} to unwrap. - The exception that is thrown if the - argument is null. - A Task that represents the asynchronous operation of the provided Task{Task}. - - - - Creates a proxy Task{TResult} that represents the - asynchronous operation of a Task{Task{TResult}}. - - - It is often useful to be able to return a Task{TResult} from a Task{TResult}, where the inner Task{TResult} - represents work done as part of the outer Task{TResult}. However, doing so results in a Task{Task{TResult}}, - which, if not dealt with carefully, could produce unexpected behavior. Unwrap solves this problem by - creating a proxy Task{TResult} that represents the entire asynchronous operation of such a Task{Task{TResult}}. - - The Task{Task{TResult}} to unwrap. - The exception that is thrown if the - argument is null. - A Task{TResult} that represents the asynchronous operation of the provided Task{Task{TResult}}. /// Unwraps a Task that returns another Task. - - - - Provides support for creating and scheduling - Tasks. - - - - There are many common patterns for which tasks are relevant. The - class encodes some of these patterns into methods that pick up default settings, which are - configurable through its constructors. - - - A default instance of is available through the - Task.Factory property. - - - - - - Initializes a instance with the default configuration. - - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The - TaskScheduler to use to schedule any tasks created with this TaskFactory. A null value - indicates that the current TaskScheduler should be used. - - - With this constructor, the - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to , unless it's null, in which case the property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory. - - - The exception that is thrown when the - argument or the - argument specifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory. - - - The default - TaskScheduler to use to schedule any Tasks created with this TaskFactory. A null value - indicates that TaskScheduler.Current should be used. - - - The exception that is thrown when the - argument or the - argumentspecifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to - , unless it's null, in which case the property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The started Task. - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors - and then calling - Start to schedule it for execution. However, - unless creation and scheduling must be separated, StartNew is the recommended - approach for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The that will be assigned to the new task. - The started Task. - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors - and then calling - Start to schedule it for execution. However, - unless creation and scheduling must be separated, StartNew is the recommended - approach for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The that will be assigned to the new - A TaskCreationOptions value that controls the behavior of the - created - Task. - The TaskScheduler - that is used to schedule the created Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The started Task. - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The that will be assigned to the new - The started Task. - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The TaskScheduler - that is used to schedule the created Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the asynchronous - operation. - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the asynchronous - operation. - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the asynchronous - operation. - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Check validity of options passed to FromAsync method - - The options to be validated. - determines type of FromAsync method that called this method - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Gets the default CancellationToken of this - TaskFactory. - - - This property returns the default that will be assigned to all - tasks created by this factory unless another CancellationToken value is explicitly specified - during the call to the factory methods. - - - - - Gets the TaskScheduler of this - TaskFactory. - - - This property returns the default scheduler for this factory. It will be used to schedule all - tasks unless another scheduler is explicitly specified during calls to this factory's methods. - If null, TaskScheduler.Current - will be used. - - - - - Gets the TaskCreationOptions - value of this TaskFactory. - - - This property returns the default creation options for this factory. They will be used to create all - tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Gets the TaskContinuationOptions - value of this TaskFactory. - - - This property returns the default continuation options for this factory. They will be used to create - all continuation tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Represents an abstract scheduler for tasks. - - - - TaskScheduler acts as the extension point for all - pluggable scheduling logic. This includes mechanisms such as how to schedule a task for execution, and - how scheduled tasks should be exposed to debuggers. - - - All members of the abstract type are thread-safe - and may be used from multiple threads concurrently. - - - - - - Queues a Task to the scheduler. - - - - A class derived from TaskScheduler - implements this method to accept tasks being scheduled on the scheduler. - A typical implementation would store the task in an internal data structure, which would - be serviced by threads that would execute those tasks at some time in the future. - - - This method is only meant to be called by the .NET Framework and - should not be called directly by the derived class. This is necessary - for maintaining the consistency of the system. - - - The Task to be queued. - The argument is null. - - - - Determines whether the provided Task - can be executed synchronously in this call, and if it can, executes it. - - - - A class derived from TaskScheduler implements this function to - support inline execution of a task on a thread that initiates a wait on that task object. Inline - execution is optional, and the request may be rejected by returning false. However, better - scalability typically results the more tasks that can be inlined, and in fact a scheduler that - inlines too little may be prone to deadlocks. A proper implementation should ensure that a - request executing under the policies guaranteed by the scheduler can successfully inline. For - example, if a scheduler uses a dedicated thread to execute tasks, any inlining requests from that - thread should succeed. - - - If a scheduler decides to perform the inline execution, it should do so by calling to the base - TaskScheduler's - TryExecuteTask method with the provided task object, propagating - the return value. It may also be appropriate for the scheduler to remove an inlined task from its - internal data structures if it decides to honor the inlining request. Note, however, that under - some circumstances a scheduler may be asked to inline a task that was not previously provided to - it with the method. - - - The derived scheduler is responsible for making sure that the calling thread is suitable for - executing the given task as far as its own scheduling and execution policies are concerned. - - - The Task to be - executed. - A Boolean denoting whether or not task has previously been - queued. If this parameter is True, then the task may have been previously queued (scheduled); if - False, then the task is known not to have been queued, and this call is being made in order to - execute the task inline without queueing it. - A Boolean value indicating whether the task was executed inline. - The argument is - null. - The was already - executed. - - - - Generates an enumerable of Task instances - currently queued to the scheduler waiting to be executed. - - - - A class derived from implements this method in order to support - integration with debuggers. This method will only be invoked by the .NET Framework when the - debugger requests access to the data. The enumerable returned will be traversed by debugging - utilities to access the tasks currently queued to this scheduler, enabling the debugger to - provide a representation of this information in the user interface. - - - It is important to note that, when this method is called, all other threads in the process will - be frozen. Therefore, it's important to avoid synchronization with other threads that may lead to - blocking. If synchronization is necessary, the method should prefer to throw a - than to block, which could cause a debugger to experience delays. Additionally, this method and - the enumerable returned must not modify any globally visible state. - - - The returned enumerable should never be null. If there are currently no queued tasks, an empty - enumerable should be returned instead. - - - For developers implementing a custom debugger, this method shouldn't be called directly, but - rather this functionality should be accessed through the internal wrapper method - GetScheduledTasksForDebugger: - internal Task[] GetScheduledTasksForDebugger(). This method returns an array of tasks, - rather than an enumerable. In order to retrieve a list of active schedulers, a debugger may use - another internal method: internal static TaskScheduler[] GetTaskSchedulersForDebugger(). - This static method returns an array of all active TaskScheduler instances. - GetScheduledTasksForDebugger then may be used on each of these scheduler instances to retrieve - the list of scheduled tasks for each. - - - An enumerable that allows traversal of tasks currently queued to this scheduler. - - - This scheduler is unable to generate a list of queued tasks at this time. - - - - - Retrieves some thread static state that can be cached and passed to multiple - TryRunInline calls, avoiding superflous TLS fetches. - - A bag of TLS state (or null if none exists). - - - - Attempts to execute the target task synchronously. - - The task to run. - True if the task may have been previously queued, - false if the task was absolutely not previously queued. - The state retrieved from GetThreadStatics - True if it ran, false otherwise. - - - - Attempts to dequeue a Task that was previously queued to - this scheduler. - - The Task to be dequeued. - A Boolean denoting whether the argument was successfully dequeued. - The argument is null. - - - - Notifies the scheduler that a work item has made progress. - - - - - Initializes the . - - - - - Frees all resources associated with this scheduler. - - - - - Creates a - associated with the current . - - - All Task instances queued to - the returned scheduler will be executed through a call to the - Post method - on that context. - - - A associated with - the current SynchronizationContext, as - determined by SynchronizationContext.Current. - - - The current SynchronizationContext may not be used as a TaskScheduler. - - - - - Attempts to execute the provided Task - on this scheduler. - - - - Scheduler implementations are provided with Task - instances to be executed through either the method or the - method. When the scheduler deems it appropriate to run the - provided task, should be used to do so. TryExecuteTask handles all - aspects of executing a task, including action invocation, exception handling, state management, - and lifecycle control. - - - must only be used for tasks provided to this scheduler by the .NET - Framework infrastructure. It should not be used to execute arbitrary tasks obtained through - custom mechanisms. - - - - A Task object to be executed. - - The is not associated with this scheduler. - - A Boolean that is true if was successfully executed, false if it - was not. A common reason for execution failure is that the task had previously been executed or - is in the process of being executed by another thread. - - - - Provides an array of all queued Task instances - for the debugger. - - - The returned array is populated through a call to . - Note that this function is only meant to be invoked by a debugger remotely. - It should not be called by any other codepaths. - - An array of Task instances. - - This scheduler is unable to generate a list of queued tasks at this time. - - - - - Provides an array of all active TaskScheduler - instances for the debugger. - - - This function is only meant to be invoked by a debugger remotely. - It should not be called by any other codepaths. - - An array of TaskScheduler instances. - - - - Registers a new TaskScheduler instance in the global collection of schedulers. - - - - - Removes a TaskScheduler instance from the global collection of schedulers. - - - - - Indicates the maximum concurrency level this - is able to support. - - - - - Indicates whether this is a custom scheduler, in which case the safe code paths will be taken upon task entry - using a CAS to transition from queued state to executing. - - - - - Gets the default TaskScheduler instance. - - - - - Gets the TaskScheduler - associated with the currently executing task. - - - When not called from within a task, will return the scheduler. - - - - - Gets the unique ID for this . - - - - - Occurs when a faulted 's unobserved exception is about to trigger exception escalation - policy, which, by default, would terminate the process. - - - This AppDomain-wide event provides a mechanism to prevent exception - escalation policy (which, by default, terminates the process) from triggering. - Each handler is passed a - instance, which may be used to examine the exception and to mark it as observed. - - - - - Nested class that provides debugger view for TaskScheduler - - - - Default thread pool scheduler. - - - - A TaskScheduler implementation that executes all tasks queued to it through a call to - on the - that its associated with. The default constructor for this class binds to the current - - - - - Constructs a SynchronizationContextTaskScheduler associated with - - This constructor expects to be set. - - - - Implemetation of for this scheduler class. - - Simply posts the tasks to be executed on the associated . - - - - - - Implementation of for this scheduler class. - - The task will be executed inline only if the call happens within - the associated . - - - - - - - Implementes the property for - this scheduler class. - - By default it returns 1, because a based - scheduler only supports execution on a single thread. - - - - - Provides data for the event that is raised when a faulted 's - exception goes unobserved. - - - The Exception property is used to examine the exception without marking it - as observed, whereas the method is used to mark the exception - as observed. Marking the exception as observed prevents it from triggering exception escalation policy - which, by default, terminates the process. - - - - - Initializes a new instance of the class - with the unobserved exception. - - The Exception that has gone unobserved. - - - - Marks the as "observed," thus preventing it - from triggering exception escalation policy which, by default, terminates the process. - - - - - Gets whether this exception has been marked as "observed." - - - - - The Exception that went unobserved. - - - - - Represents an exception used to communicate an invalid operation by a - . - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the - class using the default error message and a reference to the inner exception that is the cause of - this exception. - - The exception that is the cause of the current exception. - - - - Initializes a new instance of the - class with a specified error message and a reference to the inner exception that is the cause of - this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/ensureRedirect.xml b/packages/Microsoft.Bcl.1.1.10/lib/sl4-windowsphone71/ensureRedirect.xml deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.IO.dll b/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.IO.dll deleted file mode 100644 index 32dd41b78..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.IO.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.IO.xml b/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.IO.xml deleted file mode 100644 index e83273427..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.IO.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - System.IO - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Found invalid data while decoding.. - - - - - The exception that is thrown when a data stream is in an invalid format. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a reference to the inner exception that is the cause of this exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. If the parameter is not null, the current exception is raised in a catch block that handles the inner exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.Runtime.dll b/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.Runtime.dll deleted file mode 100644 index 118fcce20..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.Runtime.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.Runtime.xml b/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.Runtime.xml deleted file mode 100644 index 93cb00d74..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.Runtime.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - System.Runtime - - - - Defines a provider for progress updates. - The type of progress update value. - - - Reports a progress update. - The value of the updated progress. - - - Identities the async state machine type for this method. - - - Identities the state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - Gets the type that implements the state machine. - - - Initializes the attribute. - The type that implements the state machine. - - - - Allows you to obtain the method or property name of the caller to the method. - - - - - Allows you to obtain the line number in the source file at which the method is called. - - - - - Allows you to obtain the full path of the source file that contains the caller. - This is the file path at the time of compile. - - - - Identities the iterator state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.Threading.Tasks.dll b/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.Threading.Tasks.dll deleted file mode 100644 index a089d474d..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.Threading.Tasks.xml b/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.Threading.Tasks.xml deleted file mode 100644 index 6c770122e..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/sl4/System.Threading.Tasks.xml +++ /dev/null @@ -1,8969 +0,0 @@ - - - - System.Threading.Tasks - - - - Represents one or more errors that occur during application execution. - - is used to consolidate multiple failures into a single, throwable - exception object. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with - a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a specified error - message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - The argument - is null. - - - - Initializes a new instance of the class with - references to the inner exceptions that are the cause of this exception. - - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with - references to the inner exceptions that are the cause of this exception. - - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with a specified error - message and references to the inner exceptions that are the cause of this exception. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Initializes a new instance of the class with a specified error - message and references to the inner exceptions that are the cause of this exception. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Allocates a new aggregate exception with the specified message and list of inner exceptions. - - The error message that explains the reason for the exception. - The exceptions that are the cause of the current exception. - The argument - is null. - An element of is - null. - - - - Returns the that is the root cause of this exception. - - - - - Invokes a handler on each contained by this . - - The predicate to execute for each exception. The predicate accepts as an - argument the to be processed and returns a Boolean to indicate - whether the exception was handled. - - Each invocation of the returns true or false to indicate whether the - was handled. After all invocations, if any exceptions went - unhandled, all unhandled exceptions will be put into a new - which will be thrown. Otherwise, the method simply returns. If any - invocations of the throws an exception, it will halt the processing - of any more exceptions and immediately propagate the thrown exception as-is. - - An exception contained by this was not handled. - The argument is - null. - - - - Flattens an instances into a single, new instance. - - A new, flattened . - - If any inner exceptions are themselves instances of - , this method will recursively flatten all of them. The - inner exceptions returned in the new - will be the union of all of the the inner exceptions from exception tree rooted at the provided - instance. - - - - - Creates and returns a string representation of the current . - - A string representation of the current exception. - - - - Gets a read-only collection of the instances that caused the - current exception. - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to One or more errors occurred.. - - - - - Looks up a localized string similar to An element of innerExceptions was null.. - - - - - Looks up a localized string similar to {0}{1}---> (Inner Exception #{2}) {3}{4}{5}. - - - - - Looks up a localized string similar to No tokens were supplied.. - - - - - Looks up a localized string similar to The CancellationTokenSource associated with this CancellationToken has been disposed.. - - - - - Looks up a localized string similar to The CancellationTokenSource has been disposed.. - - - - - Looks up a localized string similar to The SyncRoot property may not be used for the synchronization of concurrent collections.. - - - - - Looks up a localized string similar to The array is multidimensional, or the type parameter for the set cannot be cast automatically to the type of the destination array.. - - - - - Looks up a localized string similar to The index is equal to or greater than the length of the array, or the number of elements in the dictionary is greater than the available space from index to the end of the destination array.. - - - - - Looks up a localized string similar to The capacity argument must be greater than or equal to zero.. - - - - - Looks up a localized string similar to The concurrencyLevel argument must be positive.. - - - - - Looks up a localized string similar to The index argument is less than zero.. - - - - - Looks up a localized string similar to TKey is a reference type and item.Key is null.. - - - - - Looks up a localized string similar to The key already existed in the dictionary.. - - - - - Looks up a localized string similar to The source argument contains duplicate keys.. - - - - - Looks up a localized string similar to The key was of an incorrect type for this dictionary.. - - - - - Looks up a localized string similar to The value was of an incorrect type for this dictionary.. - - - - - Looks up a localized string similar to The lazily-initialized type does not have a public, parameterless constructor.. - - - - - Looks up a localized string similar to ValueFactory returned null.. - - - - - Looks up a localized string similar to The spinCount argument must be in the range 0 to {0}, inclusive.. - - - - - Looks up a localized string similar to There are too many threads currently waiting on the event. A maximum of {0} waiting threads are supported.. - - - - - Looks up a localized string similar to The event has been disposed.. - - - - - Looks up a localized string similar to The operation was canceled.. - - - - - Looks up a localized string similar to The condition argument is null.. - - - - - Looks up a localized string similar to The timeout must represent a value between -1 and Int32.MaxValue, inclusive.. - - - - - Looks up a localized string similar to The specified TaskContinuationOptions combined LongRunning and ExecuteSynchronously. Synchronous continuations should not be long running.. - - - - - Looks up a localized string similar to The specified TaskContinuationOptions excluded all continuation kinds.. - - - - - Looks up a localized string similar to (Internal)An attempt was made to create a LongRunning SelfReplicating task.. - - - - - Looks up a localized string similar to The value needs to translate in milliseconds to -1 (signifying an infinite timeout), 0 or a positive integer less than or equal to Int32.MaxValue.. - - - - - Looks up a localized string similar to The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer.. - - - - - Looks up a localized string similar to A task may only be disposed if it is in a completion state (RanToCompletion, Faulted or Canceled).. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.LongRunning in calls to FromAsync.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.PreferFairness in calls to FromAsync.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.SelfReplicating in calls to FromAsync.. - - - - - Looks up a localized string similar to FromAsync was called with a TaskManager that had already shut down.. - - - - - Looks up a localized string similar to The tasks argument contains no tasks.. - - - - - Looks up a localized string similar to It is invalid to exclude specific continuation kinds for continuations off of multiple tasks.. - - - - - Looks up a localized string similar to The tasks argument included a null value.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task that was already started.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a continuation task.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.. - - - - - Looks up a localized string similar to RunSynchronously may not be called on a task that has already completed.. - - - - - Looks up a localized string similar to Start may not be called on a task that was already started.. - - - - - Looks up a localized string similar to Start may not be called on a continuation task.. - - - - - Looks up a localized string similar to Start may not be called on a task with null action.. - - - - - Looks up a localized string similar to Start may not be called on a promise-style task.. - - - - - Looks up a localized string similar to Start may not be called on a task that has completed.. - - - - - Looks up a localized string similar to The task has been disposed.. - - - - - Looks up a localized string similar to The tasks array included at least one null element.. - - - - - Looks up a localized string similar to The awaited task has not yet completed.. - - - - - Looks up a localized string similar to A task was canceled.. - - - - - Looks up a localized string similar to The exceptions collection was empty.. - - - - - Looks up a localized string similar to The exceptions collection included at least one null element.. - - - - - Looks up a localized string similar to A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread.. - - - - - Looks up a localized string similar to (Internal)Expected an Exception or an IEnumerable<Exception>. - - - - - Looks up a localized string similar to ExecuteTask may not be called for a task which was already executed.. - - - - - Looks up a localized string similar to ExecuteTask may not be called for a task which was previously queued to a different TaskScheduler.. - - - - - Looks up a localized string similar to The current SynchronizationContext may not be used as a TaskScheduler.. - - - - - Looks up a localized string similar to The TryExecuteTaskInline call to the underlying scheduler succeeded, but the task body was not invoked.. - - - - - Looks up a localized string similar to An exception was thrown by a TaskScheduler.. - - - - - Looks up a localized string similar to It is invalid to specify TaskCreationOptions.SelfReplicating for a Task<TResult>.. - - - - - Looks up a localized string similar to {Not yet computed}. - - - - - Looks up a localized string similar to A task's Exception may only be set directly if the task was created without a function.. - - - - - Looks up a localized string similar to An attempt was made to transition a task to a final state when it had already completed.. - - - - - Represents a thread-safe collection of keys and values. - - The type of the keys in the dictionary. - The type of the values in the dictionary. - - All public and protected members of are thread-safe and may be used - concurrently from multiple threads. - - - - - Initializes a new instance of the - class that is empty, has the default concurrency level, has the default initial capacity, and - uses the default comparer for the key type. - - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level and capacity, and uses the default - comparer for the key type. - - The estimated number of threads that will update the - concurrently. - The initial number of elements that the - can contain. - is - less than 1. - is less than - 0. - - - - Initializes a new instance of the - class that contains elements copied from the specified , has the default concurrency - level, has the default initial capacity, and uses the default comparer for the key type. - - The whose elements are copied to - the new - . - is a null reference - (Nothing in Visual Basic). - contains one or more - duplicate keys. - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level and capacity, and uses the specified - . - - The - implementation to use when comparing keys. - is a null reference - (Nothing in Visual Basic). - - - - Initializes a new instance of the - class that contains elements copied from the specified , has the default concurrency level, has the default - initial capacity, and uses the specified - . - - The whose elements are copied to - the new - . - The - implementation to use when comparing keys. - is a null reference - (Nothing in Visual Basic). -or- - is a null reference (Nothing in Visual Basic). - - - - - Initializes a new instance of the - class that contains elements copied from the specified , - has the specified concurrency level, has the specified initial capacity, and uses the specified - . - - The estimated number of threads that will update the - concurrently. - The whose elements are copied to the new - . - The implementation to use - when comparing keys. - - is a null reference (Nothing in Visual Basic). - -or- - is a null reference (Nothing in Visual Basic). - - - is less than 1. - - contains one or more duplicate keys. - - - - Initializes a new instance of the - class that is empty, has the specified concurrency level, has the specified initial capacity, and - uses the specified . - - The estimated number of threads that will update the - concurrently. - The initial number of elements that the - can contain. - The - implementation to use when comparing keys. - - is less than 1. -or- - is less than 0. - - is a null reference - (Nothing in Visual Basic). - - - - Attempts to add the specified key and value to the . - - The key of the element to add. - The value of the element to add. The value can be a null reference (Nothing - in Visual Basic) for reference types. - true if the key/value pair was added to the - successfully; otherwise, false. - is null reference - (Nothing in Visual Basic). - The - contains too many elements. - - - - Determines whether the contains the specified - key. - - The key to locate in the . - true if the contains an element with - the specified key; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Attempts to remove and return the the value with the specified key from the - . - - The key of the element to remove and return. - When this method returns, contains the object removed from the - or the default value of - if the operation failed. - true if an object was removed successfully; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Removes the specified key from the dictionary if it exists and returns its associated value. - If matchValue flag is set, the key will be removed only if is associated with a particular - value. - - The key to search for and remove if it exists. - The variable into which the removed value, if found, is stored. - Whether removal of the key is conditional on its value. - The conditional value to compare against if is true - - - - - Attempts to get the value associated with the specified key from the . - - The key of the value to get. - When this method returns, contains the object from - the - with the spedified key or the default value of - , if the operation failed. - true if the key was found in the ; - otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - - Compares the existing value for the specified key with a specified value, and if they’re equal, - updates the key with a third value. - - The key whose value is compared with and - possibly replaced. - The value that replaces the value of the element with if the comparison results in equality. - The value that is compared to the value of the element with - . - true if the value with was equal to and replaced with ; otherwise, - false. - is a null - reference. - - - - Removes all keys and values from the . - - - - - Copies the elements of the to an array of - type , starting at the - specified array index. - - The one-dimensional array of type - that is the destination of the elements copied from the . The array must have zero-based indexing. - The zero-based index in at which copying - begins. - is a null reference - (Nothing in Visual Basic). - is less than - 0. - is equal to or greater than - the length of the . -or- The number of elements in the source - is greater than the available space from to the end of the destination - . - - - - Copies the key and value pairs stored in the to a - new array. - - A new array containing a snapshot of key and value pairs copied from the . - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToPairs. - - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToEntries. - - - - - Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. - - Important: the caller must hold all locks in m_locks before calling CopyToObjects. - - - - Returns an enumerator that iterates through the . - An enumerator for the . - - The enumerator returned from the dictionary is safe to use concurrently with - reads and writes to the dictionary, however it does not represent a moment-in-time snapshot - of the dictionary. The contents exposed through the enumerator may contain modifications - made to the dictionary after was called. - - - - - Shared internal implementation for inserts and updates. - If key exists, we always return false; and if updateIfExists == true we force update with value; - If key doesn't exist, we always add value and return true; - - - - - Adds a key/value pair to the - if the key does not already exist. - - The key of the element to add. - The function used to generate a value for the key - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The value for the key. This will be either the existing value for the key if the - key is already in the dictionary, or the new value for the key as returned by valueFactory - if the key was not in the dictionary. - - - - Adds a key/value pair to the - if the key does not already exist. - - The key of the element to add. - the value to be added, if the key does not already exist - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The value for the key. This will be either the existing value for the key if the - key is already in the dictionary, or the new value if the key was not in the dictionary. - - - - Adds a key/value pair to the if the key does not already - exist, or updates a key/value pair in the if the key - already exists. - - The key to be added or whose value should be updated - The function used to generate a value for an absent key - The function used to generate a new value for an existing key - based on the key's existing value - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The new value for the key. This will be either be the result of addValueFactory (if the key was - absent) or the result of updateValueFactory (if the key was present). - - - - Adds a key/value pair to the if the key does not already - exist, or updates a key/value pair in the if the key - already exists. - - The key to be added or whose value should be updated - The value to be added for an absent key - The function used to generate a new value for an existing key based on - the key's existing value - is a null reference - (Nothing in Visual Basic). - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - The new value for the key. This will be either be the result of addValueFactory (if the key was - absent) or the result of updateValueFactory (if the key was present). - - - - Adds the specified key and value to the . - - The object to use as the key of the element to add. - The object to use as the value of the element to add. - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - - An element with the same key already exists in the . - - - - Removes the element with the specified key from the . - - The key of the element to remove. - true if the element is successfully remove; otherwise false. This method also returns - false if - was not found in the original . - - is a null reference - (Nothing in Visual Basic). - - - - Adds the specified value to the - with the specified key. - - The - structure representing the key and value to add to the . - The of is null. - The - contains too many elements. - An element with the same key already exists in the - - - - - Determines whether the - contains a specific key and value. - - The - structure to locate in the . - true if the is found in the ; otherwise, false. - - - - Removes a key and value from the dictionary. - - The - structure representing the key and value to remove from the . - true if the key and value represented by is successfully - found and removed; otherwise, false. - The Key property of is a null reference (Nothing in Visual Basic). - - - Returns an enumerator that iterates through the . - An enumerator for the . - - The enumerator returned from the dictionary is safe to use concurrently with - reads and writes to the dictionary, however it does not represent a moment-in-time snapshot - of the dictionary. The contents exposed through the enumerator may contain modifications - made to the dictionary after was called. - - - - - Adds the specified key and value to the dictionary. - - The object to use as the key. - The object to use as the value. - is a null reference - (Nothing in Visual Basic). - The dictionary contains too many - elements. - - is of a type that is not assignable to the key type of the . -or- - is of a type that is not assignable to , - the type of values in the . - -or- A value with the same key already exists in the . - - - - - Gets whether the contains an - element with the specified key. - - The key to locate in the . - true if the contains - an element with the specified key; otherwise, false. - is a null reference - (Nothing in Visual Basic). - - - Provides an for the - . - An for the . - - - - Removes the element with the specified key from the . - - The key of the element to remove. - is a null reference - (Nothing in Visual Basic). - - - - Copies the elements of the to an array, starting - at the specified array index. - - The one-dimensional array that is the destination of the elements copied from - the . The array must have zero-based - indexing. - The zero-based index in at which copying - begins. - is a null reference - (Nothing in Visual Basic). - is less than - 0. - is equal to or greater than - the length of the . -or- The number of elements in the source - is greater than the available space from to the end of the destination - . - - - - Replaces the internal table with a larger one. To prevent multiple threads from resizing the - table as a result of races, the table of buckets that was deemed too small is passed in as - an argument to GrowTable(). GrowTable() obtains a lock, and then checks whether the bucket - table has been replaced in the meantime or not. - - Reference to the bucket table that was deemed too small. - - - - Computes the bucket and lock number for a particular key. - - - - - Acquires all locks for this hash table, and increments locksAcquired by the number - of locks that were successfully acquired. The locks are acquired in an increasing - order. - - - - - Acquires a contiguous range of locks for this hash table, and increments locksAcquired - by the number of locks that were successfully acquired. The locks are acquired in an - increasing order. - - - - - Releases a contiguous range of locks. - - - - - Gets a collection containing the keys in the dictionary. - - - - - Gets a collection containing the values in the dictionary. - - - - - A helper method for asserts. - - - - - Get the data array to be serialized - - - - - Construct the dictionary from a previously seiralized one - - - - - Gets or sets the value associated with the specified key. - - The key of the value to get or set. - The value associated with the specified key. If the specified key is not found, a get - operation throws a - , and a set operation creates a new - element with the specified key. - is a null reference - (Nothing in Visual Basic). - The property is retrieved and - - does not exist in the collection. - - - - Gets the number of key/value pairs contained in the . - - The dictionary contains too many - elements. - The number of key/value paris contained in the . - Count has snapshot semantics and represents the number of items in the - at the moment when Count was accessed. - - - - Gets a value that indicates whether the is empty. - - true if the is empty; otherwise, - false. - - - - Gets a collection containing the keys in the . - - An containing the keys in the - . - - - - Gets a collection containing the values in the . - - An containing the values in - the - . - - - - Gets a value indicating whether the dictionary is read-only. - - true if the is - read-only; otherwise, false. For , this property always returns - false. - - - - Gets a value indicating whether the has a fixed size. - - true if the has a - fixed size; otherwise, false. For , this property always - returns false. - - - - Gets a value indicating whether the is read-only. - - true if the is - read-only; otherwise, false. For , this property always - returns false. - - - - Gets an containing the keys of the . - - An containing the keys of the . - - - - Gets an containing the values in the . - - An containing the values in the . - - - - Gets or sets the value associated with the specified key. - - The key of the value to get or set. - The value associated with the specified key, or a null reference (Nothing in Visual Basic) - if is not in the dictionary or is of a type that is - not assignable to the key type of the . - is a null reference - (Nothing in Visual Basic). - - A value is being assigned, and is of a type that is not assignable to the - key type of the . -or- A value is being - assigned, and is of a type that is not assignable to the value type - of the - - - - - Gets a value indicating whether access to the is - synchronized with the SyncRoot. - - true if access to the is synchronized - (thread safe); otherwise, false. For , this property always - returns false. - - - - Gets an object that can be used to synchronize access to the . This property is not supported. - - The SyncRoot property is not supported. - - - - The number of concurrent writes for which to optimize by default. - - - - - A node in a singly-linked list representing a particular hash table bucket. - - - - - A private class to represent enumeration over the dictionary that implements the - IDictionaryEnumerator interface. - - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - Represents an asynchronous method builder. - - - A cached VoidTaskResult task used for builders that complete synchronously. - - - The generic builder object to which this non-generic instance delegates. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state. - - The builder is not initialized. - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - - Gets the for this builder. - The representing the builder's asynchronous operation. - The builder is not initialized. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - Holds state related to the builder's IAsyncStateMachine. - This is a mutable struct. Be very delicate with it. - - - A reference to the heap-allocated state machine object associated with this builder. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument is null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - - Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method. - On first invocation, the supplied state machine will be boxed. - - Specifies the type of the method builder used. - Specifies the type of the state machine used. - The builder. - The state machine. - An Action to provide to the awaiter. - - - Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext. - - - The context with which to run MoveNext. - - - The state machine whose MoveNext method should be invoked. - - - Initializes the runner. - The context with which to run MoveNext. - - - Invokes MoveNext under the provided context. - - - Cached delegate used with ExecutionContext.Run. - - - Invokes the MoveNext method on the supplied IAsyncStateMachine. - The IAsyncStateMachine machine instance. - - - - Provides a builder for asynchronous methods that return void. - This type is intended for compiler use only. - - - - The synchronization context associated with this operation. - - - State related to the IAsyncStateMachine. - - - An object used by the debugger to uniquely identify this builder. Lazily initialized. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Registers with UnobservedTaskException to suppress exception crashing. - - - Non-zero if PreventUnobservedTaskExceptions has already been invoked. - - - Initializes a new . - The initialized . - - - Initializes the . - The synchronizationContext associated with this operation. This may be null. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument was null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - Completes the method builder successfully. - - - Faults the method builder with an exception. - The exception that is the cause of this fault. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - - - Notifies the current synchronization context that the operation completed. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger and only in a single-threaded manner. - - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - A cached task for default(TResult). - - - State related to the IAsyncStateMachine. - - - The lazily-initialized task. - Must be named m_task for debugger step-over to work correctly. - - - The lazily-initialized task completion source. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state with the specified result. - - The result to use to complete the task. - The task has already completed. - - - - Completes the builder by using either the supplied completed task, or by completing - the builder's previously accessed task using default(TResult). - - A task already completed with the value default(TResult). - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - This should only be invoked from within an asynchronous method, - and only by the debugger. - - - - - Gets a task for the specified result. This will either - be a cached or new task, never null. - - The result for which we need a task. - The completed task containing the result. - - - Gets the lazily-initialized TaskCompletionSource. - - - Gets the for this builder. - The representing the builder's asynchronous operation. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - Provides a base class used to cache tasks of a specific return type. - Specifies the type of results the cached tasks return. - - - - A singleton cache for this result type. - This may be null if there are no cached tasks for this TResult. - - - - Creates a non-disposable task. - The result for the task. - The cacheable task. - - - Creates a cache. - A task cache for this result type. - - - Gets a cached task if one exists. - The result for which we want a cached task. - A cached task if one exists; otherwise, null. - - - Provides a cache for Boolean tasks. - - - A true task. - - - A false task. - - - Gets a cached task for the Boolean result. - true or false - A cached task for the Boolean result. - - - Provides a cache for zero Int32 tasks. - - - The minimum value, inclusive, for which we want a cached task. - - - The maximum value, exclusive, for which we want a cached task. - - - The cache of Task{Int32}. - - - Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). - - - Gets a cached task for the zero Int32 result. - The integer value - A cached task for the Int32 result or null if not cached. - - - - Represents state machines generated for asynchronous methods. - This type is intended for compiler use only. - - - - Moves the state machine to its next state. - - - Configures the state machine with a heap-allocated replica. - The heap-allocated replica. - - - - Represents an awaiter used to schedule continuations when an await operation completes. - - - - - Represents an operation that will schedule continuations when the operation completes. - - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information. - - - Used with Task(of void) - - - - An interface similar to the one added in .NET 4.0. - - - - The exception that is thrown in a thread upon cancellation of an operation that the thread was executing. - - - Initializes the exception. - - - Initializes the exception. - The error message that explains the reason for the exception. - - - Initializes the exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - Initializes the exception. - A cancellation token associated with the operation that was canceled. - - - Initializes the exception. - The error message that explains the reason for the exception. - A cancellation token associated with the operation that was canceled. - - - Initializes the exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - A cancellation token associated with the operation that was canceled. - - - Gets a token associated with the operation that was canceled. - - - - A dummy replacement for the .NET internal class StackCrawlMark. - - - - - Propogates notification that operations should be canceled. - - - - A may be created directly in an unchangeable canceled or non-canceled state - using the CancellationToken's constructors. However, to have a CancellationToken that can change - from a non-canceled to a canceled state, - CancellationTokenSource must be used. - CancellationTokenSource exposes the associated CancellationToken that may be canceled by the source through its - Token property. - - - Once canceled, a token may not transition to a non-canceled state, and a token whose - is false will never change to one that can be canceled. - - - All members of this struct are thread-safe and may be used concurrently from multiple threads. - - - - - - Internal constructor only a CancellationTokenSource should create a CancellationToken - - - - - Initializes the CancellationToken. - - - The canceled state for the token. - - - Tokens created with this constructor will remain in the canceled state specified - by the parameter. If is false, - both and will be false. - If is true, - both and will be true. - - - - - Registers a delegate that will be called when this CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - A Boolean value that indicates whether to capture - the current SynchronizationContext and use it - when invoking the . - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The state to pass to the when the delegate is invoked. This may be null. - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Registers a delegate that will be called when this - CancellationToken is canceled. - - - - If this token is already in the canceled state, the - delegate will be run immediately and synchronously. Any exception the delegate generates will be - propogated out of this method call. - - - The delegate to be executed when the CancellationToken is canceled. - The state to pass to the when the delegate is invoked. This may be null. - A Boolean value that indicates whether to capture - the current SynchronizationContext and use it - when invoking the . - The instance that can - be used to deregister the callback. - is null. - The associated CancellationTokenSource has been disposed. - - - - Determines whether the current CancellationToken instance is equal to the - specified token. - - The other CancellationToken to which to compare this - instance. - True if the instances are equal; otherwise, false. Two tokens are equal if they are associated - with the same CancellationTokenSource or if they were both constructed - from public CancellationToken constructors and their values are equal. - - - - Determines whether the current CancellationToken instance is equal to the - specified . - - The other object to which to compare this instance. - True if is a CancellationToken - and if the two instances are equal; otherwise, false. Two tokens are equal if they are associated - with the same CancellationTokenSource or if they were both constructed - from public CancellationToken constructors and their values are equal. - An associated CancellationTokenSource has been disposed. - - - - Serves as a hash function for a CancellationToken. - - A hash code for the current CancellationToken instance. - - - - Determines whether two CancellationToken instances are equal. - - The first instance. - The second instance. - True if the instances are equal; otherwise, false. - An associated CancellationTokenSource has been disposed. - - - - Determines whether two CancellationToken instances are not equal. - - The first instance. - The second instance. - True if the instances are not equal; otherwise, false. - An associated CancellationTokenSource has been disposed. - - - - Throws a OperationCanceledException if - this token has had cancellation requested. - - - This method provides functionality equivalent to: - - if (token.IsCancellationRequested) - throw new OperationCanceledException(token); - - - The token has had cancellation requested. - The associated CancellationTokenSource has been disposed. - - - - Returns an empty CancellationToken value. - - - The value returned by this property will be non-cancelable by default. - - - - - Gets whether cancellation has been requested for this token. - - Whether cancellation has been requested for this token. - - - This property indicates whether cancellation has been requested for this token, - either through the token initially being construted in a canceled state, or through - calling Cancel - on the token's associated . - - - If this property is true, it only guarantees that cancellation has been requested. - It does not guarantee that every registered handler - has finished executing, nor that cancellation requests have finished propagating - to all registered handlers. Additional synchronization may be required, - particularly in situations where related objects are being canceled concurrently. - - - - - - Gets whether this token is capable of being in the canceled state. - - - If CanBeCanceled returns false, it is guaranteed that the token will never transition - into a canceled state, meaning that will never - return true. - - - - - Gets a that is signaled when the token is canceled. - - Accessing this property causes a WaitHandle - to be instantiated. It is preferable to only use this property when necessary, and to then - dispose the associated instance at the earliest opportunity (disposing - the source will dispose of this allocated handle). The handle should not be closed or disposed directly. - - The associated CancellationTokenSource has been disposed. - - - - Represents a callback delegate that has been registered with a CancellationToken. - - - To unregister a callback, dispose the corresponding Registration instance. - - - - - Attempts to deregister the item. If it's already being run, this may fail. - Entails a full memory fence. - - True if the callback was found and deregistered, false otherwise. - - - - Disposes of the registration and unregisters the target callback from the associated - CancellationToken. - If the target callback is currently executing this method will wait until it completes, except - in the degenerate cases where a callback method deregisters itself. - - - - - Determines whether two CancellationTokenRegistration - instances are equal. - - The first instance. - The second instance. - True if the instances are equal; otherwise, false. - - - - Determines whether two CancellationTokenRegistration instances are not equal. - - The first instance. - The second instance. - True if the instances are not equal; otherwise, false. - - - - Determines whether the current CancellationTokenRegistration instance is equal to the - specified . - - The other object to which to compare this instance. - True, if both this and are equal. False, otherwise. - Two CancellationTokenRegistration instances are equal if - they both refer to the output of a single call to the same Register method of a - CancellationToken. - - - - - Determines whether the current CancellationToken instance is equal to the - specified . - - The other CancellationTokenRegistration to which to compare this instance. - True, if both this and are equal. False, otherwise. - Two CancellationTokenRegistration instances are equal if - they both refer to the output of a single call to the same Register method of a - CancellationToken. - - - - - Serves as a hash function for a CancellationTokenRegistration.. - - A hash code for the current CancellationTokenRegistration instance. - - - - Signals to a that it should be canceled. - - - - is used to instantiate a - (via the source's Token property) - that can be handed to operations that wish to be notified of cancellation or that can be used to - register asynchronous operations for cancellation. That token may have cancellation requested by - calling to the source's Cancel - method. - - - All members of this class, except Dispose, are thread-safe and may be used - concurrently from multiple threads. - - - - - The ID of the thread currently executing the main body of CTS.Cancel() - this helps us to know if a call to ctr.Dispose() is running 'within' a cancellation callback. - This is updated as we move between the main thread calling cts.Cancel() and any syncContexts that are used to - actually run the callbacks. - - - - Initializes the . - - - - - Communicates a request for cancellation. - - - - The associated will be - notified of the cancellation and will transition to a state where - IsCancellationRequested returns true. - Any callbacks or cancelable operations - registered with the will be executed. - - - Cancelable operations and callbacks registered with the token should not throw exceptions. - However, this overload of Cancel will aggregate any exceptions thrown into a , - such that one callback throwing an exception will not prevent other registered callbacks from being executed. - - - The that was captured when each callback was registered - will be reestablished when the callback is invoked. - - - An aggregate exception containing all the exceptions thrown - by the registered callbacks on the associated . - This has been disposed. - - - - Communicates a request for cancellation. - - - - The associated will be - notified of the cancellation and will transition to a state where - IsCancellationRequested returns true. - Any callbacks or cancelable operations - registered with the will be executed. - - - Cancelable operations and callbacks registered with the token should not throw exceptions. - If is true, an exception will immediately propagate out of the - call to Cancel, preventing the remaining callbacks and cancelable operations from being processed. - If is false, this overload will aggregate any - exceptions thrown into a , - such that one callback throwing an exception will not prevent other registered callbacks from being executed. - - - The that was captured when each callback was registered - will be reestablished when the callback is invoked. - - - Specifies whether exceptions should immediately propagate. - An aggregate exception containing all the exceptions thrown - by the registered callbacks on the associated . - This has been disposed. - - - - Releases the resources used by this . - - - This method is not thread-safe for any other concurrent calls. - - - - - Throws an exception if the source has been disposed. - - - - - InternalGetStaticSource() - - Whether the source should be set. - A static source to be shared among multiple tokens. - - - - Registers a callback object. If cancellation has already occurred, the - callback will have been run by the time this method returns. - - - - - - - - - - Invoke the Canceled event. - - - The handlers are invoked synchronously in LIFO order. - - - - - Creates a CancellationTokenSource that will be in the canceled state - when any of the source tokens are in the canceled state. - - The first CancellationToken to observe. - The second CancellationToken to observe. - A CancellationTokenSource that is linked - to the source tokens. - A CancellationTokenSource associated with - one of the source tokens has been disposed. - - - - Creates a CancellationTokenSource that will be in the canceled state - when any of the source tokens are in the canceled state. - - The CancellationToken instances to observe. - A CancellationTokenSource that is linked - to the source tokens. - is null. - A CancellationTokenSource associated with - one of the source tokens has been disposed. - - - - Gets whether cancellation has been requested for this CancellationTokenSource. - - Whether cancellation has been requested for this CancellationTokenSource. - - - This property indicates whether cancellation has been requested for this token source, such as - due to a call to its - Cancel method. - - - If this property returns true, it only guarantees that cancellation has been requested. It does not - guarantee that every handler registered with the corresponding token has finished executing, nor - that cancellation requests have finished propagating to all registered handlers. Additional - synchronization may be required, particularly in situations where related objects are being - canceled concurrently. - - - - - - A simple helper to determine whether cancellation has finished. - - - - - A simple helper to determine whether disposal has occured. - - - - - The ID of the thread that is running callbacks. - - - - - Gets the CancellationToken - associated with this . - - The CancellationToken - associated with this . - The token source has been - disposed. - - - - - - - - - - - - - - The currently executing callback - - - - - A helper class for collating the various bits of information required to execute - cancellation callbacks. - - - - - InternalExecuteCallbackSynchronously_GeneralPath - This will be called on the target synchronization context, however, we still need to restore the required execution context - - - - - A sparsely populated array. Elements can be sparse and some null, but this allows for - lock-free additions and growth, and also for constant time removal (by nulling out). - - The kind of elements contained within. - - - - Allocates a new array with the given initial size. - - How many array slots to pre-allocate. - - - - Adds an element in the first available slot, beginning the search from the tail-to-head. - If no slots are available, the array is grown. The method doesn't return until successful. - - The element to add. - Information about where the add happened, to enable O(1) deregistration. - - - - The tail of the doubly linked list. - - - - - A struct to hold a link to the exact spot in an array an element was inserted, enabling - constant time removal later on. - - - - - A fragment of a sparsely populated array, doubly linked. - - The kind of elements contained within. - - - - Provides lazy initialization routines. - - - These routines avoid needing to allocate a dedicated, lazy-initialization instance, instead using - references to ensure targets have been initialized as they are accessed. - - - - - Initializes a target reference type with the type's default constructor if the target has not - already been initialized. - - The refence type of the reference to be initialized. - A reference of type to initialize if it has not - already been initialized. - The initialized reference of type . - Type does not have a default - constructor. - - Permissions to access the constructor of type were missing. - - - - This method may only be used on reference types. To ensure initialization of value - types, see other overloads of EnsureInitialized. - - - This method may be used concurrently by multiple threads to initialize . - In the event that multiple threads access this method concurrently, multiple instances of - may be created, but only one will be stored into . In such an occurrence, this method will not dispose of the - objects that were not stored. If such objects must be disposed, it is up to the caller to determine - if an object was not used and to then dispose of the object appropriately. - - - - - - Initializes a target reference type using the specified function if it has not already been - initialized. - - The reference type of the reference to be initialized. - The reference of type to initialize if it has not - already been initialized. - The invoked to initialize the - reference. - The initialized reference of type . - Type does not have a - default constructor. - returned - null. - - - This method may only be used on reference types, and may - not return a null reference (Nothing in Visual Basic). To ensure initialization of value types or - to allow null reference types, see other overloads of EnsureInitialized. - - - This method may be used concurrently by multiple threads to initialize . - In the event that multiple threads access this method concurrently, multiple instances of - may be created, but only one will be stored into . In such an occurrence, this method will not dispose of the - objects that were not stored. If such objects must be disposed, it is up to the caller to determine - if an object was not used and to then dispose of the object appropriately. - - - - - - Initialize the target using the given delegate (slow path). - - The reference type of the reference to be initialized. - The variable that need to be initialized - The delegate that will be executed to initialize the target - The initialized variable - - - - Initializes a target reference or value type with its default constructor if it has not already - been initialized. - - The type of the reference to be initialized. - A reference or value of type to initialize if it - has not already been initialized. - A reference to a boolean that determines whether the target has already - been initialized. - A reference to an object used as the mutually exclusive lock for initializing - . - The initialized value of type . - - - - Initializes a target reference or value type with a specified function if it has not already been - initialized. - - The type of the reference to be initialized. - A reference or value of type to initialize if it - has not already been initialized. - A reference to a boolean that determines whether the target has already - been initialized. - A reference to an object used as the mutually exclusive lock for initializing - . - The invoked to initialize the - reference or value. - The initialized value of type . - - - - Ensure the target is initialized and return the value (slow path). This overload permits nulls - and also works for value type targets. Uses the supplied function to create the value. - - The type of target. - A reference to the target to be initialized. - A reference to a location tracking whether the target has been initialized. - A reference to a location containing a mutual exclusive lock. - - The to invoke in order to produce the lazily-initialized value. - - The initialized object. - - - - Provides a slimmed down version of . - - - All public and protected members of are thread-safe and may be used - concurrently from multiple threads, with the exception of Dispose, which - must only be used when all other operations on the have - completed, and Reset, which should only be used when no other threads are - accessing the event. - - - - - Initializes a new instance of the - class with an initial state of nonsignaled. - - - - - Initializes a new instance of the - class with a Boolen value indicating whether to set the intial state to signaled. - - true to set the initial state signaled; false to set the initial state - to nonsignaled. - - - - Initializes a new instance of the - class with a Boolen value indicating whether to set the intial state to signaled and a specified - spin count. - - true to set the initial state to signaled; false to set the initial state - to nonsignaled. - The number of spin waits that will occur before falling back to a true - wait. - is less than - 0 or greater than the maximum allowed value. - - - - Initializes the internal state of the event. - - Whether the event is set initially or not. - The spin count that decides when the event will block. - - - - Helper to ensure the lock object is created before first use. - - - - - This method lazily initializes the event object. It uses CAS to guarantee that - many threads racing to call this at once don't result in more than one event - being stored and used. The event will be signaled or unsignaled depending on - the state of the thin-event itself, with synchronization taken into account. - - True if a new event was created and stored, false otherwise. - - - - Sets the state of the event to signaled, which allows one or more threads waiting on the event to - proceed. - - - - - Private helper to actually perform the Set. - - Indicates whether we are calling Set() during cancellation. - The object has been canceled. - - - - Sets the state of the event to nonsignaled, which causes threads to block. - - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - Blocks the current thread until the current is set. - - - The maximum number of waiters has been exceeded. - - - The caller of this method blocks indefinitely until the current instance is set. The caller will - return immediately if the event is currently in a set state. - - - - - Blocks the current thread until the current receives a signal, - while observing a . - - The to - observe. - - The maximum number of waiters has been exceeded. - - was - canceled. - - The caller of this method blocks indefinitely until the current instance is set. The caller will - return immediately if the event is currently in a set state. - - - - - Blocks the current thread until the current is set, using a - to measure the time interval. - - A that represents the number of milliseconds - to wait, or a that represents -1 milliseconds to wait indefinitely. - - true if the was set; otherwise, - false. - is a negative - number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater - than . - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - to measure the time interval, while observing a . - - A that represents the number of milliseconds - to wait, or a that represents -1 milliseconds to wait indefinitely. - - The to - observe. - true if the was set; otherwise, - false. - is a negative - number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater - than . - was canceled. - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - 32-bit signed integer to measure the time interval. - - The number of milliseconds to wait, or (-1) to wait indefinitely. - true if the was set; otherwise, - false. - is a - negative number other than -1, which represents an infinite time-out. - - The maximum number of waiters has been exceeded. - - - - - Blocks the current thread until the current is set, using a - 32-bit signed integer to measure the time interval, while observing a . - - The number of milliseconds to wait, or (-1) to wait indefinitely. - The to - observe. - true if the was set; otherwise, - false. - is a - negative number other than -1, which represents an infinite time-out. - - The maximum number of waiters has been exceeded. - - was canceled. - - - - Releases all resources used by the current instance of . - - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - When overridden in a derived class, releases the unmanaged resources used by the - , and optionally releases the managed resources. - - true to release both managed and unmanaged resources; - false to release only unmanaged resources. - - Unlike most of the members of , is not - thread-safe and may not be used concurrently with other members of this instance. - - - - - Throw ObjectDisposedException if the MRES is disposed - - - - - Private helper method to wake up waiters when a cancellationToken gets canceled. - - - - - Private helper method for updating parts of a bit-string state value. - Mainly called from the IsSet and Waiters properties setters - - - Note: the parameter types must be int as CompareExchange cannot take a Uint - - The new value - The mask used to set the bits - - - - Private helper method - performs Mask and shift, particular helpful to extract a field from a packed word. - eg ExtractStatePortionAndShiftRight(0x12345678, 0xFF000000, 24) => 0x12, ie extracting the top 8-bits as a simple integer - - ?? is there a common place to put this rather than being private to MRES? - - - - - - - - - Performs a Mask operation, but does not perform the shift. - This is acceptable for boolean values for which the shift is unnecessary - eg (val & Mask) != 0 is an appropriate way to extract a boolean rather than using - ((val & Mask) >> shiftAmount) == 1 - - ?? is there a common place to put this rather than being private to MRES? - - - - - - - Helper function to measure and update the wait time - - The first time (in Ticks) observed when the wait started. - The orginal wait timeoutout in milliseconds. - The new wait time in milliseconds, -1 if the time expired, -2 if overflow in counters - has occurred. - - - - Gets the underlying object for this . - - The underlying event object fore this . - - Accessing this property forces initialization of an underlying event object if one hasn't - already been created. To simply wait on this , - the public Wait methods should be preferred. - - - - - Gets whether the event is set. - - true if the event has is set; otherwise, false. - - - - Gets the number of spin waits that will be occur before falling back to a true wait. - - - - - How many threads are waiting. - - - - - Provides support for spin-based waiting. - - - - encapsulates common spinning logic. On single-processor machines, yields are - always used instead of busy waits, and on computers with Intel™ processors employing Hyper-Threading™ - technology, it helps to prevent hardware thread starvation. SpinWait encapsulates a good mixture of - spinning and true yielding. - - - is a value type, which means that low-level code can utilize SpinWait without - fear of unnecessary allocation overheads. SpinWait is not generally useful for ordinary applications. - In most cases, you should use the synchronization classes provided by the .NET Framework, such as - . For most purposes where spin waiting is required, however, - the type should be preferred over the System.Threading.Thread.SpinWait method. - - - While SpinWait is designed to be used in concurrent applications, it is not designed to be - used from multiple threads concurrently. SpinWait's members are not thread-safe. If multiple - threads must spin, each should use its own instance of SpinWait. - - - - - - Performs a single spin. - - - This is typically called in a loop, and may change in behavior based on the number of times a - has been called thus far on this instance. - - - - - Resets the spin counter. - - - This makes and behave as though no calls - to had been issued on this instance. If a instance - is reused many times, it may be useful to reset it to avoid yielding too soon. - - - - - Spins until the specified condition is satisfied. - - A delegate to be executed over and over until it returns true. - The argument is null. - - - - Spins until the specified condition is satisfied or until the specified timeout is expired. - - A delegate to be executed over and over until it returns true. - - A that represents the number of milliseconds to wait, - or a TimeSpan that represents -1 milliseconds to wait indefinitely. - True if the condition is satisfied within the timeout; otherwise, false - The argument is null. - is a negative number - other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than - . - - - - Spins until the specified condition is satisfied or until the specified timeout is expired. - - A delegate to be executed over and over until it returns true. - The number of milliseconds to wait, or (-1) to wait indefinitely. - True if the condition is satisfied within the timeout; otherwise, false - The argument is null. - is a - negative number other than -1, which represents an infinite time-out. - - - - Gets the number of times has been called on this instance. - - - - - Gets whether the next call to will yield the processor, triggering a - forced context switch. - - Whether the next call to will yield the processor, triggering a - forced context switch. - - On a single-CPU machine, always yields the processor. On machines with - multiple CPUs, may yield after an unspecified number of calls. - - - - - A helper class to get the number of preocessors, it updates the numbers of processors every sampling interval - - - - - Gets the number of available processors - - - - - Gets whether the current machine has only a single processor. - - - - - Represents an asynchronous operation that produces a result at some time in the future. - - - The type of the result produced by this . - - - - instances may be created in a variety of ways. The most common approach is by - using the task's property to retrieve a instance that can be used to create tasks for several - purposes. For example, to create a that runs a function, the factory's StartNew - method may be used: - - // C# - var t = Task<int>.Factory.StartNew(() => GenerateResult()); - - or - - var t = Task.Factory.StartNew(() => GenerateResult()); - - ' Visual Basic - Dim t = Task<int>.Factory.StartNew(Function() GenerateResult()) - - or - - Dim t = Task.Factory.StartNew(Function() GenerateResult()) - - - - The class also provides constructors that initialize the task but that do not - schedule it for execution. For performance reasons, the StartNew method should be the - preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation - and scheduling must be separated, the constructors may be used, and the task's - Start - method may then be used to schedule the task for execution at a later time. - - - All members of , except for - Dispose, are thread-safe - and may be used from multiple threads concurrently. - - - - - - Represents an asynchronous operation. - - - - instances may be created in a variety of ways. The most common approach is by - using the Task type's property to retrieve a instance that can be used to create tasks for several - purposes. For example, to create a that runs an action, the factory's StartNew - method may be used: - - // C# - var t = Task.Factory.StartNew(() => DoAction()); - - ' Visual Basic - Dim t = Task.Factory.StartNew(Function() DoAction()) - - - - The class also provides constructors that initialize the Task but that do not - schedule it for execution. For performance reasons, TaskFactory's StartNew method should be the - preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation - and scheduling must be separated, the constructors may be used, and the task's - method may then be used to schedule the task for execution at a later time. - - - All members of , except for , are thread-safe - and may be used from multiple threads concurrently. - - - For operations that return values, the class - should be used. - - - For developers implementing custom debuggers, several internal and private members of Task may be - useful (these may change from release to release). The Int32 m_taskId field serves as the backing - store for the property, however accessing this field directly from a debugger may be - more efficient than accessing the same value through the property's getter method (the - s_taskIdCounter Int32 counter is used to retrieve the next available ID for a Task). Similarly, the - Int32 m_stateFlags field stores information about the current lifecycle stage of the Task, - information also accessible through the property. The m_action System.Object - field stores a reference to the Task's delegate, and the m_stateObject System.Object field stores the - async state passed to the Task by the developer. Finally, for debuggers that parse stack frames, the - InternalWait method serves a potential marker for when a Task is entering a wait operation. - - - - - - A type initializer that runs with the appropriate permissions. - - - - - Initializes a new with the specified action. - - The delegate that represents the code to execute in the Task. - The argument is null. - - - - Initializes a new with the specified action and CancellationToken. - - The delegate that represents the code to execute in the Task. - The CancellationToken - that will be assigned to the new Task. - The argument is null. - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action and creation options. - - The delegate that represents the code to execute in the task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action and creation options. - - The delegate that represents the code to execute in the task. - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action and state. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - - The argument is null. - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - The that will be assigned to the new task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action, state, snd options. - - The delegate that represents the code to execute in the task. - An object representing data to be used by the action. - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the Task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - An internal constructor used by the factory methods on task and its descendent(s). - This variant does not capture the ExecutionContext; it is up to the caller to do that. - - An action to execute. - Optional state to pass to the action. - Parent of Task. - A CancellationToken for the task. - A task scheduler under which the task will run. - Options to control its execution. - Internal options to control its execution - - - - Common logic used by the following internal ctors: - Task() - Task(object action, object state, Task parent, TaskCreationOptions options, TaskScheduler taskScheduler) - - ASSUMES THAT m_creatingTask IS ALREADY SET. - - - Action for task to execute. - Object to which to pass to action (may be null) - Task scheduler on which to run thread (only used by continuation tasks). - A CancellationToken for the Task. - Options to customize behavior of Task. - Internal options to customize behavior of Task. - - - - Checks if we registered a CT callback during construction, and deregisters it. - This should be called when we know the registration isn't useful anymore. Specifically from Finish() if the task has completed - successfully or with an exception. - - - - - Captures the ExecutionContext so long as flow isn't suppressed. - - A stack crawl mark pointing to the frame of the caller. - - - - Internal function that will be called by a new child task to add itself to - the children list of the parent (this). - - Since a child task can only be created from the thread executing the action delegate - of this task, reentrancy is neither required nor supported. This should not be called from - anywhere other than the task construction/initialization codepaths. - - - - - Starts the , scheduling it for execution to the current TaskScheduler. - - - A task may only be started and run only once. Any attempts to schedule a task a second time - will result in an exception. - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Starts the , scheduling it for execution to the specified TaskScheduler. - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - The TaskScheduler with which to associate - and execute this task. - - - The argument is null. - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Runs the synchronously on the current TaskScheduler. - - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - Tasks executed with will be associated with the current TaskScheduler. - - - If the target scheduler does not support running this Task on the current thread, the Task will - be scheduled for execution on the scheduler, and the current thread will block until the - Task has completed execution. - - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - - - - Runs the synchronously on the scheduler provided. - - - - A task may only be started and run only once. Any attempts to schedule a task a second time will - result in an exception. - - - If the target scheduler does not support running this Task on the current thread, the Task will - be scheduled for execution on the scheduler, and the current thread will block until the - Task has completed execution. - - - - The is not in a valid state to be started. It may have already been started, - executed, or canceled, or it may have been created in a manner that doesn't support direct - scheduling. - - - The instance has been disposed. - - The parameter - is null. - The scheduler on which to attempt to run this task inline. - - - - Throws an exception if the task has been disposed, and hence can no longer be accessed. - - The task has been disposed. - - - - Sets the internal completion event. - - - - - Disposes the , releasing all of its unmanaged resources. - - - Unlike most of the members of , this method is not thread-safe. - Also, may only be called on a that is in one of - the final states: RanToCompletion, - Faulted, or - Canceled. - - - The exception that is thrown if the is not in - one of the final states: RanToCompletion, - Faulted, or - Canceled. - - - - - Disposes the , releasing all of its unmanaged resources. - - - A Boolean value that indicates whether this method is being called due to a call to . - - - Unlike most of the members of , this method is not thread-safe. - - - - - Schedules the task for execution. - - If true, TASK_STATE_STARTED bit is turned on in - an atomic fashion, making sure that TASK_STATE_CANCELED does not get set - underneath us. If false, TASK_STATE_STARTED bit is OR-ed right in. This - allows us to streamline things a bit for StartNew(), where competing cancellations - are not a problem. - - - - Adds an exception to the list of exceptions this task has thrown. - - An object representing either an Exception or a collection of Exceptions. - - - - Returns a list of exceptions by aggregating the holder's contents. Or null if - no exceptions have been thrown. - - Whether to include a TCE if cancelled. - An aggregate exception, or null if no exceptions have been caught. - - - - Throws an aggregate exception if the task contains exceptions. - - - - - Checks whether this is an attached task, and whether we are being called by the parent task. - And sets the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag based on that. - - This is meant to be used internally when throwing an exception, and when WaitAll is gathering - exceptions for tasks it waited on. If this flag gets set, the implicit wait on children - will skip exceptions to prevent duplication. - - This should only be called when this task has completed with an exception - - - - - - Signals completion of this particular task. - - The bUserDelegateExecuted parameter indicates whether this Finish() call comes following the - full execution of the user delegate. - - If bUserDelegateExecuted is false, it mean user delegate wasn't invoked at all (either due to - a cancellation request, or because this task is a promise style Task). In this case, the steps - involving child tasks (i.e. WaitForChildren) will be skipped. - - - - - - FinishStageTwo is to be executed as soon as we known there are no more children to complete. - It can happen i) either on the thread that originally executed this task (if no children were spawned, or they all completed by the time this task's delegate quit) - ii) or on the thread that executed the last child. - - - - - Final stage of the task completion code path. Notifies the parent (if any) that another of its childre are done, and runs continuations. - This function is only separated out from FinishStageTwo because these two operations are also needed to be called from CancellationCleanupLogic() - - - - - This is called by children of this task when they are completed. - - - - - This is to be called just before the task does its final state transition. - It traverses the list of exceptional children, and appends their aggregate exceptions into this one's exception list - - - - - Special purpose Finish() entry point to be used when the task delegate throws a ThreadAbortedException - This makes a note in the state flags so that we avoid any costly synchronous operations in the finish codepath - such as inlined continuations - - - Indicates whether the ThreadAbortException was added to this task's exception holder. - This should always be true except for the case of non-root self replicating task copies. - - Whether the delegate was executed. - - - - Executes the task. This method will only be called once, and handles bookeeping associated with - self-replicating tasks, in addition to performing necessary exception marshaling. - - The task has already been disposed. - - - - IThreadPoolWorkItem override, which is the entry function for this task when the TP scheduler decides to run it. - - - - - - Outermost entry function to execute this task. Handles all aspects of executing a task on the caller thread. - Currently this is called by IThreadPoolWorkItem.ExecuteWorkItem(), and TaskManager.TryExecuteInline. - - - Performs atomic updates to prevent double execution. Should only be set to true - in codepaths servicing user provided TaskSchedulers. The ConcRT or ThreadPool schedulers don't need this. - - - - The actual code which invokes the body of the task. This can be overriden in derived types. - - - - - Alternate InnerInvoke prototype to be called from ExecuteSelfReplicating() so that - the Parallel Debugger can discover the actual task being invoked. - Details: Here, InnerInvoke is actually being called on the rootTask object while we are actually executing the - childTask. And the debugger needs to discover the childTask, so we pass that down as an argument. - The NoOptimization and NoInlining flags ensure that the childTask pointer is retained, and that this - function appears on the callstack. - - - - - - Performs whatever handling is necessary for an unhandled exception. Normally - this just entails adding the exception to the holder object. - - The exception that went unhandled. - - - - Waits for the to complete execution. - - - The was canceled -or- an exception was thrown during - the execution of the . - - - The has been disposed. - - - - - Waits for the to complete execution. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - true if the completed execution within the allotted time; otherwise, false. - - - The was canceled -or- an exception was thrown during the execution of the . - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - The has been disposed. - - - - - Waits for the to complete execution. - - - A to observe while waiting for the task to complete. - - - The was canceled. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - - - Waits for the to complete execution. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - true if the completed execution within the allotted time; otherwise, - false. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - - - Waits for the to complete execution. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for the task to complete. - - - true if the completed execution within the allotted time; otherwise, false. - - - The was canceled -or- an exception was thrown during the execution of the . - - - The - has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - The core wait function, which is only accesible internally. It's meant to be used in places in TPL code where - the current context is known or cached. - - - - - Cancels the . - - Indiactes whether we should only cancel non-invoked tasks. - For the default scheduler this option will only be serviced through TryDequeue. - For custom schedulers we also attempt an atomic state transition. - true if the task was successfully canceled; otherwise, false. - The - has been disposed. - - - - Sets the task's cancellation acknowledged flag. - - - - - Runs all of the continuations, as appropriate. - - - - - Helper function to determine whether the current task is in the state desired by the - continuation kind under evaluation. Three possibilities exist: the task failed with - an unhandled exception (OnFailed), the task was canceled before running (OnAborted), - or the task completed successfully (OnCompletedSuccessfully). Note that the last - one includes completing due to cancellation. - - The continuation options under evaluation. - True if the continuation should be run given the task's current state. - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - The that will be assigned to the new continuation task. - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Converts TaskContinuationOptions to TaskCreationOptions, and also does - some validity checking along the way. - - Incoming TaskContinuationOptions - Outgoing TaskCreationOptions - Outgoing InternalTaskOptions - - - - Registers the continuation and possibly runs it (if the task is already finished). - - The continuation task itself. - TaskScheduler with which to associate continuation task. - Restrictions on when the continuation becomes active. - - - - Waits for all of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - An array of instances on which to wait. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - A to observe while waiting for the tasks to complete. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The was canceled. - - - The has been disposed. - - - - - Waits for all of the provided objects to complete execution. - - - true if all of the instances completed execution within the allotted time; - otherwise, false. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for the tasks to complete. - - - The argument is null. - - - The argument contains a null element. - - - At least one of the instances was canceled -or- an exception was thrown during - the execution of at least one of the instances. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - Waits for a set of handles in a STA-aware way. In other words, it will wait for each - of the events individually if we're on a STA thread, because MsgWaitForMultipleObjectsEx - can't do a true wait-all due to its hidden message queue event. This is not atomic, - of course, but we only wait on one-way (MRE) events anyway so this is OK. - - An array of wait handles to wait on. - The timeout to use during waits. - The cancellationToken that enables a wait to be canceled. - True if all waits succeeded, false if a timeout occurred. - - - - Internal WaitAll implementation which is meant to be used with small number of tasks, - optimized for Parallel.Invoke and other structured primitives. - - - - - This internal function is only meant to be called by WaitAll() - If the completed task is canceled or it has other exceptions, here we will add those - into the passed in exception list (which will be lazily initialized here). - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - The index of the completed task in the array argument. - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1 milliseconds, which represents an - infinite time-out -or- timeout is greater than - . - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - A to observe while waiting for a task to complete. - - - The index of the completed task in the array argument. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - The was canceled. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - - - Waits for any of the provided objects to complete execution. - - - An array of instances on which to wait. - - - The number of milliseconds to wait, or (-1) to - wait indefinitely. - - - A to observe while waiting for a task to complete. - - - The index of the completed task in the array argument, or -1 if the - timeout occurred. - - - The argument is null. - - - The argument contains a null element. - - - The has been disposed. - - - is a negative number other than -1, which represents an - infinite time-out. - - - The was canceled. - - - - - Gets a unique ID for this Task instance. - - - Task IDs are assigned on-demand and do not necessarily represent the order in the which Task - instances were created. - - - - - Returns the unique ID of the currently executing Task. - - - - - Gets the Task instance currently executing, or - null if none exists. - - - - - Gets the Exception that caused the Task to end prematurely. If the Task completed successfully or has not yet thrown any - exceptions, this will return null. - - - Tasks that throw unhandled exceptions store the resulting exception and propagate it wrapped in a - in calls to Wait - or in accesses to the property. Any exceptions not observed by the time - the Task instance is garbage collected will be propagated on the finalizer thread. - - - The Task - has been disposed. - - - - - Gets the TaskStatus of this Task. - - - - - Gets whether this Task instance has completed - execution due to being canceled. - - - A Task will complete in Canceled state either if its CancellationToken - was marked for cancellation before the task started executing, or if the task acknowledged the cancellation request on - its already signaled CancellationToken by throwing an - OperationCanceledException2 that bears the same - CancellationToken. - - - - - Returns true if this task has a cancellation token and it was signaled. - To be used internally in execute entry codepaths. - - - - - This internal property provides access to the CancellationToken that was set on the task - when it was constructed. - - - - - Gets whether this threw an OperationCanceledException2 while its CancellationToken was signaled. - - - - - Gets whether this Task has completed. - - - will return true when the Task is in one of the three - final states: RanToCompletion, - Faulted, or - Canceled. - - - - - Checks whether this task has been disposed. - - - - - Gets the TaskCreationOptions used - to create this task. - - - - - Gets a that can be used to wait for the task to - complete. - - - Using the wait functionality provided by - should be preferred over using for similar - functionality. - - - The has been disposed. - - - - - Gets the state object supplied when the Task was created, - or null if none was supplied. - - - - - Gets an indication of whether the asynchronous operation completed synchronously. - - true if the asynchronous operation completed synchronously; otherwise, false. - - - - Provides access to the TaskScheduler responsible for executing this Task. - - - - - Provides access to factory methods for creating and instances. - - - The factory returned from is a default instance - of , as would result from using - the default constructor on TaskFactory. - - - - - Provides an event that can be used to wait for completion. - Only called by Wait*(), which means that we really do need to instantiate a completion event. - - - - - Determines whether this is the root task of a self replicating group. - - - - - Determines whether the task is a replica itself. - - - - - The property formerly known as IsFaulted. - - - - - Gets whether the completed due to an unhandled exception. - - - If is true, the Task's will be equal to - TaskStatus.Faulted, and its - property will be non-null. - - - - - Checks whether the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag is set, - This will only be used by the implicit wait to prevent double throws - - - - - - Checks whether the body was ever invoked. Used by task scheduler code to verify custom schedulers actually ran the task. - - - - - A structure to hold continuation information. - - - - - Constructs a new continuation structure. - - The task to be activated. - The continuation options. - The scheduler to use for the continuation. - - - - Invokes the continuation for the target completion task. - - The completed task. - Whether the continuation can be inlined. - - - - Initializes a new with the specified function. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - - The argument is null. - - - - - Initializes a new with the specified function. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - The to be assigned to this task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified function and creation options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified function and creation options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - The that will be assigned to the new task. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified function and state. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the action. - - The argument is null. - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - The to be assigned to the new task. - - The argument is null. - - The provided CancellationToken - has already been disposed. - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - - - - Initializes a new with the specified action, state, and options. - - - The delegate that represents the code to execute in the task. When the function has completed, - the task's property will be set to return the result value of the function. - - An object representing data to be used by the function. - The to be assigned to the new task. - - The TaskCreationOptions used to - customize the task's behavior. - - - The argument is null. - - - The argument specifies an invalid value for . - - The provided CancellationToken - has already been disposed. - - - - - Creates a new future object. - - The parent task for this future. - A function that yields the future value. - The task scheduler which will be used to execute the future. - The CancellationToken for the task. - Options to control the future's behavior. - Internal options to control the future's behavior. - The argument specifies - a SelfReplicating , which is illegal."/>. - - - - Creates a new future object. - - The parent task for this future. - An object containing data to be used by the action; may be null. - A function that yields the future value. - The CancellationToken for the task. - The task scheduler which will be used to execute the future. - Options to control the future's behavior. - Internal options to control the future's behavior. - The argument specifies - a SelfReplicating , which is illegal."/>. - - - - Evaluates the value selector of the Task which is passed in as an object and stores the result. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the continuation criteria specified through the parameter are not met, the continuation task will be canceled - instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - An action to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new continuation task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed. If the criteria specified through the parameter - are not met, the continuation task will be canceled instead of scheduled. - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - A new continuation . - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - The that will be assigned to the new task. - A new continuation . - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - The to associate with the continuation task and to use for its execution. - - A new continuation . - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The argument is null. - - - The argument is null. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be - passed the completed task as an argument. - - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - A new continuation . - - - The returned will not be scheduled for execution until the current - task has completed, whether it completes due to running to completion successfully, faulting due - to an unhandled exception, or exiting out early due to being canceled. - - - The , when executed, should return a . This task's completion state will be transferred to the task returned - from the ContinueWith call. - - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The has been disposed. - - - - - Creates a continuation that executes when the target completes. - - - The type of the result produced by the continuation. - - - A function to run when the completes. When run, the delegate will be passed as - an argument this completed task. - - The that will be assigned to the new task. - - Options for when the continuation is scheduled and how it behaves. This includes criteria, such - as OnlyOnCanceled, as - well as execution options, such as ExecuteSynchronously. - - - The to associate with the continuation task and to use for its - execution. - - A new continuation . - - - The returned will not be scheduled for execution until the current task has - completed, whether it completes due to running to completion successfully, faulting due to an - unhandled exception, or exiting out early due to being canceled. - - - The , when executed, should return a . - This task's completion state will be transferred to the task returned from the - ContinueWith call. - - - - The argument is null. - - - The argument specifies an invalid value for TaskContinuationOptions. - - - The argument is null. - - - The has been disposed. - - The provided CancellationToken - has already been disposed. - - - - - Gets the result value of this . - - - The get accessor for this property ensures that the asynchronous operation is complete before - returning. Once the result of the computation is available, it is stored and will be returned - immediately on later calls to . - - - - - Provides access to factory methods for creating instances. - - - The factory returned from is a default instance - of , as would result from using - the default constructor on the factory type. - - - - - Provides support for creating and scheduling - Task{TResult} objects. - - The type of the results that are available though - the Task{TResult} objects that are associated with - the methods in this class. - - - There are many common patterns for which tasks are relevant. The - class encodes some of these patterns into methods that pick up default settings, which are - configurable through its constructors. - - - A default instance of is available through the - Task{TResult}.Factory property. - - - - - - Initializes a instance with the default configuration. - - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the default configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The - TaskScheduler to use to schedule any tasks created with this TaskFactory{TResult}. A null value - indicates that the current TaskScheduler should be used. - - - With this constructor, the - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to , unless it's null, in which case the property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory{TResult}. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory{TResult}. - - - The exception that is thrown when the - argument or the - argument specifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory{TResult}. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory{TResult}. - - - The default - TaskScheduler to use to schedule any Tasks created with this TaskFactory{TResult}. A null value - indicates that TaskScheduler.Current should be used. - - - The exception that is thrown when the - argument or the - argumentspecifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to - , unless it's null, in which case the property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new task. - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The that will be assigned to the new task. - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The function delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Gets the default CancellationToken of this - TaskFactory. - - - This property returns the default that will be assigned to all - tasks created by this factory unless another CancellationToken value is explicitly specified - during the call to the factory methods. - - - - - Gets the TaskScheduler of this - TaskFactory{TResult}. - - - This property returns the default scheduler for this factory. It will be used to schedule all - tasks unless another scheduler is explicitly specified during calls to this factory's methods. - If null, TaskScheduler.Current - will be used. - - - - - Gets the TaskCreationOptions - value of this TaskFactory{TResult}. - - - This property returns the default creation options for this factory. They will be used to create all - tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Gets the TaskContinuationOptions - value of this TaskFactory{TResult}. - - - This property returns the default continuation options for this factory. They will be used to create - all continuation tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Represents the current stage in the lifecycle of a . - - - - - The task has been initialized but has not yet been scheduled. - - - - - The task is waiting to be activated and scheduled internally by the .NET Framework infrastructure. - - - - - The task has been scheduled for execution but has not yet begun executing. - - - - - The task is running but has not yet completed. - - - - - The task has finished executing and is implicitly waiting for - attached child tasks to complete. - - - - - The task completed execution successfully. - - - - - The task acknowledged cancellation by throwing an OperationCanceledException2 with its own CancellationToken - while the token was in signaled state, or the task's CancellationToken was already signaled before the - task started executing. - - - - - The task completed due to an unhandled exception. - - - - - Specifies flags that control optional behavior for the creation and execution of tasks. - - - - - Specifies that the default behavior should be used. - - - - - A hint to a TaskScheduler to schedule a - task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to - be run sooner, and tasks scheduled later will be more likely to be run later. - - - - - Specifies that a task will be a long-running, course-grained operation. It provides a hint to the - TaskScheduler that oversubscription may be - warranted. - - - - - Specifies that a task is attached to a parent in the task hierarchy. - - - - - Task creation flags which are only used internally. - - - - Specifies "No internal task options" - - - Used to filter out internal vs. public task creation options. - - - Specifies that the task will be queued by the runtime before handing it over to the user. - This flag will be used to skip the cancellationtoken registration step, which is only meant for unstarted tasks. - - - - Specifies flags that control optional behavior for the creation and execution of continuation tasks. - - - - - Default = "Continue on any, no task options, run asynchronously" - Specifies that the default behavior should be used. Continuations, by default, will - be scheduled when the antecedent task completes, regardless of the task's final TaskStatus. - - - - - A hint to a TaskScheduler to schedule a - task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to - be run sooner, and tasks scheduled later will be more likely to be run later. - - - - - Specifies that a task will be a long-running, course-grained operation. It provides - a hint to the TaskScheduler that - oversubscription may be warranted. - - - - - Specifies that a task is attached to a parent in the task hierarchy. - - - - - Specifies that the continuation task should not be scheduled if its antecedent ran to completion. - This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should not be scheduled if its antecedent threw an unhandled - exception. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should not be scheduled if its antecedent was canceled. This - option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent ran to - completion. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent threw an - unhandled exception. This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be scheduled only if its antecedent was canceled. - This option is not valid for multi-task continuations. - - - - - Specifies that the continuation task should be executed synchronously. With this option - specified, the continuation will be run on the same thread that causes the antecedent task to - transition into its final state. If the antecedent is already complete when the continuation is - created, the continuation will run on the thread creating the continuation. Only very - short-running continuations should be executed synchronously. - - - - - Represents an exception used to communicate task cancellation. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the - class with a specified error message and a reference to the inner exception that is the cause of - this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - - Initializes a new instance of the class - with a reference to the that has been canceled. - - A task that has been canceled. - - - - Gets the task associated with this exception. - - - It is permissible for no Task to be associated with a - , in which case - this property will return null. - - - - - Represents the producer side of a unbound to a - delegate, providing access to the consumer side through the property. - - - - It is often the case that a is desired to - represent another asynchronous operation. - TaskCompletionSource is provided for this purpose. It enables - the creation of a task that can be handed out to consumers, and those consumers can use the members - of the task as they would any other. However, unlike most tasks, the state of a task created by a - TaskCompletionSource is controlled explicitly by the methods on TaskCompletionSource. This enables the - completion of the external asynchronous operation to be propagated to the underlying Task. The - separation also ensures that consumers are not able to transition the state without access to the - corresponding TaskCompletionSource. - - - All members of are thread-safe - and may be used from multiple threads concurrently. - - - The type of the result value assocatied with this . - - - - Creates a . - - - - - Creates a - with the specified options. - - - The created - by this instance and accessible through its property - will be instantiated using the specified . - - The options to use when creating the underlying - . - - The represent options invalid for use - with a . - - - - - Creates a - with the specified state. - - The state to use as the underlying - 's AsyncState. - - - - Creates a with - the specified state and options. - - The options to use when creating the underlying - . - The state to use as the underlying - 's AsyncState. - - The represent options invalid for use - with a . - - - - - Attempts to transition the underlying - into the - Faulted - state. - - The exception to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The argument is null. - The was disposed. - - - - Attempts to transition the underlying - into the - Faulted - state. - - The collection of exceptions to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The argument is null. - There are one or more null elements in . - The collection is empty. - The was disposed. - - - - Transitions the underlying - into the - Faulted - state. - - The exception to bind to this . - The argument is null. - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - Faulted - state. - - The collection of exceptions to bind to this . - The argument is null. - There are one or more null elements in . - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Attempts to transition the underlying - into the - RanToCompletion - state. - - The result value to bind to this . - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - RanToCompletion - state. - - The result value to bind to this . - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Transitions the underlying - into the - Canceled - state. - - - The underlying is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Attempts to transition the underlying - into the - Canceled - state. - - True if the operation was successful; otherwise, false. - This operation will return false if the - is already in one - of the three final states: - RanToCompletion, - Faulted, or - Canceled. - - The was disposed. - - - - Gets the created - by this . - - - This property enables a consumer access to the that is controlled by this instance. - The , , - , and - methods (and their "Try" variants) on this instance all result in the relevant state - transitions on this underlying Task. - - - - - An exception holder manages a list of exceptions for one particular task. - It offers the ability to aggregate, but more importantly, also offers intrinsic - support for propagating unhandled exceptions that are never observed. It does - this by aggregating and throwing if the holder is ever GC'd without the holder's - contents ever having been requested (e.g. by a Task.Wait, Task.get_Exception, etc). - - - - - Creates a new holder; it will be registered for finalization. - - The task this holder belongs to. - - - - A finalizer that repropagates unhandled exceptions. - - - - - Add an exception to the internal list. This will ensure the holder is - in the proper state (handled/unhandled) depending on the list's contents. - - An exception object (either an Exception or an - IEnumerable{Exception}) to add to the list. - - - - A private helper method that ensures the holder is considered - unhandled, i.e. it is registered for finalization. - - - - - A private helper method that ensures the holder is considered - handled, i.e. it is not registered for finalization. - - Whether this is called from the finalizer thread. - - - - Allocates a new aggregate exception and adds the contents of the list to - it. By calling this method, the holder assumes exceptions to have been - "observed", such that the finalization check will be subsequently skipped. - - Whether this is being called from a finalizer. - An extra exception to be included (optionally). - The aggregate exception to throw. - - - - Provides a set of static (Shared in Visual Basic) methods for working with specific kinds of - instances. - - - - - Creates a proxy Task that represents the - asynchronous operation of a Task{Task}. - - - It is often useful to be able to return a Task from a - Task{TResult}, where the inner Task represents work done as part of the outer Task{TResult}. However, - doing so results in a Task{Task}, which, if not dealt with carefully, could produce unexpected behavior. Unwrap - solves this problem by creating a proxy Task that represents the entire asynchronous operation of such a Task{Task}. - - The Task{Task} to unwrap. - The exception that is thrown if the - argument is null. - A Task that represents the asynchronous operation of the provided Task{Task}. - - - - Creates a proxy Task{TResult} that represents the - asynchronous operation of a Task{Task{TResult}}. - - - It is often useful to be able to return a Task{TResult} from a Task{TResult}, where the inner Task{TResult} - represents work done as part of the outer Task{TResult}. However, doing so results in a Task{Task{TResult}}, - which, if not dealt with carefully, could produce unexpected behavior. Unwrap solves this problem by - creating a proxy Task{TResult} that represents the entire asynchronous operation of such a Task{Task{TResult}}. - - The Task{Task{TResult}} to unwrap. - The exception that is thrown if the - argument is null. - A Task{TResult} that represents the asynchronous operation of the provided Task{Task{TResult}}. /// Unwraps a Task that returns another Task. - - - - Provides support for creating and scheduling - Tasks. - - - - There are many common patterns for which tasks are relevant. The - class encodes some of these patterns into methods that pick up default settings, which are - configurable through its constructors. - - - A default instance of is available through the - Task.Factory property. - - - - - - Initializes a instance with the default configuration. - - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - This constructor creates a instance with a default configuration. The - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The - TaskScheduler to use to schedule any tasks created with this TaskFactory. A null value - indicates that the current TaskScheduler should be used. - - - With this constructor, the - property is initialized to - TaskCreationOptions.None, the - property is initialized to TaskContinuationOptions.None, - and the TaskScheduler property is - initialized to , unless it's null, in which case the property is - initialized to the current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory. - - - The exception that is thrown when the - argument or the - argument specifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Initializes a instance with the specified configuration. - - The default that will be assigned - to tasks created by this unless another CancellationToken is explicitly specified - while calling the factory methods. - - The default - TaskCreationOptions to use when creating tasks with this TaskFactory. - - - The default - TaskContinuationOptions to use when creating continuation tasks with this TaskFactory. - - - The default - TaskScheduler to use to schedule any Tasks created with this TaskFactory. A null value - indicates that TaskScheduler.Current should be used. - - - The exception that is thrown when the - argument or the - argumentspecifies an invalid value. - - - With this constructor, the - property is initialized to , - the - property is initialized to , and the TaskScheduler property is initialized to - , unless it's null, in which case the property is initialized to the - current scheduler (see TaskScheduler.Current). - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The started Task. - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors - and then calling - Start to schedule it for execution. However, - unless creation and scheduling must be separated, StartNew is the recommended - approach for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The that will be assigned to the new task. - The started Task. - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors - and then calling - Start to schedule it for execution. However, - unless creation and scheduling must be separated, StartNew is the recommended - approach for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - The that will be assigned to the new - A TaskCreationOptions value that controls the behavior of the - created - Task. - The TaskScheduler - that is used to schedule the created Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The started Task. - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The that will be assigned to the new - The started Task. - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a Task. - - The action delegate to execute asynchronously. - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - Task. - The TaskScheduler - that is used to schedule the created Task. - The started Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a Task using one of its constructors and - then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The started . - The exception that is thrown when the - argument is null. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new - The started . - The exception that is thrown when the - argument is null. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - A TaskCreationOptions value that controls the behavior of the - created - . - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates and starts a . - - The type of the result available through the - Task. - - A function delegate that returns the future result to be available through - the . - An object containing data to be used by the - delegate. - The that will be assigned to the new task. - A TaskCreationOptions value that controls the behavior of the - created - . - The TaskScheduler - that is used to schedule the created - Task{TResult}. - The started . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The provided CancellationToken - has already been disposed. - - - Calling StartNew is functionally equivalent to creating a using one - of its constructors and then calling - Start to schedule it for execution. - However, unless creation and scheduling must be separated, StartNew is the recommended approach - for both simplicity and performance. - - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the asynchronous - operation. - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the asynchronous - operation. - - - - Creates a Task that executes an end method action - when a specified IAsyncResult completes. - - The IAsyncResult whose completion should trigger the processing of the - . - The action delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the asynchronous - operation. - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of begin - and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the - delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that represents the - asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that executes an end - method function when a specified IAsyncResult completes. - - The type of the result available through the - Task. - - The IAsyncResult whose completion should trigger the processing of the - . - The function delegate that processes the completed . - The TaskScheduler - that is used to schedule the task that executes the end method. - The TaskCreationOptions value that controls the behavior of the - created Task. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - A Task that represents the - asynchronous operation. - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Creates a Task that represents a pair of - begin and end methods that conform to the Asynchronous Programming Model pattern. - - The type of the first argument passed to the delegate. - The type of the second argument passed to - delegate. - The type of the third argument passed to - delegate. - The type of the result available through the - Task. - - The delegate that begins the asynchronous operation. - The delegate that ends the asynchronous operation. - The first argument passed to the - delegate. - The second argument passed to the - delegate. - The third argument passed to the - delegate. - The TaskCreationOptions value that controls the behavior of the - created Task. - An object containing data to be used by the - delegate. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument specifies an invalid TaskCreationOptions - value. - The created Task that - represents the asynchronous operation. - - This method throws any exceptions thrown by the . - - - - - Check validity of options passed to FromAsync method - - The options to be validated. - determines type of FromAsync method that called this method - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in - the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result of the antecedent . - The array of tasks from which to continue. - The action delegate to execute when all tasks in the array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of a set of provided Tasks. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue. - The function delegate to execute when all tasks in the - array have completed. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAll. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation Task. - The new continuation Task. - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result that is returned by the - delegate and associated with the created . - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The function delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Creates a continuation Task - that will be started upon the completion of any Task in the provided set. - - The type of the result of the antecedent . - The array of tasks from which to continue when one task completes. - The action delegate to execute when one task in the - array completes. - The CancellationToken - that will be assigned to the new continuation task. - The - TaskContinuationOptions value that controls the behavior of - the created continuation Task. - The TaskScheduler - that is used to schedule the created continuation . - The new continuation . - The exception that is thrown when the - array is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - argument is null. - The exception that is thrown when the - array contains a null value. - The exception that is thrown when the - array is empty. - The exception that is thrown when the - argument specifies an invalid TaskContinuationOptions - value. - The exception that is thrown when one - of the elements in the array has been disposed. - The provided CancellationToken - has already been disposed. - - - The NotOn* and OnlyOn* TaskContinuationOptions, - which constrain for which TaskStatus states a continuation - will be executed, are illegal with ContinueWhenAny. - - - - - Gets the default CancellationToken of this - TaskFactory. - - - This property returns the default that will be assigned to all - tasks created by this factory unless another CancellationToken value is explicitly specified - during the call to the factory methods. - - - - - Gets the TaskScheduler of this - TaskFactory. - - - This property returns the default scheduler for this factory. It will be used to schedule all - tasks unless another scheduler is explicitly specified during calls to this factory's methods. - If null, TaskScheduler.Current - will be used. - - - - - Gets the TaskCreationOptions - value of this TaskFactory. - - - This property returns the default creation options for this factory. They will be used to create all - tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Gets the TaskContinuationOptions - value of this TaskFactory. - - - This property returns the default continuation options for this factory. They will be used to create - all continuation tasks unless other options are explicitly specified during calls to this factory's methods. - - - - - Represents an abstract scheduler for tasks. - - - - TaskScheduler acts as the extension point for all - pluggable scheduling logic. This includes mechanisms such as how to schedule a task for execution, and - how scheduled tasks should be exposed to debuggers. - - - All members of the abstract type are thread-safe - and may be used from multiple threads concurrently. - - - - - - Queues a Task to the scheduler. - - - - A class derived from TaskScheduler - implements this method to accept tasks being scheduled on the scheduler. - A typical implementation would store the task in an internal data structure, which would - be serviced by threads that would execute those tasks at some time in the future. - - - This method is only meant to be called by the .NET Framework and - should not be called directly by the derived class. This is necessary - for maintaining the consistency of the system. - - - The Task to be queued. - The argument is null. - - - - Determines whether the provided Task - can be executed synchronously in this call, and if it can, executes it. - - - - A class derived from TaskScheduler implements this function to - support inline execution of a task on a thread that initiates a wait on that task object. Inline - execution is optional, and the request may be rejected by returning false. However, better - scalability typically results the more tasks that can be inlined, and in fact a scheduler that - inlines too little may be prone to deadlocks. A proper implementation should ensure that a - request executing under the policies guaranteed by the scheduler can successfully inline. For - example, if a scheduler uses a dedicated thread to execute tasks, any inlining requests from that - thread should succeed. - - - If a scheduler decides to perform the inline execution, it should do so by calling to the base - TaskScheduler's - TryExecuteTask method with the provided task object, propagating - the return value. It may also be appropriate for the scheduler to remove an inlined task from its - internal data structures if it decides to honor the inlining request. Note, however, that under - some circumstances a scheduler may be asked to inline a task that was not previously provided to - it with the method. - - - The derived scheduler is responsible for making sure that the calling thread is suitable for - executing the given task as far as its own scheduling and execution policies are concerned. - - - The Task to be - executed. - A Boolean denoting whether or not task has previously been - queued. If this parameter is True, then the task may have been previously queued (scheduled); if - False, then the task is known not to have been queued, and this call is being made in order to - execute the task inline without queueing it. - A Boolean value indicating whether the task was executed inline. - The argument is - null. - The was already - executed. - - - - Generates an enumerable of Task instances - currently queued to the scheduler waiting to be executed. - - - - A class derived from implements this method in order to support - integration with debuggers. This method will only be invoked by the .NET Framework when the - debugger requests access to the data. The enumerable returned will be traversed by debugging - utilities to access the tasks currently queued to this scheduler, enabling the debugger to - provide a representation of this information in the user interface. - - - It is important to note that, when this method is called, all other threads in the process will - be frozen. Therefore, it's important to avoid synchronization with other threads that may lead to - blocking. If synchronization is necessary, the method should prefer to throw a - than to block, which could cause a debugger to experience delays. Additionally, this method and - the enumerable returned must not modify any globally visible state. - - - The returned enumerable should never be null. If there are currently no queued tasks, an empty - enumerable should be returned instead. - - - For developers implementing a custom debugger, this method shouldn't be called directly, but - rather this functionality should be accessed through the internal wrapper method - GetScheduledTasksForDebugger: - internal Task[] GetScheduledTasksForDebugger(). This method returns an array of tasks, - rather than an enumerable. In order to retrieve a list of active schedulers, a debugger may use - another internal method: internal static TaskScheduler[] GetTaskSchedulersForDebugger(). - This static method returns an array of all active TaskScheduler instances. - GetScheduledTasksForDebugger then may be used on each of these scheduler instances to retrieve - the list of scheduled tasks for each. - - - An enumerable that allows traversal of tasks currently queued to this scheduler. - - - This scheduler is unable to generate a list of queued tasks at this time. - - - - - Retrieves some thread static state that can be cached and passed to multiple - TryRunInline calls, avoiding superflous TLS fetches. - - A bag of TLS state (or null if none exists). - - - - Attempts to execute the target task synchronously. - - The task to run. - True if the task may have been previously queued, - false if the task was absolutely not previously queued. - The state retrieved from GetThreadStatics - True if it ran, false otherwise. - - - - Attempts to dequeue a Task that was previously queued to - this scheduler. - - The Task to be dequeued. - A Boolean denoting whether the argument was successfully dequeued. - The argument is null. - - - - Notifies the scheduler that a work item has made progress. - - - - - Initializes the . - - - - - Frees all resources associated with this scheduler. - - - - - Creates a - associated with the current . - - - All Task instances queued to - the returned scheduler will be executed through a call to the - Post method - on that context. - - - A associated with - the current SynchronizationContext, as - determined by SynchronizationContext.Current. - - - The current SynchronizationContext may not be used as a TaskScheduler. - - - - - Attempts to execute the provided Task - on this scheduler. - - - - Scheduler implementations are provided with Task - instances to be executed through either the method or the - method. When the scheduler deems it appropriate to run the - provided task, should be used to do so. TryExecuteTask handles all - aspects of executing a task, including action invocation, exception handling, state management, - and lifecycle control. - - - must only be used for tasks provided to this scheduler by the .NET - Framework infrastructure. It should not be used to execute arbitrary tasks obtained through - custom mechanisms. - - - - A Task object to be executed. - - The is not associated with this scheduler. - - A Boolean that is true if was successfully executed, false if it - was not. A common reason for execution failure is that the task had previously been executed or - is in the process of being executed by another thread. - - - - Provides an array of all queued Task instances - for the debugger. - - - The returned array is populated through a call to . - Note that this function is only meant to be invoked by a debugger remotely. - It should not be called by any other codepaths. - - An array of Task instances. - - This scheduler is unable to generate a list of queued tasks at this time. - - - - - Provides an array of all active TaskScheduler - instances for the debugger. - - - This function is only meant to be invoked by a debugger remotely. - It should not be called by any other codepaths. - - An array of TaskScheduler instances. - - - - Registers a new TaskScheduler instance in the global collection of schedulers. - - - - - Removes a TaskScheduler instance from the global collection of schedulers. - - - - - Indicates the maximum concurrency level this - is able to support. - - - - - Indicates whether this is a custom scheduler, in which case the safe code paths will be taken upon task entry - using a CAS to transition from queued state to executing. - - - - - Gets the default TaskScheduler instance. - - - - - Gets the TaskScheduler - associated with the currently executing task. - - - When not called from within a task, will return the scheduler. - - - - - Gets the unique ID for this . - - - - - Occurs when a faulted 's unobserved exception is about to trigger exception escalation - policy, which, by default, would terminate the process. - - - This AppDomain-wide event provides a mechanism to prevent exception - escalation policy (which, by default, terminates the process) from triggering. - Each handler is passed a - instance, which may be used to examine the exception and to mark it as observed. - - - - - Nested class that provides debugger view for TaskScheduler - - - - Default thread pool scheduler. - - - - A TaskScheduler implementation that executes all tasks queued to it through a call to - on the - that its associated with. The default constructor for this class binds to the current - - - - - Constructs a SynchronizationContextTaskScheduler associated with - - This constructor expects to be set. - - - - Implemetation of for this scheduler class. - - Simply posts the tasks to be executed on the associated . - - - - - - Implementation of for this scheduler class. - - The task will be executed inline only if the call happens within - the associated . - - - - - - - Implementes the property for - this scheduler class. - - By default it returns 1, because a based - scheduler only supports execution on a single thread. - - - - - Provides data for the event that is raised when a faulted 's - exception goes unobserved. - - - The Exception property is used to examine the exception without marking it - as observed, whereas the method is used to mark the exception - as observed. Marking the exception as observed prevents it from triggering exception escalation policy - which, by default, terminates the process. - - - - - Initializes a new instance of the class - with the unobserved exception. - - The Exception that has gone unobserved. - - - - Marks the as "observed," thus preventing it - from triggering exception escalation policy which, by default, terminates the process. - - - - - Gets whether this exception has been marked as "observed." - - - - - The Exception that went unobserved. - - - - - Represents an exception used to communicate an invalid operation by a - . - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the - class using the default error message and a reference to the inner exception that is the cause of - this exception. - - The exception that is the cause of the current exception. - - - - Initializes a new instance of the - class with a specified error message and a reference to the inner exception that is the cause of - this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.IO.dll b/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.IO.dll deleted file mode 100644 index 32dd41b78..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.IO.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.IO.xml b/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.IO.xml deleted file mode 100644 index e83273427..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.IO.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - System.IO - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Found invalid data while decoding.. - - - - - The exception that is thrown when a data stream is in an invalid format. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class with a reference to the inner exception that is the cause of this exception. - The error message that explains the reason for the exception. - The exception that is the cause of the current exception. If the parameter is not null, the current exception is raised in a catch block that handles the inner exception. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.Runtime.dll b/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.Runtime.dll deleted file mode 100644 index 118fcce20..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.Runtime.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.Runtime.xml b/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.Runtime.xml deleted file mode 100644 index 93cb00d74..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.Runtime.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - System.Runtime - - - - Defines a provider for progress updates. - The type of progress update value. - - - Reports a progress update. - The value of the updated progress. - - - Identities the async state machine type for this method. - - - Identities the state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - Gets the type that implements the state machine. - - - Initializes the attribute. - The type that implements the state machine. - - - - Allows you to obtain the method or property name of the caller to the method. - - - - - Allows you to obtain the line number in the source file at which the method is called. - - - - - Allows you to obtain the full path of the source file that contains the caller. - This is the file path at the time of compile. - - - - Identities the iterator state machine type for this method. - - - Initializes the attribute. - The type that implements the state machine. - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.Threading.Tasks.dll b/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.Threading.Tasks.dll deleted file mode 100644 index a60ab2657..000000000 Binary files a/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.Threading.Tasks.xml b/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.Threading.Tasks.xml deleted file mode 100644 index b47921e5d..000000000 --- a/packages/Microsoft.Bcl.1.1.10/lib/sl5/System.Threading.Tasks.xml +++ /dev/null @@ -1,475 +0,0 @@ - - - - System.Threading.Tasks - - - - Holds state related to the builder's IAsyncStateMachine. - This is a mutable struct. Be very delicate with it. - - - A reference to the heap-allocated state machine object associated with this builder. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument is null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - - Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method. - On first invocation, the supplied state machine will be boxed. - - Specifies the type of the method builder used. - Specifies the type of the state machine used. - The builder. - The state machine. - An Action to provide to the awaiter. - - - Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext. - - - The context with which to run MoveNext. - - - The state machine whose MoveNext method should be invoked. - - - Initializes the runner. - The context with which to run MoveNext. - - - Invokes MoveNext under the provided context. - - - Cached delegate used with ExecutionContext.Run. - - - Invokes the MoveNext method on the supplied IAsyncStateMachine. - The IAsyncStateMachine machine instance. - - - Provides a base class used to cache tasks of a specific return type. - Specifies the type of results the cached tasks return. - - - - A singleton cache for this result type. - This may be null if there are no cached tasks for this TResult. - - - - Creates a non-disposable task. - The result for the task. - The cacheable task. - - - Creates a cache. - A task cache for this result type. - - - Gets a cached task if one exists. - The result for which we want a cached task. - A cached task if one exists; otherwise, null. - - - Provides a cache for Boolean tasks. - - - A true task. - - - A false task. - - - Gets a cached task for the Boolean result. - true or false - A cached task for the Boolean result. - - - Provides a cache for zero Int32 tasks. - - - The minimum value, inclusive, for which we want a cached task. - - - The maximum value, exclusive, for which we want a cached task. - - - The cache of Task{Int32}. - - - Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). - - - Gets a cached task for the zero Int32 result. - The integer value - A cached task for the Int32 result or null if not cached. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - Represents an asynchronous method builder. - - - A cached VoidTaskResult task used for builders that complete synchronously. - - - The generic builder object to which this non-generic instance delegates. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state. - - The builder is not initialized. - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - - Gets the for this builder. - The representing the builder's asynchronous operation. - The builder is not initialized. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - - Provides a builder for asynchronous methods that return . - This type is intended for compiler use only. - - - AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value. - Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, - or else the copies may end up building distinct Task instances. - - - - A cached task for default(TResult). - - - State related to the IAsyncStateMachine. - - - The lazily-initialized task. - Must be named m_task for debugger step-over to work correctly. - - - The lazily-initialized task completion source. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Initializes a new . - The initialized . - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Completes the in the - RanToCompletion state with the specified result. - - The result to use to complete the task. - The task has already completed. - - - - Completes the builder by using either the supplied completed task, or by completing - the builder's previously accessed task using default(TResult). - - A task already completed with the value default(TResult). - The task has already completed. - - - - Completes the in the - Faulted state with the specified exception. - - The to use to fault the task. - The argument is null (Nothing in Visual Basic). - The task has already completed. - - - - Called by the debugger to request notification when the first wait operation - (await, Wait, Result, etc.) on this builder's task completes. - - - true to enable notification; false to disable a previously set notification. - - - This should only be invoked from within an asynchronous method, - and only by the debugger. - - - - - Gets a task for the specified result. This will either - be a cached or new task, never null. - - The result for which we need a task. - The completed task containing the result. - - - Gets the lazily-initialized TaskCompletionSource. - - - Gets the for this builder. - The representing the builder's asynchronous operation. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger, and only in a single-threaded manner - when no other threads are in the middle of accessing this property or this.Task. - - - - - Provides a builder for asynchronous methods that return void. - This type is intended for compiler use only. - - - - The synchronization context associated with this operation. - - - State related to the IAsyncStateMachine. - - - An object used by the debugger to uniquely identify this builder. Lazily initialized. - - - Temporary support for disabling crashing if tasks go unobserved. - - - Registers with UnobservedTaskException to suppress exception crashing. - - - Non-zero if PreventUnobservedTaskExceptions has already been invoked. - - - Initializes a new . - The initialized . - - - Initializes the . - The synchronizationContext associated with this operation. This may be null. - - - Initiates the builder's execution with the associated state machine. - Specifies the type of the state machine. - The state machine instance, passed by reference. - The argument was null (Nothing in Visual Basic). - - - Associates the builder with the state machine it represents. - The heap-allocated state machine object. - The argument was null (Nothing in Visual Basic). - The builder is incorrectly initialized. - - - Perform any initialization necessary prior to lifting the builder to the heap. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - - Schedules the specified state machine to be pushed forward when the specified awaiter completes. - - Specifies the type of the awaiter. - Specifies the type of the state machine. - The awaiter. - The state machine. - - - Completes the method builder successfully. - - - Faults the method builder with an exception. - The exception that is the cause of this fault. - The argument is null (Nothing in Visual Basic). - The builder is not initialized. - - - Notifies the current synchronization context that the operation completed. - - - - Gets an object that may be used to uniquely identify this builder to the debugger. - - - This property lazily instantiates the ID in a non-thread-safe manner. - It must only be used by the debugger and only in a single-threaded manner. - - - - - Represents state machines generated for asynchronous methods. - This type is intended for compiler use only. - - - - Moves the state machine to its next state. - - - Configures the state machine with a heap-allocated replica. - The heap-allocated replica. - - - - Represents an awaiter used to schedule continuations when an await operation completes. - - - - - Represents an operation that will schedule continuations when the operation completes. - - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - - - Schedules the continuation action to be invoked when the instance completes. - The action to invoke when the operation completes. - The argument is null (Nothing in Visual Basic). - Unlike OnCompleted, UnsafeOnCompleted need not propagate ExecutionContext information. - - - Used with Task(of void) - - - diff --git a/packages/Microsoft.Bcl.1.1.10/lib/win8/_._ b/packages/Microsoft.Bcl.1.1.10/lib/win8/_._ deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/wp8/_._ b/packages/Microsoft.Bcl.1.1.10/lib/wp8/_._ deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.1.1.10/lib/wpa81/_._ b/packages/Microsoft.Bcl.1.1.10/lib/wpa81/_._ deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/Microsoft.Bcl.Async.1.0.168/License-Stable.rtf b/packages/Microsoft.Bcl.Async.1.0.168/License-Stable.rtf deleted file mode 100644 index 3aec6b654..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/License-Stable.rtf +++ /dev/null @@ -1,118 +0,0 @@ -{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fswiss\fprq2\fcharset0 Tahoma;}{\f1\froman\fprq2\fcharset0 Times New Roman;}{\f2\fswiss\fprq2\fcharset0 Calibri;}{\f3\fnil\fcharset0 Calibri;}{\f4\fnil\fcharset2 Symbol;}} -{\colortbl ;\red31\green73\blue125;\red0\green0\blue255;} -{\*\listtable -{\list\listhybrid -{\listlevel\levelnfc0\leveljc0\levelstartat1{\leveltext\'02\'00.;}{\levelnumbers\'01;}\jclisttab\tx360} -{\listlevel\levelnfc4\leveljc0\levelstartat1{\leveltext\'02\'01.;}{\levelnumbers\'01;}\jclisttab\tx363} -{\listlevel\levelnfc2\leveljc0\levelstartat1{\leveltext\'02\'02.;}{\levelnumbers\'01;}\jclisttab\tx720}\listid1 } -{\list\listhybrid -{\listlevel\levelnfc0\leveljc0\levelstartat1{\leveltext\'02\'00.;}{\levelnumbers\'01;}\jclisttab\tx363} -{\listlevel\levelnfc4\leveljc0\levelstartat1{\leveltext\'02\'01.;}{\levelnumbers\'01;}\jclisttab\tx363}\listid2 }} -{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}} -{\stylesheet{ Normal;}{\s1 heading 1;}{\s2 heading 2;}{\s3 heading 3;}} -{\*\generator Riched20 6.2.9200}\viewkind4\uc1 -\pard\nowidctlpar\sb120\sa120\b\f0\fs24 MICROSOFT SOFTWARE LICENSE TERMS\par - -\pard\brdrb\brdrs\brdrw10\brsp20 \nowidctlpar\sb120\sa120 MICROSOFT .NET LIBRARY \par - -\pard\nowidctlpar\sb120\sa120\fs19 These license terms are an agreement between Microsoft Corporation (or based on where you live, one of its affiliates) and you. Please read them. They apply to the software named above, which includes the media on which you received it, if any. The terms also apply to any Microsoft\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent363{\pntxtb\'B7}}\nowidctlpar\fi-363\li720\sb120\sa120\b0 updates,\par -{\pntext\f4\'B7\tab}supplements,\par -{\pntext\f4\'B7\tab}Internet-based services, and\par -{\pntext\f4\'B7\tab}support services\par - -\pard\nowidctlpar\sb120\sa120\b for this software, unless other terms accompany those items. If so, those terms apply.\par -BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS. IF YOU DO NOT ACCEPT THEM, DO NOT USE THE SOFTWARE.\par - -\pard\brdrt\brdrs\brdrw10\brsp20 \nowidctlpar\sb120\sa120 IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE PERPETUAL RIGHTS BELOW.\par - -\pard -{\listtext\f0 1.\tab}\jclisttab\tx360\ls1\nowidctlpar\s1\fi-357\li357\sb120\sa120 INSTALLATION AND USE RIGHTS. \par - -\pard -{\listtext\f0 a.\tab}\jclisttab\tx363\ls1\ilvl1\nowidctlpar\s2\fi-363\li720\sb120\sa120 Installation and Use.\b0\fs20 You may install and use any number of copies of the software to design, develop and test your programs.\par -{\listtext\f0 b.\tab}\b\fs19 Third Party Programs.\b0\fs20 The software may include third party programs that Microsoft, not the third party, licenses to you under this agreement. Notices, if any, for the third party program are included for your information only.\b\fs19\par - -\pard -{\listtext\f0 2.\tab}\jclisttab\tx360\ls1\nowidctlpar\s1\fi-357\li357\sb120\sa120\fs20 ADDITIONAL LICENSING REQUIREMENTS AND/OR USE RIGHTS.\par - -\pard -{\listtext\f0 a.\tab}\jclisttab\tx363\ls1\ilvl1\nowidctlpar\s2\fi-363\li720\sb120\sa120 DISTRIBUTABLE CODE.\~ \b0 The software is comprised of Distributable Code. \f1\ldblquote\f0 Distributable Code\f1\rdblquote\f0 is code that you are permitted to distribute in programs you develop if you comply with the terms below.\b\par - -\pard -{\listtext\f0 i.\tab}\jclisttab\tx720\ls1\ilvl2\nowidctlpar\s3\fi-357\li1077\sb120\sa120\tx1077 Right to Use and Distribute. \par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\fi-357\li1434\sb120\sa120\b0 You may copy and distribute the object code form of the software.\par -{\pntext\f4\'B7\tab}Third Party Distribution. You may permit distributors of your programs to copy and distribute the Distributable Code as part of those programs.\par - -\pard\nowidctlpar\s3\fi-357\li1077\sb120\sa120\tx1077\b ii.\tab Distribution Requirements.\b0 \b For any Distributable Code you distribute, you must\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\fi-357\li1434\sb120\sa120\b0 add significant primary functionality to it in your programs;\par -{\pntext\f4\'B7\tab}require distributors and external end users to agree to terms that protect it at least as much as this agreement;\par -{\pntext\f4\'B7\tab}display your valid copyright notice on your programs; and\par -{\pntext\f4\'B7\tab}indemnify, defend, and hold harmless Microsoft from any claims, including attorneys\rquote fees, related to the distribution or use of your programs.\par - -\pard\nowidctlpar\s3\fi-357\li1077\sb120\sa120\tx1077\b iii.\tab Distribution Restrictions.\b0 \b You may not\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\fi-357\li1434\sb120\sa120\b0 alter any copyright, trademark or patent notice in the Distributable Code;\par -{\pntext\f4\'B7\tab}use Microsoft\rquote s trademarks in your programs\rquote names or in a way that suggests your programs come from or are endorsed by Microsoft;\par -{\pntext\f4\'B7\tab}include Distributable Code in malicious, deceptive or unlawful programs; or\par -{\pntext\f4\'B7\tab}modify or distribute the source code of any Distributable Code so that any part of it becomes subject to an Excluded License. An Excluded License is one that requires, as a condition of use, modification or distribution, that\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\fi-358\li1792\sb120\sa120 the code be disclosed or distributed in source code form; or\cf1\f2\par -{\pntext\f4\'B7\tab}\cf0\f0 others have the right to modify it.\cf1\f2\par - -\pard\nowidctlpar\s1\fi-357\li357\sb120\sa120\cf0\b\f0 3.\tab\fs19 SCOPE OF LICENSE. \b0 The software is licensed, not sold. This agreement only gives you some rights to use the software. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the software only as expressly permitted in this agreement. In doing so, you must comply with any technical limitations in the software that only allow you to use it in certain ways. You may not\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent363{\pntxtb\'B7}}\nowidctlpar\fi-363\li720\sb120\sa120 work around any technical limitations in the software;\par -{\pntext\f4\'B7\tab}reverse engineer, decompile or disassemble the software, except and only to the extent that applicable law expressly permits, despite this limitation;\par -{\pntext\f4\'B7\tab}publish the software for others to copy;\par -{\pntext\f4\'B7\tab}rent, lease or lend the software;\par -{\pntext\f4\'B7\tab}transfer the software or this agreement to any third party; or\par -{\pntext\f4\'B7\tab}use the software for commercial software hosting services.\par - -\pard\nowidctlpar\s1\fi-357\li357\sb120\sa120\b\fs20 4.\tab\fs19 BACKUP COPY. \b0 You may make one backup copy of the software. You may use it only to reinstall the software.\par -\b\fs20 5.\tab\fs19 DOCUMENTATION. \b0 Any person that has valid access to your computer or internal network may copy and use the documentation for your internal, reference purposes.\par -\b\fs20 6.\tab\fs19 EXPORT RESTRICTIONS. \b0 The software is subject to United States export laws and regulations. You must comply with all domestic and international export laws and regulations that apply to the software. These laws include restrictions on destinations, end users and end use. For additional information, see {\cf2\ul\fs20{\field{\*\fldinst{HYPERLINK www.microsoft.com/exporting }}{\fldrslt{www.microsoft.com/exporting}}}}\f0\fs19 .\cf2\ul\fs20\par -\cf0\ulnone\b 7.\tab\fs19 SUPPORT SERVICES. \b0 Because this software is \ldblquote as is,\rdblquote we may not provide support services for it.\par -\b\fs20 8.\tab\fs19 ENTIRE AGREEMENT. \b0 This agreement, and the terms for supplements, updates, Internet-based services and support services that you use, are the entire agreement for the software and support services.\par -\b\fs20 9.\tab\fs19 APPLICABLE LAW.\par - -\pard -{\listtext\f0 a.\tab}\jclisttab\tx363\ls2\ilvl1\nowidctlpar\s2\fi-363\li720\sb120\sa120 United States. \b0 If you acquired the software in the United States, Washington state law governs the interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws principles. The laws of the state where you live govern all other claims, including claims under state consumer protection laws, unfair competition laws, and in tort.\par -{\listtext\f0 b.\tab}\b Outside the United States. If you acquired the software in any other country, the laws of that country apply.\par - -\pard\nowidctlpar\s1\fi-357\li357\sb120\sa120\fs20 10.\tab\fs19 LEGAL EFFECT. \b0 This agreement describes certain legal rights. You may have other rights under the laws of your country. You may also have rights with respect to the party from whom you acquired the software. This agreement does not change your rights under the laws of your country if the laws of your country do not permit it to do so.\par -\b\fs20 11.\tab\fs19 DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED \ldblquote AS-IS.\rdblquote YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES OR CONDITIONS. YOU MAY HAVE ADDITIONAL CONSUMER RIGHTS OR STATUTORY GUARANTEES UNDER YOUR LOCAL LAWS WHICH THIS AGREEMENT CANNOT CHANGE. TO THE EXTENT PERMITTED UNDER YOUR LOCAL LAWS, MICROSOFT EXCLUDES THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.\par - -\pard\nowidctlpar\li357\sb120\sa120 FOR AUSTRALIA \endash YOU HAVE STATUTORY GUARANTEES UNDER THE AUSTRALIAN CONSUMER LAW AND NOTHING IN THESE TERMS IS INTENDED TO AFFECT THOSE RIGHTS.\par - -\pard\nowidctlpar\s1\fi-357\li357\sb120\sa120\fs20 12.\tab\fs19 LIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES. YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES.\par - -\pard\nowidctlpar\li357\sb120\sa120\b0 This limitation applies to\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent363{\pntxtb\'B7}}\nowidctlpar\fi-363\li720\sb120\sa120 anything related to the software, services, content (including code) on third party Internet sites, or third party programs; and\par -{\pntext\f4\'B7\tab}claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the extent permitted by applicable law.\par - -\pard\nowidctlpar\sb120\sa120 It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential or other damages.\par -\lang9 Please note: As this software is distributed in Quebec, Canada, some of the clauses in this agreement are provided below in French.\par -Remarque : Ce logiciel \'e9tant distribu\'e9 au Qu\'e9bec, Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en fran\'e7ais.\par - -\pard\nowidctlpar\s1\sb120\sa120\b\lang1033 EXON\'c9RATION DE GARANTIE. \b0 Le logiciel vis\'e9 par une licence est offert \'ab tel quel \'bb. Toute utilisation de ce logiciel est \'e0 votre seule risque et p\'e9ril. Microsoft n\rquote accorde aucune autre garantie expresse. Vous pouvez b\'e9n\'e9ficier de droits additionnels en vertu du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualit\'e9 marchande, d\rquote ad\'e9quation \'e0 un usage particulier et d\rquote absence de contrefa\'e7on sont exclues.\par -\b LIMITATION DES DOMMAGES-INT\'c9R\'caTS ET EXCLUSION DE RESPONSABILIT\'c9 POUR LES DOMMAGES. \b0 Vous pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de dommages directs uniquement \'e0 hauteur de 5,00 $ US. Vous ne pouvez pr\'e9tendre \'e0 aucune indemnisation pour les autres dommages, y compris les dommages sp\'e9ciaux, indirects ou accessoires et pertes de b\'e9n\'e9fices.\par - -\pard\nowidctlpar\sb120\sa120\lang9 Cette limitation concerne :\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\li720\sb120\sa120 tout ce qui est reli\'e9 au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et\par -{\pntext\f4\'B7\tab}les r\'e9clamations au titre de violation de contrat ou de garantie, ou au titre de responsabilit\'e9 stricte, de n\'e9gligence ou d\rquote une autre faute dans la limite autoris\'e9e par la loi en vigueur.\par - -\pard\nowidctlpar\sb120\sa120 Elle s\rquote applique \'e9galement, m\'eame si Microsoft connaissait ou devrait conna\'eetre l\rquote\'e9ventualit\'e9 d\rquote un tel dommage. Si votre pays n\rquote autorise pas l\rquote exclusion ou la limitation de responsabilit\'e9 pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l\rquote exclusion ci-dessus ne s\rquote appliquera pas \'e0 votre \'e9gard.\par - -\pard\nowidctlpar\s1\sb120\sa120\b\lang1033 EFFET JURIDIQUE. \b0 Le pr\'e9sent contrat d\'e9crit certains droits juridiques. Vous pourriez avoir d\rquote autres droits pr\'e9vus par les lois de votre pays. Le pr\'e9sent contrat ne modifie pas les droits que vous conf\'e8rent les lois de votre pays si celles-ci ne le permettent pas.\par - -\pard\nowidctlpar\sb120\sa120\b\fs20\lang1036\par - -\pard\sa200\sl276\slmult1\b0\f3\fs22\lang9\par -} - \ No newline at end of file diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.Extensions.Desktop.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.Extensions.Desktop.dll deleted file mode 100644 index 1288a1770..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.Extensions.Desktop.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.Extensions.Desktop.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.Extensions.Desktop.xml deleted file mode 100644 index 6fad7c97a..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.Extensions.Desktop.xml +++ /dev/null @@ -1,684 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions.Desktop - - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - - Provides asynchronous wrappers for .NET Framework operations. - - - Provides asynchronous wrappers for .NET Framework operations. - - - - Downloads the resource with the specified URI as a string, asynchronously. - The WebClient. - The URI from which to download data. - A Task that contains the downloaded string. - - - Downloads the resource with the specified URI as a string, asynchronously. - The WebClient. - The URI from which to download data. - A Task that contains the downloaded string. - - - Opens a readable stream for the data downloaded from a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a readable stream for the data downloaded from a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - The HTTP method that should be used to open the stream. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - The HTTP method that should be used to open the stream. - A Task that contains the opened stream. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The HTTP method that should be used to upload the data. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The HTTP method that should be used to upload the data. - The data to upload. - A Task containing the data in the response from the upload. - - - Converts a path to a Uri using the WebClient's logic. - Based on WebClient's private GetUri method. - - - Converts a path to a Uri using the WebClient's logic. - Based on WebClient's private GetUri method. - - - Downloads the resource with the specified URI as a byte array, asynchronously. - The WebClient. - The URI from which to download data. - A Task that contains the downloaded data. - - - Downloads the resource with the specified URI as a byte array, asynchronously. - The WebClient. - The URI from which to download data. - A Task that contains the downloaded data. - - - Downloads the resource with the specified URI to a local file, asynchronously. - The WebClient. - The URI from which to download data. - The name of the local file that is to receive the data. - A Task that contains the downloaded data. - - - Downloads the resource with the specified URI to a local file, asynchronously. - The WebClient. - The URI from which to download data. - The name of the local file that is to receive the data. - A Task that contains the downloaded data. - - - Uploads data to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The HTTP method that should be used to upload the data. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The HTTP method that should be used to upload the data. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads a file to the specified resource, asynchronously. - The WebClient. - The URI to which the file should be uploaded. - A path to the file to upload. - A Task containing the data in the response from the upload. - - - Uploads a file to the specified resource, asynchronously. - The WebClient. - The URI to which the file should be uploaded. - A path to the file to upload. - A Task containing the data in the response from the upload. - - - Uploads a file to the specified resource, asynchronously. - The WebClient. - The URI to which the file should be uploaded. - The HTTP method that should be used to upload the file. - A path to the file to upload. - A Task containing the data in the response from the upload. - - - Uploads a file to the specified resource, asynchronously. - The WebClient. - The URI to which the file should be uploaded. - The HTTP method that should be used to upload the file. - A path to the file to upload. - A Task containing the data in the response from the upload. - - - Causes an online announcement (Hello) message to be sent asynchronously with the specified endpoint discovery metadata and user-defined state. The specified is called when the operation completes. - Task instance. - The endpoint discovery metadata. - The source. - - - Causes an offline announcement (Bye) message to be sent asynchronously with the specified endpoint discovery metadata and user-defined state. The specified is called when the operation completes. - Task instance. - The endpoint discovery metadata. - The source. - - - Begins asynchronously retrieving an incoming request. - Task object that indicates the status of the asynchronous operation. - A Win32 function call failed. Check the exception's property to determine the cause of the exception. - This object has not been started or is currently stopped. - This object is closed. - The source. - - - Starts an asynchronous request for the client's X.509 v.3 certificate. - Task that indicates the status of the operation. - The source. - - - Called by clients to begin an asynchronous operation to authenticate the client, and optionally the server, in a client-server connection. This method does not block. - Task object indicating the status of the asynchronous operation. - The authentication failed. You can use this object to retry the authentication. - The authentication failed. You can use this object to retry the authentication. - This object has been closed. - Authentication has already occurred.- or -This stream was used previously to attempt authentication as the server. You cannot use the stream to retry authentication as the client. - The source. - - - Called by clients to begin an asynchronous operation to authenticate the client, and optionally the server, in a client-server connection. The authentication process uses the specified credentials. This method does not block. - Task object indicating the status of the asynchronous operation. - The that is used to establish the identity of the client. - The Service Principal Name (SPN) that uniquely identifies the server to authenticate. - is null.- or - is null. - The authentication failed. You can use this object to retry the authentication. - The authentication failed. You can use this object to retry the authentication. - This object has been closed. - Authentication has already occurred.- or -This stream was used previously to attempt authentication as the server. You cannot use the stream to retry authentication as the client. - The source. - - - Called by clients to begin an asynchronous operation to authenticate the client, and optionally the server, in a client-server connection. The authentication process uses the specified credentials and channel binding. This method does not block. - Task object indicating the status of the asynchronous operation. - The that is used to establish the identity of the client. - The that is used for extended protection. - The Service Principal Name (SPN) that uniquely identifies the server to authenticate. - is null.- or - is null. - The authentication failed. You can use this object to retry the authentication. - The authentication failed. You can use this object to retry the authentication. - Authentication has already occurred.- or -This stream was used previously to attempt authentication as the server. You cannot use the stream to retry authentication as the client. - This object has been closed. - The source. - - - Called by servers to begin an asynchronous operation to authenticate the client, and optionally the server, in a client-server connection. This method does not block. - Task object indicating the status of the asynchronous operation. - The authentication failed. You can use this object to retry the authentication. - The authentication failed. You can use this object to retry the authentication. - This object has been closed. - Windows 95 and Windows 98 are not supported. - The source. - - - Called by servers to begin an asynchronous operation to authenticate the client, and optionally the server, in a client-server connection. The authentication process uses the specified extended protection policy. This method does not block. - Task object indicating the status of the asynchronous operation. - The that is used for extended protection. - The and on the extended protection policy passed in the parameter are both null. - The authentication failed. You can use this object to retry the authentication. - The authentication failed. You can use this object to retry the authentication. - Windows 95 and Windows 98 are not supported. - This object has been closed. - The source. - - - Called by servers to begin an asynchronous operation to authenticate the client, and optionally the server, in a client-server connection. The authentication process uses the specified server credentials and authentication options. This method does not block. - Task object indicating the status of the asynchronous operation. - The that is used to establish the identity of the client. - One of the values, indicating the security services for the stream. - One of the values, indicating how the server can use the client's credentials to access resources. - is null. - must be , , or , - The authentication failed. You can use this object to retry the authentication. - The authentication failed. You can use this object to retry the authentication. - This object has been closed. - Authentication has already occurred.- or -This stream was used previously to attempt authentication as the client. You cannot use the stream to retry authentication as the server. - Windows 95 and Windows 98 are not supported. - The source. - - - Called by clients to begin an asynchronous operation to authenticate the server and optionally the client. - Task object that indicates the status of the asynchronous operation. - The name of the server that shares this . - is null. - The authentication failed and left this object in an unusable state. - Authentication has already occurred.-or-Server authentication using this was tried previously.-or- Authentication is already in progress. - This object has been closed. - The source. - - - Called by servers to begin an asynchronous operation to authenticate the client and optionally the server in a client-server connection. - Task object indicating the status of the asynchronous operation. - The X509Certificate used to authenticate the server. - is null. - The authentication failed and left this object in an unusable state. - Authentication has already occurred.-or-Client authentication using this was tried previously.-or- Authentication is already in progress. - This object has been closed. - The method is not supported on Windows 95, Windows 98, or Windows Millennium. - The source. - - - Starts an asynchronous request for a remote host connection. The host is specified by a host name and a port number. - Task that represents the asynchronous connection. - The name of the remote host. - The port number of the remote host. - is null. - The has been closed. - This method is valid for sockets in the or families. - The port number is not valid. - The is ing. - - The source. - - - Starts an asynchronous request for a remote host connection. The host is specified by an and a port number. - Task that represents the asynchronous connection. - The of the remote host. - The port number of the remote host. - is null. - An error occurred when attempting to access the socket. See the Remarks section for more information. - The has been closed. - The is not in the socket family. - The port number is not valid. - The length of is zero. - The is ing. - - The source. - - - Starts an asynchronous request for a remote host connection. The host is specified by an array and a port number. - Task that represents the asynchronous connections. - At least one , designating the remote host. - The port number of the remote host. - is null. - An error occurred when attempting to access the socket. See the Remarks section for more information. - The has been closed. - This method is valid for sockets that use or . - The port number is not valid. - The length of is zero. - The is ing. - - The source. - - - Starts an asynchronous operation to accept an incoming connection attempt. - Task that represents the asynchronous creation of the . - An error occurred while attempting to access the socket. See the Remarks section for more information. - The has been closed. - - The source. - - - Starts an asynchronous operation to accept an incoming connection attempt. - Task that represents the asynchronous creation of the . - An error occurred while attempting to access the socket. See the Remarks section for more information. - The has been closed. - - The source. - - - Sends a datagram to a destination asynchronously. The destination is specified by a . - Task object that represents the asynchronous send. - A array that contains the data to be sent. - The number of bytes to send. - The that represents the destination for the data. - The source. - - - Sends a datagram to a remote host asynchronously. The destination was specified previously by a call to . - Task object that represents the asynchronous send. - A array that contains the data to be sent. - The number of bytes to send. - The source. - - - Sends a datagram to a remote host asynchronously. The destination was specified previously by a call to . - Task object that represents the asynchronous send. - A array that contains the data to be sent. - The number of bytes to send. - The host name. - The host name. - The source. - - - Starts an asynchronous request to retrieve the stable unicast IP address table on the local computer. - Task that represents the asynchronous request. - This method is not implemented on the platform. This method uses the native NotifyStableUnicastIpAddressTable function that is supported on Windows Vista and later. - The call to the native NotifyStableUnicastIpAddressTable function failed. - The source. - - - Opens the connection asynchronously. - The source. - Task that represents the asynchronous request. - - - Opens the connection asynchronously. - The source. - The cancellation token. - Task that represents the asynchronous request. - - - Initiates the asynchronous execution of the Transact-SQL statement or stored procedure that is described by this , given a callback procedure and state information. - Task that can be used to poll or wait for results, or both; this value is also needed when invoking , which returns the number of affected rows. - Any error that occurred while executing the command text. - The name/value pair "Asynchronous Processing=true" was not included within the connection string defining the connection for this . - 2 - The source. - - - Initiates the asynchronous execution of the Transact-SQL statement or stored procedure that is described by this , given a callback procedure and state information. - Task that can be used to poll or wait for results, or both; this value is also needed when invoking , which returns the number of affected rows. - Any error that occurred while executing the command text. - The name/value pair "Asynchronous Processing=true" was not included within the connection string defining the connection for this . - 2 - The cancellation token. - The source. - - - Initiates the asynchronous execution of the Transact-SQL statement or stored procedure that is described by this and returns results as an object, using a callback procedure. - Task that can be used to poll, wait for results, or both; this value is also needed when the is called, which returns the results of the command as XML. - Any error that occurred while executing the command text. - The name/value pair "Asynchronous Processing=true" was not included within the connection string defining the connection for this . - 2 - The source. - - - Initiates the asynchronous execution of the Transact-SQL statement or stored procedure that is described by this and returns results as an object, using a callback procedure. - Task that can be used to poll, wait for results, or both; this value is also needed when the is called, which returns the results of the command as XML. - Any error that occurred while executing the command text. - The name/value pair "Asynchronous Processing=true" was not included within the connection string defining the connection for this . - 2 - The cancellation token. - The source. - - - Initiates the asynchronous execution of the Transact-SQL statement or stored procedure that is described by this and retrieves one or more result sets from the server, given a callback procedure and state information. - Task that can be used to poll, wait for results, or both; this value is also needed when invoking , which returns a instance which can be used to retrieve the returned rows. - Any error that occurred while executing the command text. - The name/value pair "Asynchronous Processing=true" was not included within the connection string defining the connection for this . - 2 - The source. - - - Initiates the asynchronous execution of the Transact-SQL statement or stored procedure that is described by this and retrieves one or more result sets from the server, given a callback procedure and state information. - Task that can be used to poll, wait for results, or both; this value is also needed when invoking , which returns a instance which can be used to retrieve the returned rows. - Any error that occurred while executing the command text. - The name/value pair "Asynchronous Processing=true" was not included within the connection string defining the connection for this . - 2 - The cancellation token. - The source. - - - Starts an asynchronous method call that returns a . - The metadata. - The source. - - - Starts an asynchronous method call that returns a using the specified address, callback, asynchronous state, and download mechanism. - The metadata obtained from the specified . - The address of the metadata. - The value to use when downloading the metadata. - The source. - - - Starts an asynchronous method call that returns a using the specified address, callback, and asynchronous state. - The metadata obtained from the specified . - The address of the metadata. - The source. - - - - Begins an asynchronous find operation with the specified criteria. - - The discovery client. - The criteria for finding services. - A Task that represents the asynchronous operation. - - - - Begins an asynchronous resolve operation with the specified criteria. - - The discovery client. - The criteria for matching a service endpoint. - A Task that represents the asynchronous operation. - - - - Asynchronously attempts to send an Internet Control Message Protocol (ICMP) echo message. - - The Ping. - An IPAddress that identifies the computer that is the destination for the ICMP echo message. - A task that represents the asynchronous operation. - - - - Asynchronously attempts to send an Internet Control Message Protocol (ICMP) echo message. - - The Ping. - - A String that identifies the computer that is the destination for the ICMP echo message. - The value specified for this parameter can be a host name or a string representation of an IP address. - - A task that represents the asynchronous operation. - - - - Asynchronously attempts to send an Internet Control Message Protocol (ICMP) echo message. - - The Ping. - An IPAddress that identifies the computer that is the destination for the ICMP echo message. - - An Int32 value that specifies the maximum number of milliseconds (after sending the echo message) - to wait for the ICMP echo reply message. - - A task that represents the asynchronous operation. - - - - Asynchronously attempts to send an Internet Control Message Protocol (ICMP) echo message. - - The Ping. - - A String that identifies the computer that is the destination for the ICMP echo message. - The value specified for this parameter can be a host name or a string representation of an IP address. - - - An Int32 value that specifies the maximum number of milliseconds (after sending the echo message) - to wait for the ICMP echo reply message. - - A task that represents the asynchronous operation. - - - - Asynchronously attempts to send an Internet Control Message Protocol (ICMP) echo message. - - The Ping. - An IPAddress that identifies the computer that is the destination for the ICMP echo message. - - An Int32 value that specifies the maximum number of milliseconds (after sending the echo message) - to wait for the ICMP echo reply message. - - - A Byte array that contains data to be sent with the ICMP echo message and returned - in the ICMP echo reply message. The array cannot contain more than 65,500 bytes. - - A task that represents the asynchronous operation. - - - - Asynchronously attempts to send an Internet Control Message Protocol (ICMP) echo message. - - The Ping. - - A String that identifies the computer that is the destination for the ICMP echo message. - The value specified for this parameter can be a host name or a string representation of an IP address. - - - An Int32 value that specifies the maximum number of milliseconds (after sending the echo message) - to wait for the ICMP echo reply message. - - - A Byte array that contains data to be sent with the ICMP echo message and returned - in the ICMP echo reply message. The array cannot contain more than 65,500 bytes. - - A task that represents the asynchronous operation. - - - - Asynchronously attempts to send an Internet Control Message Protocol (ICMP) echo message. - - The Ping. - An IPAddress that identifies the computer that is the destination for the ICMP echo message. - - An Int32 value that specifies the maximum number of milliseconds (after sending the echo message) - to wait for the ICMP echo reply message. - - - A Byte array that contains data to be sent with the ICMP echo message and returned - in the ICMP echo reply message. The array cannot contain more than 65,500 bytes. - - A PingOptions object used to control fragmentation and Time-to-Live values for the ICMP echo message packet. - A task that represents the asynchronous operation. - - - - Asynchronously attempts to send an Internet Control Message Protocol (ICMP) echo message. - - The Ping. - - A String that identifies the computer that is the destination for the ICMP echo message. - The value specified for this parameter can be a host name or a string representation of an IP address. - - - An Int32 value that specifies the maximum number of milliseconds (after sending the echo message) - to wait for the ICMP echo reply message. - - - A Byte array that contains data to be sent with the ICMP echo message and returned - in the ICMP echo reply message. The array cannot contain more than 65,500 bytes. - - A PingOptions object used to control fragmentation and Time-to-Live values for the ICMP echo message packet. - A task that represents the asynchronous operation. - - - The core implementation of SendTaskAsync. - The Ping. - A user-defined object stored in the resulting Task. - - A delegate that initiates the asynchronous send. - The provided TaskCompletionSource must be passed as the user-supplied state to the actual Ping.SendAsync method. - - - - - Sends an e-mail message asynchronously. - The client. - A String that contains the address information of the message sender. - A String that contains the address that the message is sent to. - A String that contains the subject line for the message. - A String that contains the message body. - A Task that represents the asynchronous send. - - - Sends an e-mail message asynchronously. - The client. - A MailMessage that contains the message to send. - A Task that represents the asynchronous send. - - - The core implementation of SendTaskAsync. - The client. - The user-supplied state. - - A delegate that initiates the asynchronous send. - The provided TaskCompletionSource must be passed as the user-supplied state to the actual SmtpClient.SendAsync method. - - - - - Provides asynchronous wrappers for the class. - - - Asynchronously returns the Internet Protocol (IP) addresses for the specified host. - The host name or IP address to resolve. - An array of type System.Net.IPAddress that holds the IP addresses for the host specified. - - - Asynchronously resolves an IP address to an System.Net.IPHostEntry instance. - The IP address to resolve. - An System.Net.IPHostEntry instance that contains address information about the host. - - - Asynchronously resolves an IP address to an System.Net.IPHostEntry instance. - The host name or IP address to resolve. - An System.Net.IPHostEntry instance that contains address information about the host. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.Extensions.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.Extensions.dll deleted file mode 100644 index 4d862e173..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.Extensions.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.Extensions.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.Extensions.xml deleted file mode 100644 index af646a2d0..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.Extensions.xml +++ /dev/null @@ -1,275 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions - - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - - Provides asynchronous wrappers for .NET Framework operations. - - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The cancellation token. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - The cancellation token. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads a maximum of count characters from the reader asynchronously and writes - the data to buffer, beginning at index. - - - When the operation completes, contains the specified character array with the - values between index and (index + count - 1) replaced by the characters read - from the current source. - - - The maximum number of characters to read. If the end of the stream is reached - before count of characters is read into buffer, the current method returns. - - The place in buffer at which to begin writing. - the source reader. - A Task that represents the asynchronous operation. - - - - Reads asynchronously a maximum of count characters from the current stream, and writes the - data to buffer, beginning at index. - - The source reader. - - When this method returns, this parameter contains the specified character - array with the values between index and (index + count -1) replaced by the - characters read from the current source. - - The position in buffer at which to begin writing. - The maximum number of characters to read. - A Task that represents the asynchronous operation. - - - - Reads a line of characters from the reader and returns the string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - - Reads all characters from the current position to the end of the TextReader - and returns them as one string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - Writes a string asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - Writes a line terminator asynchronously to a text stream. - The writer. - A Task representing the asynchronous write. - - - Writes a string followed by a line terminator asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char followed by a line terminator asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - - Clears all buffers for the current writer and causes any buffered data to - be written to the underlying device. - - The writer. - A Task representing the asynchronous flush. - - - Starts an asynchronous request for a web resource. - Task that represents the asynchronous request. - The stream is already in use by a previous call to . - - The source. - - - Starts an asynchronous request for a object to use to write data. - Task that represents the asynchronous request. - The property is GET and the application writes to the stream. - The stream is being used by a previous call to . - No write stream is available. - - The source. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.dll deleted file mode 100644 index 8438577c2..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.xml deleted file mode 100644 index 5c22030d7..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/net40/Microsoft.Threading.Tasks.xml +++ /dev/null @@ -1,630 +0,0 @@ - - - - Microsoft.Threading.Tasks - - - - - Provides extension methods for threading-related types. - - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time in milliseconds for the source to be canceled. - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time for the source to be canceled. - - - Gets an awaiter used to await this . - The task to await. - An awaiter instance. - - - Gets an awaiter used to await this . - Specifies the type of data returned by the task. - The task to await. - An awaiter instance. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Event handler for progress reports. - Specifies the type of data for the progress report. - The sender of the report. - The reported value. - - - - Provides an IProgress{T} that invokes callbacks for each reported progress value. - - Specifies the type of the progress report value. - - Any handler provided to the constructor or event handlers registered with - the event are invoked through a - instance captured - when the instance is constructed. If there is no current SynchronizationContext - at the time of construction, the callbacks will be invoked on the ThreadPool. - - - - The synchronization context captured upon construction. This will never be null. - - - The handler specified to the constructor. This may be null. - - - A cached delegate used to post invocation to the synchronization context. - - - Initializes the . - - - Initializes the with the specified callback. - - A handler to invoke for each reported progress value. This handler will be invoked - in addition to any delegates registered with the event. - - The is null (Nothing in Visual Basic). - - - Reports a progress change. - The value of the updated progress. - - - Reports a progress change. - The value of the updated progress. - - - Invokes the action and event callbacks. - The progress value. - - - Raised for each reported progress value. - - Handlers registered with this event will be invoked on the - captured when the instance was constructed. - - - - Holds static values for . - This avoids one static instance per type T. - - - A default synchronization context that targets the ThreadPool. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The to await. - - true to attempt to marshal the continuation back to the original context captured - when BeginAwait is called; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The underlying awaitable on whose logic this awaitable relies. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The default value to use for continueOnCapturedContext. - - - Error message for GetAwaiter. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - - Fast checks for the end of an await operation to determine whether more needs to be done - prior to completing the await. - - The awaited task. - - - Handles validations on tasks that aren't successfully completed. - The awaited task. - - - Throws an exception to handle a task that completed in a state other than RanToCompletion. - - - Schedules the continuation onto the associated with this . - The awaited task. - The action to invoke when the await operation completes. - Whether to capture and marshal back to the current context. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Invokes the delegate in a try/catch that will propagate the exception asynchronously on the ThreadPool. - - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Whether the current thread is appropriate for inlining the await continuation. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable context for switching into a target environment. - This type is intended for compiler use only. - - - Gets an awaiter for this . - An awaiter for this awaitable. - This method is intended for compiler user rather than use directly in code. - - - Provides an awaiter that switches into a target environment. - This type is intended for compiler use only. - - - A completed task. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Ends the await operation. - - - Gets whether a yield is not required. - This property is intended for compiler user rather than use directly in code. - - - Provides methods for creating and manipulating tasks. - - - Creates a task that runs the specified action. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified action. - The action to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the function. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - An already completed task. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - - A callback invoked when all of the tasks complete successfully in the RanToCompletion state. - This callback is responsible for storing the results into the TaskCompletionSource. - - A Task that represents the completion of all of the provided tasks. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates an already completed from the specified result. - The result from which to create the completed task. - The completed task. - - - Creates an awaitable that asynchronously yields back to the current context when awaited. - - A context that, when awaited, will asynchronously transition back into the current context. - If SynchronizationContext.Current is non-null, that is treated as the current context. - Otherwise, TaskScheduler.Current is treated as the current context. - - - - Adds the target exception to the list, initializing the list if it's null. - The list to which to add the exception and initialize if the list is null. - The exception to add, and unwrap if it's an aggregate. - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net40+sl4+win8+wp71+wpa81/Microsoft.Threading.Tasks.Extensions.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net40+sl4+win8+wp71+wpa81/Microsoft.Threading.Tasks.Extensions.dll deleted file mode 100644 index 4d862e173..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net40+sl4+win8+wp71+wpa81/Microsoft.Threading.Tasks.Extensions.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net40+sl4+win8+wp71+wpa81/Microsoft.Threading.Tasks.Extensions.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net40+sl4+win8+wp71+wpa81/Microsoft.Threading.Tasks.Extensions.xml deleted file mode 100644 index af646a2d0..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net40+sl4+win8+wp71+wpa81/Microsoft.Threading.Tasks.Extensions.xml +++ /dev/null @@ -1,275 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions - - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - - Provides asynchronous wrappers for .NET Framework operations. - - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The cancellation token. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - The cancellation token. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads a maximum of count characters from the reader asynchronously and writes - the data to buffer, beginning at index. - - - When the operation completes, contains the specified character array with the - values between index and (index + count - 1) replaced by the characters read - from the current source. - - - The maximum number of characters to read. If the end of the stream is reached - before count of characters is read into buffer, the current method returns. - - The place in buffer at which to begin writing. - the source reader. - A Task that represents the asynchronous operation. - - - - Reads asynchronously a maximum of count characters from the current stream, and writes the - data to buffer, beginning at index. - - The source reader. - - When this method returns, this parameter contains the specified character - array with the values between index and (index + count -1) replaced by the - characters read from the current source. - - The position in buffer at which to begin writing. - The maximum number of characters to read. - A Task that represents the asynchronous operation. - - - - Reads a line of characters from the reader and returns the string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - - Reads all characters from the current position to the end of the TextReader - and returns them as one string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - Writes a string asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - Writes a line terminator asynchronously to a text stream. - The writer. - A Task representing the asynchronous write. - - - Writes a string followed by a line terminator asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char followed by a line terminator asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - - Clears all buffers for the current writer and causes any buffered data to - be written to the underlying device. - - The writer. - A Task representing the asynchronous flush. - - - Starts an asynchronous request for a web resource. - Task that represents the asynchronous request. - The stream is already in use by a previous call to . - - The source. - - - Starts an asynchronous request for a object to use to write data. - Task that represents the asynchronous request. - The property is GET and the application writes to the stream. - The stream is being used by a previous call to . - No write stream is available. - - The source. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net40+sl4+win8+wp71+wpa81/Microsoft.Threading.Tasks.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net40+sl4+win8+wp71+wpa81/Microsoft.Threading.Tasks.dll deleted file mode 100644 index 8438577c2..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net40+sl4+win8+wp71+wpa81/Microsoft.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net40+sl4+win8+wp71+wpa81/Microsoft.Threading.Tasks.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net40+sl4+win8+wp71+wpa81/Microsoft.Threading.Tasks.xml deleted file mode 100644 index 5c22030d7..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net40+sl4+win8+wp71+wpa81/Microsoft.Threading.Tasks.xml +++ /dev/null @@ -1,630 +0,0 @@ - - - - Microsoft.Threading.Tasks - - - - - Provides extension methods for threading-related types. - - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time in milliseconds for the source to be canceled. - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time for the source to be canceled. - - - Gets an awaiter used to await this . - The task to await. - An awaiter instance. - - - Gets an awaiter used to await this . - Specifies the type of data returned by the task. - The task to await. - An awaiter instance. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Event handler for progress reports. - Specifies the type of data for the progress report. - The sender of the report. - The reported value. - - - - Provides an IProgress{T} that invokes callbacks for each reported progress value. - - Specifies the type of the progress report value. - - Any handler provided to the constructor or event handlers registered with - the event are invoked through a - instance captured - when the instance is constructed. If there is no current SynchronizationContext - at the time of construction, the callbacks will be invoked on the ThreadPool. - - - - The synchronization context captured upon construction. This will never be null. - - - The handler specified to the constructor. This may be null. - - - A cached delegate used to post invocation to the synchronization context. - - - Initializes the . - - - Initializes the with the specified callback. - - A handler to invoke for each reported progress value. This handler will be invoked - in addition to any delegates registered with the event. - - The is null (Nothing in Visual Basic). - - - Reports a progress change. - The value of the updated progress. - - - Reports a progress change. - The value of the updated progress. - - - Invokes the action and event callbacks. - The progress value. - - - Raised for each reported progress value. - - Handlers registered with this event will be invoked on the - captured when the instance was constructed. - - - - Holds static values for . - This avoids one static instance per type T. - - - A default synchronization context that targets the ThreadPool. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The to await. - - true to attempt to marshal the continuation back to the original context captured - when BeginAwait is called; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The underlying awaitable on whose logic this awaitable relies. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The default value to use for continueOnCapturedContext. - - - Error message for GetAwaiter. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - - Fast checks for the end of an await operation to determine whether more needs to be done - prior to completing the await. - - The awaited task. - - - Handles validations on tasks that aren't successfully completed. - The awaited task. - - - Throws an exception to handle a task that completed in a state other than RanToCompletion. - - - Schedules the continuation onto the associated with this . - The awaited task. - The action to invoke when the await operation completes. - Whether to capture and marshal back to the current context. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Invokes the delegate in a try/catch that will propagate the exception asynchronously on the ThreadPool. - - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Whether the current thread is appropriate for inlining the await continuation. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable context for switching into a target environment. - This type is intended for compiler use only. - - - Gets an awaiter for this . - An awaiter for this awaitable. - This method is intended for compiler user rather than use directly in code. - - - Provides an awaiter that switches into a target environment. - This type is intended for compiler use only. - - - A completed task. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Ends the await operation. - - - Gets whether a yield is not required. - This property is intended for compiler user rather than use directly in code. - - - Provides methods for creating and manipulating tasks. - - - Creates a task that runs the specified action. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified action. - The action to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the function. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - An already completed task. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - - A callback invoked when all of the tasks complete successfully in the RanToCompletion state. - This callback is responsible for storing the results into the TaskCompletionSource. - - A Task that represents the completion of all of the provided tasks. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates an already completed from the specified result. - The result from which to create the completed task. - The completed task. - - - Creates an awaitable that asynchronously yields back to the current context when awaited. - - A context that, when awaited, will asynchronously transition back into the current context. - If SynchronizationContext.Current is non-null, that is treated as the current context. - Otherwise, TaskScheduler.Current is treated as the current context. - - - - Adds the target exception to the list, initializing the list if it's null. - The list to which to add the exception and initialize if the list is null. - The exception to add, and unwrap if it's an aggregate. - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wp8+wpa81/Microsoft.Threading.Tasks.Extensions.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wp8+wpa81/Microsoft.Threading.Tasks.Extensions.dll deleted file mode 100644 index 4d862e173..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wp8+wpa81/Microsoft.Threading.Tasks.Extensions.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wp8+wpa81/Microsoft.Threading.Tasks.Extensions.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wp8+wpa81/Microsoft.Threading.Tasks.Extensions.xml deleted file mode 100644 index af646a2d0..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wp8+wpa81/Microsoft.Threading.Tasks.Extensions.xml +++ /dev/null @@ -1,275 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions - - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - - Provides asynchronous wrappers for .NET Framework operations. - - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The cancellation token. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - The cancellation token. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads a maximum of count characters from the reader asynchronously and writes - the data to buffer, beginning at index. - - - When the operation completes, contains the specified character array with the - values between index and (index + count - 1) replaced by the characters read - from the current source. - - - The maximum number of characters to read. If the end of the stream is reached - before count of characters is read into buffer, the current method returns. - - The place in buffer at which to begin writing. - the source reader. - A Task that represents the asynchronous operation. - - - - Reads asynchronously a maximum of count characters from the current stream, and writes the - data to buffer, beginning at index. - - The source reader. - - When this method returns, this parameter contains the specified character - array with the values between index and (index + count -1) replaced by the - characters read from the current source. - - The position in buffer at which to begin writing. - The maximum number of characters to read. - A Task that represents the asynchronous operation. - - - - Reads a line of characters from the reader and returns the string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - - Reads all characters from the current position to the end of the TextReader - and returns them as one string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - Writes a string asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - Writes a line terminator asynchronously to a text stream. - The writer. - A Task representing the asynchronous write. - - - Writes a string followed by a line terminator asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char followed by a line terminator asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - - Clears all buffers for the current writer and causes any buffered data to - be written to the underlying device. - - The writer. - A Task representing the asynchronous flush. - - - Starts an asynchronous request for a web resource. - Task that represents the asynchronous request. - The stream is already in use by a previous call to . - - The source. - - - Starts an asynchronous request for a object to use to write data. - Task that represents the asynchronous request. - The property is GET and the application writes to the stream. - The stream is being used by a previous call to . - No write stream is available. - - The source. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wp8+wpa81/Microsoft.Threading.Tasks.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wp8+wpa81/Microsoft.Threading.Tasks.dll deleted file mode 100644 index 8438577c2..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wp8+wpa81/Microsoft.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wp8+wpa81/Microsoft.Threading.Tasks.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wp8+wpa81/Microsoft.Threading.Tasks.xml deleted file mode 100644 index 5c22030d7..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wp8+wpa81/Microsoft.Threading.Tasks.xml +++ /dev/null @@ -1,630 +0,0 @@ - - - - Microsoft.Threading.Tasks - - - - - Provides extension methods for threading-related types. - - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time in milliseconds for the source to be canceled. - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time for the source to be canceled. - - - Gets an awaiter used to await this . - The task to await. - An awaiter instance. - - - Gets an awaiter used to await this . - Specifies the type of data returned by the task. - The task to await. - An awaiter instance. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Event handler for progress reports. - Specifies the type of data for the progress report. - The sender of the report. - The reported value. - - - - Provides an IProgress{T} that invokes callbacks for each reported progress value. - - Specifies the type of the progress report value. - - Any handler provided to the constructor or event handlers registered with - the event are invoked through a - instance captured - when the instance is constructed. If there is no current SynchronizationContext - at the time of construction, the callbacks will be invoked on the ThreadPool. - - - - The synchronization context captured upon construction. This will never be null. - - - The handler specified to the constructor. This may be null. - - - A cached delegate used to post invocation to the synchronization context. - - - Initializes the . - - - Initializes the with the specified callback. - - A handler to invoke for each reported progress value. This handler will be invoked - in addition to any delegates registered with the event. - - The is null (Nothing in Visual Basic). - - - Reports a progress change. - The value of the updated progress. - - - Reports a progress change. - The value of the updated progress. - - - Invokes the action and event callbacks. - The progress value. - - - Raised for each reported progress value. - - Handlers registered with this event will be invoked on the - captured when the instance was constructed. - - - - Holds static values for . - This avoids one static instance per type T. - - - A default synchronization context that targets the ThreadPool. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The to await. - - true to attempt to marshal the continuation back to the original context captured - when BeginAwait is called; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The underlying awaitable on whose logic this awaitable relies. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The default value to use for continueOnCapturedContext. - - - Error message for GetAwaiter. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - - Fast checks for the end of an await operation to determine whether more needs to be done - prior to completing the await. - - The awaited task. - - - Handles validations on tasks that aren't successfully completed. - The awaited task. - - - Throws an exception to handle a task that completed in a state other than RanToCompletion. - - - Schedules the continuation onto the associated with this . - The awaited task. - The action to invoke when the await operation completes. - Whether to capture and marshal back to the current context. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Invokes the delegate in a try/catch that will propagate the exception asynchronously on the ThreadPool. - - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Whether the current thread is appropriate for inlining the await continuation. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable context for switching into a target environment. - This type is intended for compiler use only. - - - Gets an awaiter for this . - An awaiter for this awaitable. - This method is intended for compiler user rather than use directly in code. - - - Provides an awaiter that switches into a target environment. - This type is intended for compiler use only. - - - A completed task. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Ends the await operation. - - - Gets whether a yield is not required. - This property is intended for compiler user rather than use directly in code. - - - Provides methods for creating and manipulating tasks. - - - Creates a task that runs the specified action. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified action. - The action to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the function. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - An already completed task. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - - A callback invoked when all of the tasks complete successfully in the RanToCompletion state. - This callback is responsible for storing the results into the TaskCompletionSource. - - A Task that represents the completion of all of the provided tasks. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates an already completed from the specified result. - The result from which to create the completed task. - The completed task. - - - Creates an awaitable that asynchronously yields back to the current context when awaited. - - A context that, when awaited, will asynchronously transition back into the current context. - If SynchronizationContext.Current is non-null, that is treated as the current context. - Otherwise, TaskScheduler.Current is treated as the current context. - - - - Adds the target exception to the list, initializing the list if it's null. - The list to which to add the exception and initialize if the list is null. - The exception to add, and unwrap if it's an aggregate. - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wpa81/Microsoft.Threading.Tasks.Extensions.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wpa81/Microsoft.Threading.Tasks.Extensions.dll deleted file mode 100644 index 4d862e173..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wpa81/Microsoft.Threading.Tasks.Extensions.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wpa81/Microsoft.Threading.Tasks.Extensions.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wpa81/Microsoft.Threading.Tasks.Extensions.xml deleted file mode 100644 index af646a2d0..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wpa81/Microsoft.Threading.Tasks.Extensions.xml +++ /dev/null @@ -1,275 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions - - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - - Provides asynchronous wrappers for .NET Framework operations. - - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The cancellation token. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - The cancellation token. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads a maximum of count characters from the reader asynchronously and writes - the data to buffer, beginning at index. - - - When the operation completes, contains the specified character array with the - values between index and (index + count - 1) replaced by the characters read - from the current source. - - - The maximum number of characters to read. If the end of the stream is reached - before count of characters is read into buffer, the current method returns. - - The place in buffer at which to begin writing. - the source reader. - A Task that represents the asynchronous operation. - - - - Reads asynchronously a maximum of count characters from the current stream, and writes the - data to buffer, beginning at index. - - The source reader. - - When this method returns, this parameter contains the specified character - array with the values between index and (index + count -1) replaced by the - characters read from the current source. - - The position in buffer at which to begin writing. - The maximum number of characters to read. - A Task that represents the asynchronous operation. - - - - Reads a line of characters from the reader and returns the string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - - Reads all characters from the current position to the end of the TextReader - and returns them as one string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - Writes a string asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - Writes a line terminator asynchronously to a text stream. - The writer. - A Task representing the asynchronous write. - - - Writes a string followed by a line terminator asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char followed by a line terminator asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - - Clears all buffers for the current writer and causes any buffered data to - be written to the underlying device. - - The writer. - A Task representing the asynchronous flush. - - - Starts an asynchronous request for a web resource. - Task that represents the asynchronous request. - The stream is already in use by a previous call to . - - The source. - - - Starts an asynchronous request for a object to use to write data. - Task that represents the asynchronous request. - The property is GET and the application writes to the stream. - The stream is being used by a previous call to . - No write stream is available. - - The source. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wpa81/Microsoft.Threading.Tasks.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wpa81/Microsoft.Threading.Tasks.dll deleted file mode 100644 index 8438577c2..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wpa81/Microsoft.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wpa81/Microsoft.Threading.Tasks.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wpa81/Microsoft.Threading.Tasks.xml deleted file mode 100644 index 5c22030d7..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/portable-net45+win8+wpa81/Microsoft.Threading.Tasks.xml +++ /dev/null @@ -1,630 +0,0 @@ - - - - Microsoft.Threading.Tasks - - - - - Provides extension methods for threading-related types. - - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time in milliseconds for the source to be canceled. - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time for the source to be canceled. - - - Gets an awaiter used to await this . - The task to await. - An awaiter instance. - - - Gets an awaiter used to await this . - Specifies the type of data returned by the task. - The task to await. - An awaiter instance. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Event handler for progress reports. - Specifies the type of data for the progress report. - The sender of the report. - The reported value. - - - - Provides an IProgress{T} that invokes callbacks for each reported progress value. - - Specifies the type of the progress report value. - - Any handler provided to the constructor or event handlers registered with - the event are invoked through a - instance captured - when the instance is constructed. If there is no current SynchronizationContext - at the time of construction, the callbacks will be invoked on the ThreadPool. - - - - The synchronization context captured upon construction. This will never be null. - - - The handler specified to the constructor. This may be null. - - - A cached delegate used to post invocation to the synchronization context. - - - Initializes the . - - - Initializes the with the specified callback. - - A handler to invoke for each reported progress value. This handler will be invoked - in addition to any delegates registered with the event. - - The is null (Nothing in Visual Basic). - - - Reports a progress change. - The value of the updated progress. - - - Reports a progress change. - The value of the updated progress. - - - Invokes the action and event callbacks. - The progress value. - - - Raised for each reported progress value. - - Handlers registered with this event will be invoked on the - captured when the instance was constructed. - - - - Holds static values for . - This avoids one static instance per type T. - - - A default synchronization context that targets the ThreadPool. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The to await. - - true to attempt to marshal the continuation back to the original context captured - when BeginAwait is called; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The underlying awaitable on whose logic this awaitable relies. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The default value to use for continueOnCapturedContext. - - - Error message for GetAwaiter. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - - Fast checks for the end of an await operation to determine whether more needs to be done - prior to completing the await. - - The awaited task. - - - Handles validations on tasks that aren't successfully completed. - The awaited task. - - - Throws an exception to handle a task that completed in a state other than RanToCompletion. - - - Schedules the continuation onto the associated with this . - The awaited task. - The action to invoke when the await operation completes. - Whether to capture and marshal back to the current context. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Invokes the delegate in a try/catch that will propagate the exception asynchronously on the ThreadPool. - - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Whether the current thread is appropriate for inlining the await continuation. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable context for switching into a target environment. - This type is intended for compiler use only. - - - Gets an awaiter for this . - An awaiter for this awaitable. - This method is intended for compiler user rather than use directly in code. - - - Provides an awaiter that switches into a target environment. - This type is intended for compiler use only. - - - A completed task. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Ends the await operation. - - - Gets whether a yield is not required. - This property is intended for compiler user rather than use directly in code. - - - Provides methods for creating and manipulating tasks. - - - Creates a task that runs the specified action. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified action. - The action to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the function. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - An already completed task. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - - A callback invoked when all of the tasks complete successfully in the RanToCompletion state. - This callback is responsible for storing the results into the TaskCompletionSource. - - A Task that represents the completion of all of the provided tasks. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates an already completed from the specified result. - The result from which to create the completed task. - The completed task. - - - Creates an awaitable that asynchronously yields back to the current context when awaited. - - A context that, when awaited, will asynchronously transition back into the current context. - If SynchronizationContext.Current is non-null, that is treated as the current context. - Otherwise, TaskScheduler.Current is treated as the current context. - - - - Adds the target exception to the list, initializing the list if it's null. - The list to which to add the exception and initialize if the list is null. - The exception to add, and unwrap if it's an aggregate. - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.Extensions.Phone.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.Extensions.Phone.dll deleted file mode 100644 index b9812870f..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.Extensions.Phone.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.Extensions.Phone.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.Extensions.Phone.xml deleted file mode 100644 index 515d7031d..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.Extensions.Phone.xml +++ /dev/null @@ -1,141 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions.Phone - - - - - Provides asynchronous wrappers for .NET Framework operations. - - - Provides asynchronous wrappers for .NET Framework operations. - - - - Downloads the resource with the specified URI as a string, asynchronously. - The WebClient. - The URI from which to download data. - A Task that contains the downloaded string. - - - Downloads the resource with the specified URI as a string, asynchronously. - The WebClient. - The URI from which to download data. - A Task that contains the downloaded string. - - - Opens a readable stream for the data downloaded from a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a readable stream for the data downloaded from a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - The HTTP method that should be used to open the stream. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - The HTTP method that should be used to open the stream. - A Task that contains the opened stream. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The HTTP method that should be used to upload the data. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The HTTP method that should be used to upload the data. - The data to upload. - A Task containing the data in the response from the upload. - - - Converts a path to a Uri using the WebClient's logic. - Based on WebClient's private GetUri method. - - - Converts a path to a Uri using the WebClient's logic. - Based on WebClient's private GetUri method. - - - Asynchronously invokes an Action on the Dispatcher. - The Dispatcher. - The action to invoke. - A Task that represents the execution of the action. - - - Asynchronously invokes an Action on the Dispatcher. - The Dispatcher. - The function to invoke. - A Task that represents the execution of the function. - - - Used with Task(of void) - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.Extensions.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.Extensions.dll deleted file mode 100644 index 4d862e173..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.Extensions.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.Extensions.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.Extensions.xml deleted file mode 100644 index af646a2d0..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.Extensions.xml +++ /dev/null @@ -1,275 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions - - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - - Provides asynchronous wrappers for .NET Framework operations. - - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The cancellation token. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - The cancellation token. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads a maximum of count characters from the reader asynchronously and writes - the data to buffer, beginning at index. - - - When the operation completes, contains the specified character array with the - values between index and (index + count - 1) replaced by the characters read - from the current source. - - - The maximum number of characters to read. If the end of the stream is reached - before count of characters is read into buffer, the current method returns. - - The place in buffer at which to begin writing. - the source reader. - A Task that represents the asynchronous operation. - - - - Reads asynchronously a maximum of count characters from the current stream, and writes the - data to buffer, beginning at index. - - The source reader. - - When this method returns, this parameter contains the specified character - array with the values between index and (index + count -1) replaced by the - characters read from the current source. - - The position in buffer at which to begin writing. - The maximum number of characters to read. - A Task that represents the asynchronous operation. - - - - Reads a line of characters from the reader and returns the string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - - Reads all characters from the current position to the end of the TextReader - and returns them as one string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - Writes a string asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - Writes a line terminator asynchronously to a text stream. - The writer. - A Task representing the asynchronous write. - - - Writes a string followed by a line terminator asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char followed by a line terminator asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - - Clears all buffers for the current writer and causes any buffered data to - be written to the underlying device. - - The writer. - A Task representing the asynchronous flush. - - - Starts an asynchronous request for a web resource. - Task that represents the asynchronous request. - The stream is already in use by a previous call to . - - The source. - - - Starts an asynchronous request for a object to use to write data. - Task that represents the asynchronous request. - The property is GET and the application writes to the stream. - The stream is being used by a previous call to . - No write stream is available. - - The source. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.dll deleted file mode 100644 index 8438577c2..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.xml deleted file mode 100644 index 5c22030d7..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4-windowsphone71/Microsoft.Threading.Tasks.xml +++ /dev/null @@ -1,630 +0,0 @@ - - - - Microsoft.Threading.Tasks - - - - - Provides extension methods for threading-related types. - - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time in milliseconds for the source to be canceled. - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time for the source to be canceled. - - - Gets an awaiter used to await this . - The task to await. - An awaiter instance. - - - Gets an awaiter used to await this . - Specifies the type of data returned by the task. - The task to await. - An awaiter instance. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Event handler for progress reports. - Specifies the type of data for the progress report. - The sender of the report. - The reported value. - - - - Provides an IProgress{T} that invokes callbacks for each reported progress value. - - Specifies the type of the progress report value. - - Any handler provided to the constructor or event handlers registered with - the event are invoked through a - instance captured - when the instance is constructed. If there is no current SynchronizationContext - at the time of construction, the callbacks will be invoked on the ThreadPool. - - - - The synchronization context captured upon construction. This will never be null. - - - The handler specified to the constructor. This may be null. - - - A cached delegate used to post invocation to the synchronization context. - - - Initializes the . - - - Initializes the with the specified callback. - - A handler to invoke for each reported progress value. This handler will be invoked - in addition to any delegates registered with the event. - - The is null (Nothing in Visual Basic). - - - Reports a progress change. - The value of the updated progress. - - - Reports a progress change. - The value of the updated progress. - - - Invokes the action and event callbacks. - The progress value. - - - Raised for each reported progress value. - - Handlers registered with this event will be invoked on the - captured when the instance was constructed. - - - - Holds static values for . - This avoids one static instance per type T. - - - A default synchronization context that targets the ThreadPool. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The to await. - - true to attempt to marshal the continuation back to the original context captured - when BeginAwait is called; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The underlying awaitable on whose logic this awaitable relies. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The default value to use for continueOnCapturedContext. - - - Error message for GetAwaiter. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - - Fast checks for the end of an await operation to determine whether more needs to be done - prior to completing the await. - - The awaited task. - - - Handles validations on tasks that aren't successfully completed. - The awaited task. - - - Throws an exception to handle a task that completed in a state other than RanToCompletion. - - - Schedules the continuation onto the associated with this . - The awaited task. - The action to invoke when the await operation completes. - Whether to capture and marshal back to the current context. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Invokes the delegate in a try/catch that will propagate the exception asynchronously on the ThreadPool. - - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Whether the current thread is appropriate for inlining the await continuation. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable context for switching into a target environment. - This type is intended for compiler use only. - - - Gets an awaiter for this . - An awaiter for this awaitable. - This method is intended for compiler user rather than use directly in code. - - - Provides an awaiter that switches into a target environment. - This type is intended for compiler use only. - - - A completed task. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Ends the await operation. - - - Gets whether a yield is not required. - This property is intended for compiler user rather than use directly in code. - - - Provides methods for creating and manipulating tasks. - - - Creates a task that runs the specified action. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified action. - The action to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the function. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - An already completed task. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - - A callback invoked when all of the tasks complete successfully in the RanToCompletion state. - This callback is responsible for storing the results into the TaskCompletionSource. - - A Task that represents the completion of all of the provided tasks. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates an already completed from the specified result. - The result from which to create the completed task. - The completed task. - - - Creates an awaitable that asynchronously yields back to the current context when awaited. - - A context that, when awaited, will asynchronously transition back into the current context. - If SynchronizationContext.Current is non-null, that is treated as the current context. - Otherwise, TaskScheduler.Current is treated as the current context. - - - - Adds the target exception to the list, initializing the list if it's null. - The list to which to add the exception and initialize if the list is null. - The exception to add, and unwrap if it's an aggregate. - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.Extensions.Silverlight.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.Extensions.Silverlight.dll deleted file mode 100644 index 689120e11..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.Extensions.Silverlight.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.Extensions.Silverlight.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.Extensions.Silverlight.xml deleted file mode 100644 index 950e092f0..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.Extensions.Silverlight.xml +++ /dev/null @@ -1,141 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions.Silverlight - - - - - Provides asynchronous wrappers for .NET Framework operations. - - - Provides asynchronous wrappers for .NET Framework operations. - - - - Downloads the resource with the specified URI as a string, asynchronously. - The WebClient. - The URI from which to download data. - A Task that contains the downloaded string. - - - Downloads the resource with the specified URI as a string, asynchronously. - The WebClient. - The URI from which to download data. - A Task that contains the downloaded string. - - - Opens a readable stream for the data downloaded from a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a readable stream for the data downloaded from a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - The HTTP method that should be used to open the stream. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - The HTTP method that should be used to open the stream. - A Task that contains the opened stream. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The HTTP method that should be used to upload the data. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The HTTP method that should be used to upload the data. - The data to upload. - A Task containing the data in the response from the upload. - - - Converts a path to a Uri using the WebClient's logic. - Based on WebClient's private GetUri method. - - - Converts a path to a Uri using the WebClient's logic. - Based on WebClient's private GetUri method. - - - Asynchronously invokes an Action on the Dispatcher. - The Dispatcher. - The action to invoke. - A Task that represents the execution of the action. - - - Asynchronously invokes an Action on the Dispatcher. - The Dispatcher. - The function to invoke. - A Task that represents the execution of the function. - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - Used with Task(of void) - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.Extensions.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.Extensions.dll deleted file mode 100644 index 4d862e173..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.Extensions.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.Extensions.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.Extensions.xml deleted file mode 100644 index af646a2d0..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.Extensions.xml +++ /dev/null @@ -1,275 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions - - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - - Provides asynchronous wrappers for .NET Framework operations. - - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The cancellation token. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - The cancellation token. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads a maximum of count characters from the reader asynchronously and writes - the data to buffer, beginning at index. - - - When the operation completes, contains the specified character array with the - values between index and (index + count - 1) replaced by the characters read - from the current source. - - - The maximum number of characters to read. If the end of the stream is reached - before count of characters is read into buffer, the current method returns. - - The place in buffer at which to begin writing. - the source reader. - A Task that represents the asynchronous operation. - - - - Reads asynchronously a maximum of count characters from the current stream, and writes the - data to buffer, beginning at index. - - The source reader. - - When this method returns, this parameter contains the specified character - array with the values between index and (index + count -1) replaced by the - characters read from the current source. - - The position in buffer at which to begin writing. - The maximum number of characters to read. - A Task that represents the asynchronous operation. - - - - Reads a line of characters from the reader and returns the string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - - Reads all characters from the current position to the end of the TextReader - and returns them as one string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - Writes a string asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - Writes a line terminator asynchronously to a text stream. - The writer. - A Task representing the asynchronous write. - - - Writes a string followed by a line terminator asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char followed by a line terminator asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - - Clears all buffers for the current writer and causes any buffered data to - be written to the underlying device. - - The writer. - A Task representing the asynchronous flush. - - - Starts an asynchronous request for a web resource. - Task that represents the asynchronous request. - The stream is already in use by a previous call to . - - The source. - - - Starts an asynchronous request for a object to use to write data. - Task that represents the asynchronous request. - The property is GET and the application writes to the stream. - The stream is being used by a previous call to . - No write stream is available. - - The source. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.dll deleted file mode 100644 index 8438577c2..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.xml deleted file mode 100644 index 5c22030d7..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/sl4/Microsoft.Threading.Tasks.xml +++ /dev/null @@ -1,630 +0,0 @@ - - - - Microsoft.Threading.Tasks - - - - - Provides extension methods for threading-related types. - - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time in milliseconds for the source to be canceled. - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time for the source to be canceled. - - - Gets an awaiter used to await this . - The task to await. - An awaiter instance. - - - Gets an awaiter used to await this . - Specifies the type of data returned by the task. - The task to await. - An awaiter instance. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Event handler for progress reports. - Specifies the type of data for the progress report. - The sender of the report. - The reported value. - - - - Provides an IProgress{T} that invokes callbacks for each reported progress value. - - Specifies the type of the progress report value. - - Any handler provided to the constructor or event handlers registered with - the event are invoked through a - instance captured - when the instance is constructed. If there is no current SynchronizationContext - at the time of construction, the callbacks will be invoked on the ThreadPool. - - - - The synchronization context captured upon construction. This will never be null. - - - The handler specified to the constructor. This may be null. - - - A cached delegate used to post invocation to the synchronization context. - - - Initializes the . - - - Initializes the with the specified callback. - - A handler to invoke for each reported progress value. This handler will be invoked - in addition to any delegates registered with the event. - - The is null (Nothing in Visual Basic). - - - Reports a progress change. - The value of the updated progress. - - - Reports a progress change. - The value of the updated progress. - - - Invokes the action and event callbacks. - The progress value. - - - Raised for each reported progress value. - - Handlers registered with this event will be invoked on the - captured when the instance was constructed. - - - - Holds static values for . - This avoids one static instance per type T. - - - A default synchronization context that targets the ThreadPool. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The to await. - - true to attempt to marshal the continuation back to the original context captured - when BeginAwait is called; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The underlying awaitable on whose logic this awaitable relies. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The default value to use for continueOnCapturedContext. - - - Error message for GetAwaiter. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - - Fast checks for the end of an await operation to determine whether more needs to be done - prior to completing the await. - - The awaited task. - - - Handles validations on tasks that aren't successfully completed. - The awaited task. - - - Throws an exception to handle a task that completed in a state other than RanToCompletion. - - - Schedules the continuation onto the associated with this . - The awaited task. - The action to invoke when the await operation completes. - Whether to capture and marshal back to the current context. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Invokes the delegate in a try/catch that will propagate the exception asynchronously on the ThreadPool. - - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Whether the current thread is appropriate for inlining the await continuation. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable context for switching into a target environment. - This type is intended for compiler use only. - - - Gets an awaiter for this . - An awaiter for this awaitable. - This method is intended for compiler user rather than use directly in code. - - - Provides an awaiter that switches into a target environment. - This type is intended for compiler use only. - - - A completed task. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Ends the await operation. - - - Gets whether a yield is not required. - This property is intended for compiler user rather than use directly in code. - - - Provides methods for creating and manipulating tasks. - - - Creates a task that runs the specified action. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified action. - The action to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the function. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - An already completed task. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - - A callback invoked when all of the tasks complete successfully in the RanToCompletion state. - This callback is responsible for storing the results into the TaskCompletionSource. - - A Task that represents the completion of all of the provided tasks. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates an already completed from the specified result. - The result from which to create the completed task. - The completed task. - - - Creates an awaitable that asynchronously yields back to the current context when awaited. - - A context that, when awaited, will asynchronously transition back into the current context. - If SynchronizationContext.Current is non-null, that is treated as the current context. - Otherwise, TaskScheduler.Current is treated as the current context. - - - - Adds the target exception to the list, initializing the list if it's null. - The list to which to add the exception and initialize if the list is null. - The exception to add, and unwrap if it's an aggregate. - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/win8/Microsoft.Threading.Tasks.Extensions.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/win8/Microsoft.Threading.Tasks.Extensions.dll deleted file mode 100644 index 4d862e173..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/win8/Microsoft.Threading.Tasks.Extensions.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/win8/Microsoft.Threading.Tasks.Extensions.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/win8/Microsoft.Threading.Tasks.Extensions.xml deleted file mode 100644 index af646a2d0..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/win8/Microsoft.Threading.Tasks.Extensions.xml +++ /dev/null @@ -1,275 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions - - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - - Provides asynchronous wrappers for .NET Framework operations. - - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The cancellation token. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - The cancellation token. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads a maximum of count characters from the reader asynchronously and writes - the data to buffer, beginning at index. - - - When the operation completes, contains the specified character array with the - values between index and (index + count - 1) replaced by the characters read - from the current source. - - - The maximum number of characters to read. If the end of the stream is reached - before count of characters is read into buffer, the current method returns. - - The place in buffer at which to begin writing. - the source reader. - A Task that represents the asynchronous operation. - - - - Reads asynchronously a maximum of count characters from the current stream, and writes the - data to buffer, beginning at index. - - The source reader. - - When this method returns, this parameter contains the specified character - array with the values between index and (index + count -1) replaced by the - characters read from the current source. - - The position in buffer at which to begin writing. - The maximum number of characters to read. - A Task that represents the asynchronous operation. - - - - Reads a line of characters from the reader and returns the string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - - Reads all characters from the current position to the end of the TextReader - and returns them as one string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - Writes a string asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - Writes a line terminator asynchronously to a text stream. - The writer. - A Task representing the asynchronous write. - - - Writes a string followed by a line terminator asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char followed by a line terminator asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - - Clears all buffers for the current writer and causes any buffered data to - be written to the underlying device. - - The writer. - A Task representing the asynchronous flush. - - - Starts an asynchronous request for a web resource. - Task that represents the asynchronous request. - The stream is already in use by a previous call to . - - The source. - - - Starts an asynchronous request for a object to use to write data. - Task that represents the asynchronous request. - The property is GET and the application writes to the stream. - The stream is being used by a previous call to . - No write stream is available. - - The source. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/win8/Microsoft.Threading.Tasks.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/win8/Microsoft.Threading.Tasks.dll deleted file mode 100644 index 8438577c2..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/win8/Microsoft.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/win8/Microsoft.Threading.Tasks.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/win8/Microsoft.Threading.Tasks.xml deleted file mode 100644 index 5c22030d7..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/win8/Microsoft.Threading.Tasks.xml +++ /dev/null @@ -1,630 +0,0 @@ - - - - Microsoft.Threading.Tasks - - - - - Provides extension methods for threading-related types. - - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time in milliseconds for the source to be canceled. - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time for the source to be canceled. - - - Gets an awaiter used to await this . - The task to await. - An awaiter instance. - - - Gets an awaiter used to await this . - Specifies the type of data returned by the task. - The task to await. - An awaiter instance. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Event handler for progress reports. - Specifies the type of data for the progress report. - The sender of the report. - The reported value. - - - - Provides an IProgress{T} that invokes callbacks for each reported progress value. - - Specifies the type of the progress report value. - - Any handler provided to the constructor or event handlers registered with - the event are invoked through a - instance captured - when the instance is constructed. If there is no current SynchronizationContext - at the time of construction, the callbacks will be invoked on the ThreadPool. - - - - The synchronization context captured upon construction. This will never be null. - - - The handler specified to the constructor. This may be null. - - - A cached delegate used to post invocation to the synchronization context. - - - Initializes the . - - - Initializes the with the specified callback. - - A handler to invoke for each reported progress value. This handler will be invoked - in addition to any delegates registered with the event. - - The is null (Nothing in Visual Basic). - - - Reports a progress change. - The value of the updated progress. - - - Reports a progress change. - The value of the updated progress. - - - Invokes the action and event callbacks. - The progress value. - - - Raised for each reported progress value. - - Handlers registered with this event will be invoked on the - captured when the instance was constructed. - - - - Holds static values for . - This avoids one static instance per type T. - - - A default synchronization context that targets the ThreadPool. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The to await. - - true to attempt to marshal the continuation back to the original context captured - when BeginAwait is called; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The underlying awaitable on whose logic this awaitable relies. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The default value to use for continueOnCapturedContext. - - - Error message for GetAwaiter. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - - Fast checks for the end of an await operation to determine whether more needs to be done - prior to completing the await. - - The awaited task. - - - Handles validations on tasks that aren't successfully completed. - The awaited task. - - - Throws an exception to handle a task that completed in a state other than RanToCompletion. - - - Schedules the continuation onto the associated with this . - The awaited task. - The action to invoke when the await operation completes. - Whether to capture and marshal back to the current context. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Invokes the delegate in a try/catch that will propagate the exception asynchronously on the ThreadPool. - - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Whether the current thread is appropriate for inlining the await continuation. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable context for switching into a target environment. - This type is intended for compiler use only. - - - Gets an awaiter for this . - An awaiter for this awaitable. - This method is intended for compiler user rather than use directly in code. - - - Provides an awaiter that switches into a target environment. - This type is intended for compiler use only. - - - A completed task. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Ends the await operation. - - - Gets whether a yield is not required. - This property is intended for compiler user rather than use directly in code. - - - Provides methods for creating and manipulating tasks. - - - Creates a task that runs the specified action. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified action. - The action to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the function. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - An already completed task. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - - A callback invoked when all of the tasks complete successfully in the RanToCompletion state. - This callback is responsible for storing the results into the TaskCompletionSource. - - A Task that represents the completion of all of the provided tasks. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates an already completed from the specified result. - The result from which to create the completed task. - The completed task. - - - Creates an awaitable that asynchronously yields back to the current context when awaited. - - A context that, when awaited, will asynchronously transition back into the current context. - If SynchronizationContext.Current is non-null, that is treated as the current context. - Otherwise, TaskScheduler.Current is treated as the current context. - - - - Adds the target exception to the list, initializing the list if it's null. - The list to which to add the exception and initialize if the list is null. - The exception to add, and unwrap if it's an aggregate. - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.Extensions.Phone.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.Extensions.Phone.dll deleted file mode 100644 index b9812870f..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.Extensions.Phone.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.Extensions.Phone.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.Extensions.Phone.xml deleted file mode 100644 index 515d7031d..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.Extensions.Phone.xml +++ /dev/null @@ -1,141 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions.Phone - - - - - Provides asynchronous wrappers for .NET Framework operations. - - - Provides asynchronous wrappers for .NET Framework operations. - - - - Downloads the resource with the specified URI as a string, asynchronously. - The WebClient. - The URI from which to download data. - A Task that contains the downloaded string. - - - Downloads the resource with the specified URI as a string, asynchronously. - The WebClient. - The URI from which to download data. - A Task that contains the downloaded string. - - - Opens a readable stream for the data downloaded from a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a readable stream for the data downloaded from a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - The HTTP method that should be used to open the stream. - A Task that contains the opened stream. - - - Opens a writeable stream for uploading data to a resource, asynchronously. - The WebClient. - The URI for which the stream should be opened. - The HTTP method that should be used to open the stream. - A Task that contains the opened stream. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The HTTP method that should be used to upload the data. - The data to upload. - A Task containing the data in the response from the upload. - - - Uploads data in a string to the specified resource, asynchronously. - The WebClient. - The URI to which the data should be uploaded. - The HTTP method that should be used to upload the data. - The data to upload. - A Task containing the data in the response from the upload. - - - Converts a path to a Uri using the WebClient's logic. - Based on WebClient's private GetUri method. - - - Converts a path to a Uri using the WebClient's logic. - Based on WebClient's private GetUri method. - - - Asynchronously invokes an Action on the Dispatcher. - The Dispatcher. - The action to invoke. - A Task that represents the execution of the action. - - - Asynchronously invokes an Action on the Dispatcher. - The Dispatcher. - The function to invoke. - A Task that represents the execution of the function. - - - Used with Task(of void) - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.Extensions.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.Extensions.dll deleted file mode 100644 index 4d862e173..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.Extensions.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.Extensions.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.Extensions.xml deleted file mode 100644 index af646a2d0..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.Extensions.xml +++ /dev/null @@ -1,275 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions - - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - - Provides asynchronous wrappers for .NET Framework operations. - - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The cancellation token. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - The cancellation token. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads a maximum of count characters from the reader asynchronously and writes - the data to buffer, beginning at index. - - - When the operation completes, contains the specified character array with the - values between index and (index + count - 1) replaced by the characters read - from the current source. - - - The maximum number of characters to read. If the end of the stream is reached - before count of characters is read into buffer, the current method returns. - - The place in buffer at which to begin writing. - the source reader. - A Task that represents the asynchronous operation. - - - - Reads asynchronously a maximum of count characters from the current stream, and writes the - data to buffer, beginning at index. - - The source reader. - - When this method returns, this parameter contains the specified character - array with the values between index and (index + count -1) replaced by the - characters read from the current source. - - The position in buffer at which to begin writing. - The maximum number of characters to read. - A Task that represents the asynchronous operation. - - - - Reads a line of characters from the reader and returns the string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - - Reads all characters from the current position to the end of the TextReader - and returns them as one string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - Writes a string asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - Writes a line terminator asynchronously to a text stream. - The writer. - A Task representing the asynchronous write. - - - Writes a string followed by a line terminator asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char followed by a line terminator asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - - Clears all buffers for the current writer and causes any buffered data to - be written to the underlying device. - - The writer. - A Task representing the asynchronous flush. - - - Starts an asynchronous request for a web resource. - Task that represents the asynchronous request. - The stream is already in use by a previous call to . - - The source. - - - Starts an asynchronous request for a object to use to write data. - Task that represents the asynchronous request. - The property is GET and the application writes to the stream. - The stream is being used by a previous call to . - No write stream is available. - - The source. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.dll deleted file mode 100644 index 8438577c2..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.xml deleted file mode 100644 index 5c22030d7..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/wp8/Microsoft.Threading.Tasks.xml +++ /dev/null @@ -1,630 +0,0 @@ - - - - Microsoft.Threading.Tasks - - - - - Provides extension methods for threading-related types. - - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time in milliseconds for the source to be canceled. - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time for the source to be canceled. - - - Gets an awaiter used to await this . - The task to await. - An awaiter instance. - - - Gets an awaiter used to await this . - Specifies the type of data returned by the task. - The task to await. - An awaiter instance. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Event handler for progress reports. - Specifies the type of data for the progress report. - The sender of the report. - The reported value. - - - - Provides an IProgress{T} that invokes callbacks for each reported progress value. - - Specifies the type of the progress report value. - - Any handler provided to the constructor or event handlers registered with - the event are invoked through a - instance captured - when the instance is constructed. If there is no current SynchronizationContext - at the time of construction, the callbacks will be invoked on the ThreadPool. - - - - The synchronization context captured upon construction. This will never be null. - - - The handler specified to the constructor. This may be null. - - - A cached delegate used to post invocation to the synchronization context. - - - Initializes the . - - - Initializes the with the specified callback. - - A handler to invoke for each reported progress value. This handler will be invoked - in addition to any delegates registered with the event. - - The is null (Nothing in Visual Basic). - - - Reports a progress change. - The value of the updated progress. - - - Reports a progress change. - The value of the updated progress. - - - Invokes the action and event callbacks. - The progress value. - - - Raised for each reported progress value. - - Handlers registered with this event will be invoked on the - captured when the instance was constructed. - - - - Holds static values for . - This avoids one static instance per type T. - - - A default synchronization context that targets the ThreadPool. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The to await. - - true to attempt to marshal the continuation back to the original context captured - when BeginAwait is called; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The underlying awaitable on whose logic this awaitable relies. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The default value to use for continueOnCapturedContext. - - - Error message for GetAwaiter. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - - Fast checks for the end of an await operation to determine whether more needs to be done - prior to completing the await. - - The awaited task. - - - Handles validations on tasks that aren't successfully completed. - The awaited task. - - - Throws an exception to handle a task that completed in a state other than RanToCompletion. - - - Schedules the continuation onto the associated with this . - The awaited task. - The action to invoke when the await operation completes. - Whether to capture and marshal back to the current context. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Invokes the delegate in a try/catch that will propagate the exception asynchronously on the ThreadPool. - - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Whether the current thread is appropriate for inlining the await continuation. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable context for switching into a target environment. - This type is intended for compiler use only. - - - Gets an awaiter for this . - An awaiter for this awaitable. - This method is intended for compiler user rather than use directly in code. - - - Provides an awaiter that switches into a target environment. - This type is intended for compiler use only. - - - A completed task. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Ends the await operation. - - - Gets whether a yield is not required. - This property is intended for compiler user rather than use directly in code. - - - Provides methods for creating and manipulating tasks. - - - Creates a task that runs the specified action. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified action. - The action to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the function. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - An already completed task. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - - A callback invoked when all of the tasks complete successfully in the RanToCompletion state. - This callback is responsible for storing the results into the TaskCompletionSource. - - A Task that represents the completion of all of the provided tasks. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates an already completed from the specified result. - The result from which to create the completed task. - The completed task. - - - Creates an awaitable that asynchronously yields back to the current context when awaited. - - A context that, when awaited, will asynchronously transition back into the current context. - If SynchronizationContext.Current is non-null, that is treated as the current context. - Otherwise, TaskScheduler.Current is treated as the current context. - - - - Adds the target exception to the list, initializing the list if it's null. - The list to which to add the exception and initialize if the list is null. - The exception to add, and unwrap if it's an aggregate. - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/wpa81/Microsoft.Threading.Tasks.Extensions.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/wpa81/Microsoft.Threading.Tasks.Extensions.dll deleted file mode 100644 index 4d862e173..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/wpa81/Microsoft.Threading.Tasks.Extensions.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/wpa81/Microsoft.Threading.Tasks.Extensions.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/wpa81/Microsoft.Threading.Tasks.Extensions.xml deleted file mode 100644 index af646a2d0..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/wpa81/Microsoft.Threading.Tasks.Extensions.xml +++ /dev/null @@ -1,275 +0,0 @@ - - - - Microsoft.Threading.Tasks.Extensions - - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - - Provides asynchronous wrappers for .NET Framework operations. - - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - - A Task that represents the asynchronous read. - The source. - The buffer to read data into. - The byte offset in at which to begin reading. - The maximum number of bytes to read. - The cancellation token. - The array length minus is less than . - is null. - or is negative. - An asynchronous read was attempted past the end of the file. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Writes asynchronously a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - - A Task that represents the asynchronous write. - The source. - The buffer containing data to write to the current stream. - The zero-based byte offset in at which to begin copying bytes to the current stream. - The maximum number of bytes to write. - The cancellation token. - length minus is less than . - is null. - or is negative. - The stream does not support writing. - The stream is closed. - An I/O error occurred. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Flushes asynchronously the current stream. - - A Task that represents the asynchronous flush. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads all the bytes from the current stream and writes them to the destination stream. - - The source stream. - The stream that will contain the contents of the current stream. - The size of the buffer. This value must be greater than zero. The default size is 4096. - The cancellation token to use to cancel the asynchronous operation. - A Task that represents the asynchronous operation. - - - - Reads a maximum of count characters from the reader asynchronously and writes - the data to buffer, beginning at index. - - - When the operation completes, contains the specified character array with the - values between index and (index + count - 1) replaced by the characters read - from the current source. - - - The maximum number of characters to read. If the end of the stream is reached - before count of characters is read into buffer, the current method returns. - - The place in buffer at which to begin writing. - the source reader. - A Task that represents the asynchronous operation. - - - - Reads asynchronously a maximum of count characters from the current stream, and writes the - data to buffer, beginning at index. - - The source reader. - - When this method returns, this parameter contains the specified character - array with the values between index and (index + count -1) replaced by the - characters read from the current source. - - The position in buffer at which to begin writing. - The maximum number of characters to read. - A Task that represents the asynchronous operation. - - - - Reads a line of characters from the reader and returns the string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - - Reads all characters from the current position to the end of the TextReader - and returns them as one string asynchronously. - - the source reader. - A Task that represents the asynchronous operation. - - - Writes a string asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - Writes a line terminator asynchronously to a text stream. - The writer. - A Task representing the asynchronous write. - - - Writes a string followed by a line terminator asynchronously to a text stream. - The writer. - The string to write. - A Task representing the asynchronous write. - - - Writes a char followed by a line terminator asynchronously to a text stream. - The writer. - The char to write. - A Task representing the asynchronous write. - - - Writes a char array followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - A Task representing the asynchronous write. - - - Writes a subarray of characters followed by a line terminator asynchronously to a text stream. - The writer. - The buffer to write. - Starting index in the buffer. - The number of characters to write. - A Task representing the asynchronous write. - - - - Clears all buffers for the current writer and causes any buffered data to - be written to the underlying device. - - The writer. - A Task representing the asynchronous flush. - - - Starts an asynchronous request for a web resource. - Task that represents the asynchronous request. - The stream is already in use by a previous call to . - - The source. - - - Starts an asynchronous request for a object to use to write data. - Task that represents the asynchronous request. - The property is GET and the application writes to the stream. - The stream is being used by a previous call to . - No write stream is available. - - The source. - - - diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/wpa81/Microsoft.Threading.Tasks.dll b/packages/Microsoft.Bcl.Async.1.0.168/lib/wpa81/Microsoft.Threading.Tasks.dll deleted file mode 100644 index 8438577c2..000000000 Binary files a/packages/Microsoft.Bcl.Async.1.0.168/lib/wpa81/Microsoft.Threading.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Async.1.0.168/lib/wpa81/Microsoft.Threading.Tasks.xml b/packages/Microsoft.Bcl.Async.1.0.168/lib/wpa81/Microsoft.Threading.Tasks.xml deleted file mode 100644 index 5c22030d7..000000000 --- a/packages/Microsoft.Bcl.Async.1.0.168/lib/wpa81/Microsoft.Threading.Tasks.xml +++ /dev/null @@ -1,630 +0,0 @@ - - - - Microsoft.Threading.Tasks - - - - - Provides extension methods for threading-related types. - - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time in milliseconds for the source to be canceled. - - - Cancels the after the specified duration. - The CancellationTokenSource. - The due time for the source to be canceled. - - - Gets an awaiter used to await this . - The task to await. - An awaiter instance. - - - Gets an awaiter used to await this . - Specifies the type of data returned by the task. - The task to await. - An awaiter instance. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Creates and configures an awaitable object for awaiting the specified task. - The task to be awaited. - - true to automatic marshag back to the original call site's current SynchronizationContext - or TaskScheduler; otherwise, false. - - The instance to be awaited. - - - Event handler for progress reports. - Specifies the type of data for the progress report. - The sender of the report. - The reported value. - - - - Provides an IProgress{T} that invokes callbacks for each reported progress value. - - Specifies the type of the progress report value. - - Any handler provided to the constructor or event handlers registered with - the event are invoked through a - instance captured - when the instance is constructed. If there is no current SynchronizationContext - at the time of construction, the callbacks will be invoked on the ThreadPool. - - - - The synchronization context captured upon construction. This will never be null. - - - The handler specified to the constructor. This may be null. - - - A cached delegate used to post invocation to the synchronization context. - - - Initializes the . - - - Initializes the with the specified callback. - - A handler to invoke for each reported progress value. This handler will be invoked - in addition to any delegates registered with the event. - - The is null (Nothing in Visual Basic). - - - Reports a progress change. - The value of the updated progress. - - - Reports a progress change. - The value of the updated progress. - - - Invokes the action and event callbacks. - The progress value. - - - Raised for each reported progress value. - - Handlers registered with this event will be invoked on the - captured when the instance was constructed. - - - - Holds static values for . - This avoids one static instance per type T. - - - A default synchronization context that targets the ThreadPool. - - - Throws the exception on the ThreadPool. - The exception to propagate. - The target context on which to propagate the exception. Null to use the ThreadPool. - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The to await. - - true to attempt to marshal the continuation back to the original context captured - when BeginAwait is called; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable object that allows for configured awaits on . - This type is intended for compiler use only. - - - The underlying awaitable on whose logic this awaitable relies. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Gets an awaiter for this awaitable. - The awaiter. - - - Provides an awaiter for a . - This type is intended for compiler use only. - - - The task being awaited. - - - Whether to attempt marshaling back to the original context. - - - Initializes the . - The awaitable . - - true to attempt to marshal the continuation back to the original context captured; otherwise, false. - - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The default value to use for continueOnCapturedContext. - - - Error message for GetAwaiter. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - - Fast checks for the end of an await operation to determine whether more needs to be done - prior to completing the await. - - The awaited task. - - - Handles validations on tasks that aren't successfully completed. - The awaited task. - - - Throws an exception to handle a task that completed in a state other than RanToCompletion. - - - Schedules the continuation onto the associated with this . - The awaited task. - The action to invoke when the await operation completes. - Whether to capture and marshal back to the current context. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Invokes the delegate in a try/catch that will propagate the exception asynchronously on the ThreadPool. - - - - Copies the exception's stack trace so its stack trace isn't overwritten. - The exception to prepare. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Whether the current thread is appropriate for inlining the await continuation. - - - Provides an awaiter for awaiting a . - This type is intended for compiler use only. - - - The task being awaited. - - - Initializes the . - The to be awaited. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Schedules the continuation onto the associated with this . - The action to invoke when the await operation completes. - The argument is null (Nothing in Visual Basic). - The awaiter was not properly initialized. - This method is intended for compiler user rather than use directly in code. - - - Ends the await on the completed . - The result of the completed . - The awaiter was not properly initialized. - The task was not yet completed. - The task was canceled. - The task completed in a Faulted state. - - - Gets whether the task being awaited is completed. - This property is intended for compiler user rather than use directly in code. - The awaiter was not properly initialized. - - - Provides an awaitable context for switching into a target environment. - This type is intended for compiler use only. - - - Gets an awaiter for this . - An awaiter for this awaitable. - This method is intended for compiler user rather than use directly in code. - - - Provides an awaiter that switches into a target environment. - This type is intended for compiler use only. - - - A completed task. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Posts the back to the current context. - The action to invoke asynchronously. - The awaiter was not properly initialized. - - - Ends the await operation. - - - Gets whether a yield is not required. - This property is intended for compiler user rather than use directly in code. - - - Provides methods for creating and manipulating tasks. - - - Creates a task that runs the specified action. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified action. - The action to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute. - The CancellationToken to use to request cancellation of this task. - A task that represents the completion of the function. - The argument is null. - - - Creates a task that runs the specified function. - The function to execute asynchronously. - A task that represents the completion of the action. - The argument is null. - - - Creates a task that runs the specified function. - The action to execute. - The CancellationToken to use to cancel the task. - A task that represents the completion of the action. - The argument is null. - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - Starts a Task that will complete after the specified due time. - The delay in milliseconds before the returned task completes. - A CancellationToken that may be used to cancel the task before the due time occurs. - The timed Task. - - The argument must be non-negative or -1 and less than or equal to Int32.MaxValue. - - - - An already completed task. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - A Task that represents the completion of all of the provided tasks. - - If any of the provided Tasks faults, the returned Task will also fault, and its Exception will contain information - about all of the faulted tasks. If no Tasks fault but one or more Tasks is canceled, the returned - Task will also be canceled. - - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete only when all of the provided collection of Tasks has completed. - The Tasks to monitor for completion. - - A callback invoked when all of the tasks complete successfully in the RanToCompletion state. - This callback is responsible for storing the results into the TaskCompletionSource. - - A Task that represents the completion of all of the provided tasks. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates a Task that will complete when any of the tasks in the provided collection completes. - The Tasks to be monitored. - - A Task that represents the completion of any of the provided Tasks. The completed Task is this Task's result. - - Any Tasks that fault will need to have their exceptions observed elsewhere. - The argument is null. - The argument contains a null reference. - - - Creates an already completed from the specified result. - The result from which to create the completed task. - The completed task. - - - Creates an awaitable that asynchronously yields back to the current context when awaited. - - A context that, when awaited, will asynchronously transition back into the current context. - If SynchronizationContext.Current is non-null, that is treated as the current context. - Otherwise, TaskScheduler.Current is treated as the current context. - - - - Adds the target exception to the list, initializing the list if it's null. - The list to which to add the exception and initialize if the list is null. - The exception to add, and unwrap if it's an aggregate. - - - Returns a canceled task. - The cancellation token. - The canceled task. - - - Returns a canceled task. - Specifies the type of the result. - The cancellation token. - The canceled task. - - - - Completes the Task if the user state matches the TaskCompletionSource. - - Specifies the type of data returned by the Task. - The TaskCompletionSource. - The completion event arguments. - Whether we require the tcs to match the e.UserState. - A function that gets the result with which to complete the task. - An action used to unregister work when the operaiton completes. - - - diff --git a/packages/Microsoft.Bcl.Build.1.0.21/License-Stable.rtf b/packages/Microsoft.Bcl.Build.1.0.21/License-Stable.rtf deleted file mode 100644 index 3aec6b654..000000000 --- a/packages/Microsoft.Bcl.Build.1.0.21/License-Stable.rtf +++ /dev/null @@ -1,118 +0,0 @@ -{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fswiss\fprq2\fcharset0 Tahoma;}{\f1\froman\fprq2\fcharset0 Times New Roman;}{\f2\fswiss\fprq2\fcharset0 Calibri;}{\f3\fnil\fcharset0 Calibri;}{\f4\fnil\fcharset2 Symbol;}} -{\colortbl ;\red31\green73\blue125;\red0\green0\blue255;} -{\*\listtable -{\list\listhybrid -{\listlevel\levelnfc0\leveljc0\levelstartat1{\leveltext\'02\'00.;}{\levelnumbers\'01;}\jclisttab\tx360} -{\listlevel\levelnfc4\leveljc0\levelstartat1{\leveltext\'02\'01.;}{\levelnumbers\'01;}\jclisttab\tx363} -{\listlevel\levelnfc2\leveljc0\levelstartat1{\leveltext\'02\'02.;}{\levelnumbers\'01;}\jclisttab\tx720}\listid1 } -{\list\listhybrid -{\listlevel\levelnfc0\leveljc0\levelstartat1{\leveltext\'02\'00.;}{\levelnumbers\'01;}\jclisttab\tx363} -{\listlevel\levelnfc4\leveljc0\levelstartat1{\leveltext\'02\'01.;}{\levelnumbers\'01;}\jclisttab\tx363}\listid2 }} -{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}} -{\stylesheet{ Normal;}{\s1 heading 1;}{\s2 heading 2;}{\s3 heading 3;}} -{\*\generator Riched20 6.2.9200}\viewkind4\uc1 -\pard\nowidctlpar\sb120\sa120\b\f0\fs24 MICROSOFT SOFTWARE LICENSE TERMS\par - -\pard\brdrb\brdrs\brdrw10\brsp20 \nowidctlpar\sb120\sa120 MICROSOFT .NET LIBRARY \par - -\pard\nowidctlpar\sb120\sa120\fs19 These license terms are an agreement between Microsoft Corporation (or based on where you live, one of its affiliates) and you. Please read them. They apply to the software named above, which includes the media on which you received it, if any. The terms also apply to any Microsoft\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent363{\pntxtb\'B7}}\nowidctlpar\fi-363\li720\sb120\sa120\b0 updates,\par -{\pntext\f4\'B7\tab}supplements,\par -{\pntext\f4\'B7\tab}Internet-based services, and\par -{\pntext\f4\'B7\tab}support services\par - -\pard\nowidctlpar\sb120\sa120\b for this software, unless other terms accompany those items. If so, those terms apply.\par -BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS. IF YOU DO NOT ACCEPT THEM, DO NOT USE THE SOFTWARE.\par - -\pard\brdrt\brdrs\brdrw10\brsp20 \nowidctlpar\sb120\sa120 IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE PERPETUAL RIGHTS BELOW.\par - -\pard -{\listtext\f0 1.\tab}\jclisttab\tx360\ls1\nowidctlpar\s1\fi-357\li357\sb120\sa120 INSTALLATION AND USE RIGHTS. \par - -\pard -{\listtext\f0 a.\tab}\jclisttab\tx363\ls1\ilvl1\nowidctlpar\s2\fi-363\li720\sb120\sa120 Installation and Use.\b0\fs20 You may install and use any number of copies of the software to design, develop and test your programs.\par -{\listtext\f0 b.\tab}\b\fs19 Third Party Programs.\b0\fs20 The software may include third party programs that Microsoft, not the third party, licenses to you under this agreement. Notices, if any, for the third party program are included for your information only.\b\fs19\par - -\pard -{\listtext\f0 2.\tab}\jclisttab\tx360\ls1\nowidctlpar\s1\fi-357\li357\sb120\sa120\fs20 ADDITIONAL LICENSING REQUIREMENTS AND/OR USE RIGHTS.\par - -\pard -{\listtext\f0 a.\tab}\jclisttab\tx363\ls1\ilvl1\nowidctlpar\s2\fi-363\li720\sb120\sa120 DISTRIBUTABLE CODE.\~ \b0 The software is comprised of Distributable Code. \f1\ldblquote\f0 Distributable Code\f1\rdblquote\f0 is code that you are permitted to distribute in programs you develop if you comply with the terms below.\b\par - -\pard -{\listtext\f0 i.\tab}\jclisttab\tx720\ls1\ilvl2\nowidctlpar\s3\fi-357\li1077\sb120\sa120\tx1077 Right to Use and Distribute. \par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\fi-357\li1434\sb120\sa120\b0 You may copy and distribute the object code form of the software.\par -{\pntext\f4\'B7\tab}Third Party Distribution. You may permit distributors of your programs to copy and distribute the Distributable Code as part of those programs.\par - -\pard\nowidctlpar\s3\fi-357\li1077\sb120\sa120\tx1077\b ii.\tab Distribution Requirements.\b0 \b For any Distributable Code you distribute, you must\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\fi-357\li1434\sb120\sa120\b0 add significant primary functionality to it in your programs;\par -{\pntext\f4\'B7\tab}require distributors and external end users to agree to terms that protect it at least as much as this agreement;\par -{\pntext\f4\'B7\tab}display your valid copyright notice on your programs; and\par -{\pntext\f4\'B7\tab}indemnify, defend, and hold harmless Microsoft from any claims, including attorneys\rquote fees, related to the distribution or use of your programs.\par - -\pard\nowidctlpar\s3\fi-357\li1077\sb120\sa120\tx1077\b iii.\tab Distribution Restrictions.\b0 \b You may not\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\fi-357\li1434\sb120\sa120\b0 alter any copyright, trademark or patent notice in the Distributable Code;\par -{\pntext\f4\'B7\tab}use Microsoft\rquote s trademarks in your programs\rquote names or in a way that suggests your programs come from or are endorsed by Microsoft;\par -{\pntext\f4\'B7\tab}include Distributable Code in malicious, deceptive or unlawful programs; or\par -{\pntext\f4\'B7\tab}modify or distribute the source code of any Distributable Code so that any part of it becomes subject to an Excluded License. An Excluded License is one that requires, as a condition of use, modification or distribution, that\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\fi-358\li1792\sb120\sa120 the code be disclosed or distributed in source code form; or\cf1\f2\par -{\pntext\f4\'B7\tab}\cf0\f0 others have the right to modify it.\cf1\f2\par - -\pard\nowidctlpar\s1\fi-357\li357\sb120\sa120\cf0\b\f0 3.\tab\fs19 SCOPE OF LICENSE. \b0 The software is licensed, not sold. This agreement only gives you some rights to use the software. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the software only as expressly permitted in this agreement. In doing so, you must comply with any technical limitations in the software that only allow you to use it in certain ways. You may not\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent363{\pntxtb\'B7}}\nowidctlpar\fi-363\li720\sb120\sa120 work around any technical limitations in the software;\par -{\pntext\f4\'B7\tab}reverse engineer, decompile or disassemble the software, except and only to the extent that applicable law expressly permits, despite this limitation;\par -{\pntext\f4\'B7\tab}publish the software for others to copy;\par -{\pntext\f4\'B7\tab}rent, lease or lend the software;\par -{\pntext\f4\'B7\tab}transfer the software or this agreement to any third party; or\par -{\pntext\f4\'B7\tab}use the software for commercial software hosting services.\par - -\pard\nowidctlpar\s1\fi-357\li357\sb120\sa120\b\fs20 4.\tab\fs19 BACKUP COPY. \b0 You may make one backup copy of the software. You may use it only to reinstall the software.\par -\b\fs20 5.\tab\fs19 DOCUMENTATION. \b0 Any person that has valid access to your computer or internal network may copy and use the documentation for your internal, reference purposes.\par -\b\fs20 6.\tab\fs19 EXPORT RESTRICTIONS. \b0 The software is subject to United States export laws and regulations. You must comply with all domestic and international export laws and regulations that apply to the software. These laws include restrictions on destinations, end users and end use. For additional information, see {\cf2\ul\fs20{\field{\*\fldinst{HYPERLINK www.microsoft.com/exporting }}{\fldrslt{www.microsoft.com/exporting}}}}\f0\fs19 .\cf2\ul\fs20\par -\cf0\ulnone\b 7.\tab\fs19 SUPPORT SERVICES. \b0 Because this software is \ldblquote as is,\rdblquote we may not provide support services for it.\par -\b\fs20 8.\tab\fs19 ENTIRE AGREEMENT. \b0 This agreement, and the terms for supplements, updates, Internet-based services and support services that you use, are the entire agreement for the software and support services.\par -\b\fs20 9.\tab\fs19 APPLICABLE LAW.\par - -\pard -{\listtext\f0 a.\tab}\jclisttab\tx363\ls2\ilvl1\nowidctlpar\s2\fi-363\li720\sb120\sa120 United States. \b0 If you acquired the software in the United States, Washington state law governs the interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws principles. The laws of the state where you live govern all other claims, including claims under state consumer protection laws, unfair competition laws, and in tort.\par -{\listtext\f0 b.\tab}\b Outside the United States. If you acquired the software in any other country, the laws of that country apply.\par - -\pard\nowidctlpar\s1\fi-357\li357\sb120\sa120\fs20 10.\tab\fs19 LEGAL EFFECT. \b0 This agreement describes certain legal rights. You may have other rights under the laws of your country. You may also have rights with respect to the party from whom you acquired the software. This agreement does not change your rights under the laws of your country if the laws of your country do not permit it to do so.\par -\b\fs20 11.\tab\fs19 DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED \ldblquote AS-IS.\rdblquote YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES OR CONDITIONS. YOU MAY HAVE ADDITIONAL CONSUMER RIGHTS OR STATUTORY GUARANTEES UNDER YOUR LOCAL LAWS WHICH THIS AGREEMENT CANNOT CHANGE. TO THE EXTENT PERMITTED UNDER YOUR LOCAL LAWS, MICROSOFT EXCLUDES THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.\par - -\pard\nowidctlpar\li357\sb120\sa120 FOR AUSTRALIA \endash YOU HAVE STATUTORY GUARANTEES UNDER THE AUSTRALIAN CONSUMER LAW AND NOTHING IN THESE TERMS IS INTENDED TO AFFECT THOSE RIGHTS.\par - -\pard\nowidctlpar\s1\fi-357\li357\sb120\sa120\fs20 12.\tab\fs19 LIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES. YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES.\par - -\pard\nowidctlpar\li357\sb120\sa120\b0 This limitation applies to\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent363{\pntxtb\'B7}}\nowidctlpar\fi-363\li720\sb120\sa120 anything related to the software, services, content (including code) on third party Internet sites, or third party programs; and\par -{\pntext\f4\'B7\tab}claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the extent permitted by applicable law.\par - -\pard\nowidctlpar\sb120\sa120 It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential or other damages.\par -\lang9 Please note: As this software is distributed in Quebec, Canada, some of the clauses in this agreement are provided below in French.\par -Remarque : Ce logiciel \'e9tant distribu\'e9 au Qu\'e9bec, Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en fran\'e7ais.\par - -\pard\nowidctlpar\s1\sb120\sa120\b\lang1033 EXON\'c9RATION DE GARANTIE. \b0 Le logiciel vis\'e9 par une licence est offert \'ab tel quel \'bb. Toute utilisation de ce logiciel est \'e0 votre seule risque et p\'e9ril. Microsoft n\rquote accorde aucune autre garantie expresse. Vous pouvez b\'e9n\'e9ficier de droits additionnels en vertu du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualit\'e9 marchande, d\rquote ad\'e9quation \'e0 un usage particulier et d\rquote absence de contrefa\'e7on sont exclues.\par -\b LIMITATION DES DOMMAGES-INT\'c9R\'caTS ET EXCLUSION DE RESPONSABILIT\'c9 POUR LES DOMMAGES. \b0 Vous pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de dommages directs uniquement \'e0 hauteur de 5,00 $ US. Vous ne pouvez pr\'e9tendre \'e0 aucune indemnisation pour les autres dommages, y compris les dommages sp\'e9ciaux, indirects ou accessoires et pertes de b\'e9n\'e9fices.\par - -\pard\nowidctlpar\sb120\sa120\lang9 Cette limitation concerne :\par - -\pard{\pntext\f4\'B7\tab}{\*\pn\pnlvlblt\pnf4\pnindent360{\pntxtb\'B7}}\nowidctlpar\li720\sb120\sa120 tout ce qui est reli\'e9 au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et\par -{\pntext\f4\'B7\tab}les r\'e9clamations au titre de violation de contrat ou de garantie, ou au titre de responsabilit\'e9 stricte, de n\'e9gligence ou d\rquote une autre faute dans la limite autoris\'e9e par la loi en vigueur.\par - -\pard\nowidctlpar\sb120\sa120 Elle s\rquote applique \'e9galement, m\'eame si Microsoft connaissait ou devrait conna\'eetre l\rquote\'e9ventualit\'e9 d\rquote un tel dommage. Si votre pays n\rquote autorise pas l\rquote exclusion ou la limitation de responsabilit\'e9 pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l\rquote exclusion ci-dessus ne s\rquote appliquera pas \'e0 votre \'e9gard.\par - -\pard\nowidctlpar\s1\sb120\sa120\b\lang1033 EFFET JURIDIQUE. \b0 Le pr\'e9sent contrat d\'e9crit certains droits juridiques. Vous pourriez avoir d\rquote autres droits pr\'e9vus par les lois de votre pays. Le pr\'e9sent contrat ne modifie pas les droits que vous conf\'e8rent les lois de votre pays si celles-ci ne le permettent pas.\par - -\pard\nowidctlpar\sb120\sa120\b\fs20\lang1036\par - -\pard\sa200\sl276\slmult1\b0\f3\fs22\lang9\par -} - \ No newline at end of file diff --git a/packages/Microsoft.Bcl.Build.1.0.21/build/Microsoft.Bcl.Build.Tasks.dll b/packages/Microsoft.Bcl.Build.1.0.21/build/Microsoft.Bcl.Build.Tasks.dll deleted file mode 100644 index 5c7187910..000000000 Binary files a/packages/Microsoft.Bcl.Build.1.0.21/build/Microsoft.Bcl.Build.Tasks.dll and /dev/null differ diff --git a/packages/Microsoft.Bcl.Build.1.0.21/build/Microsoft.Bcl.Build.targets b/packages/Microsoft.Bcl.Build.1.0.21/build/Microsoft.Bcl.Build.targets deleted file mode 100644 index 0e041595b..000000000 --- a/packages/Microsoft.Bcl.Build.1.0.21/build/Microsoft.Bcl.Build.targets +++ /dev/null @@ -1,250 +0,0 @@ - - - - true - - - - - false - - - $(ProjectConfigFileName) - - - - - - <_FullFrameworkReferenceAssemblyPaths>$(TargetFrameworkDirectory) - - - - - - <__IntermediateAppConfig>$(IntermediateOutputPath)$(MSBuildProjectFile).App.config - true - - - - false - true - - - - - true - true - - - - - - - - - <_EnsureBindingRedirectReference Include="@(Reference)" - Condition="'%(Reference.HintPath)' != '' and Exists('$([System.IO.Path]::GetDirectoryName("%(Reference.HintPath)"))\\ensureRedirect.xml')" /> - - - - - - - - - - - - - - - $(__IntermediateAppConfig) - - - - - $(TargetFileName).config - - - - - - - - - - - <_BclBuildProjectReferenceProperties>BclBuildReferencingProject=$(MSBuildProjectFullPath);BclBuildReferencingProjectConfig=$(MSBuildProjectDirectory)\packages.config - <_BclBuildProjectReferenceProperties Condition="'$(SkipValidatePackageReferences)' != ''">$(_BclBuildProjectReferenceProperties);SkipValidatePackageReferences=$(SkipValidatePackageReferences) - - - - - $(_BclBuildProjectReferenceProperties);%(ProjectReference.AdditionalProperties) - - - - - - - - true - - - - - - - - - - false - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/Moq.4.2.1502.0911/[Content_Types].xml b/packages/Moq.4.2.1502.0911/[Content_Types].xml deleted file mode 100644 index eb401f4c0..000000000 --- a/packages/Moq.4.2.1502.0911/[Content_Types].xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/Moq.4.2.1502.0911/lib/net35/Moq.dll b/packages/Moq.4.2.1502.0911/lib/net35/Moq.dll deleted file mode 100644 index 0a6d39464..000000000 Binary files a/packages/Moq.4.2.1502.0911/lib/net35/Moq.dll and /dev/null differ diff --git a/packages/Moq.4.2.1502.0911/lib/net35/Moq.xml b/packages/Moq.4.2.1502.0911/lib/net35/Moq.xml deleted file mode 100644 index 2fee68ebb..000000000 --- a/packages/Moq.4.2.1502.0911/lib/net35/Moq.xml +++ /dev/null @@ -1,6082 +0,0 @@ - - - - Moq - - - - - Implements the fluent API. - - - - - The expectation will be considered only in the former condition. - - - - - - - The expectation will be considered only in the former condition. - - - - - - - - Setups the get. - - The type of the property. - The expression. - - - - - Setups the set. - - The type of the property. - The setter expression. - - - - - Setups the set. - - The setter expression. - - - - - Handle interception - - the current invocation context - shared data for the interceptor as a whole - shared data among the strategies during a single interception - InterceptionAction.Continue if further interception has to be processed, otherwise InterceptionAction.Stop - - - - Covarient interface for Mock<T> such that casts between IMock<Employee> to IMock<Person> - are possible. Only covers the covariant members of Mock<T>. - - - - - Exposes the mocked object instance. - - - - - Behavior of the mock, according to the value set in the constructor. - - - - - Whether the base member virtual implementation will be called - for mocked classes if no setup is matched. Defaults to . - - - - - Specifies the behavior to use when returning default values for - unexpected invocations on loose mocks. - - - - - Get an eventInfo for a given event name. Search type ancestors depth first if necessary. - - Name of the event, with the set_ or get_ prefix already removed - - - - Get an eventInfo for a given event name. Search type ancestors depth first if necessary. - Searches also in non public events. - - Name of the event, with the set_ or get_ prefix already removed - - - - Given a type return all of its ancestors, both types and interfaces. - - The type to find immediate ancestors of - - - - Defines the Callback verb and overloads. - - - - - Helper interface used to hide the base - members from the fluent API to make it much cleaner - in Visual Studio intellisense. - - - - - - - - - - - - - - - - - Specifies a callback to invoke when the method is called. - - The callback method to invoke. - - The following example specifies a callback to set a boolean - value that can be used later: - - var called = false; - mock.Setup(x => x.Execute()) - .Callback(() => called = true); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The argument type of the invoked method. - The callback method to invoke. - - Invokes the given callback with the concrete invocation argument value. - - Notice how the specific string argument is retrieved by simply declaring - it as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute(It.IsAny<string>())) - .Callback((string command) => Console.WriteLine(command)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2) => Console.WriteLine(arg1 + arg2)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3) => Console.WriteLine(arg1 + arg2 + arg3)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4) => Console.WriteLine(arg1 + arg2 + arg3 + arg4)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The type of the sixteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15, string arg16) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16)); - - - - - - Defines the Callback verb and overloads for callbacks on - setups that return a value. - - Mocked type. - Type of the return value of the setup. - - - - Specifies a callback to invoke when the method is called. - - The callback method to invoke. - - The following example specifies a callback to set a boolean value that can be used later: - - var called = false; - mock.Setup(x => x.Execute()) - .Callback(() => called = true) - .Returns(true); - - Note that in the case of value-returning methods, after the Callback - call you can still specify the return value. - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the argument of the invoked method. - Callback method to invoke. - - Invokes the given callback with the concrete invocation argument value. - - Notice how the specific string argument is retrieved by simply declaring - it as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute(It.IsAny<string>())) - .Callback(command => Console.WriteLine(command)) - .Returns(true); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2) => Console.WriteLine(arg1 + arg2)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3) => Console.WriteLine(arg1 + arg2 + arg3)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4) => Console.WriteLine(arg1 + arg2 + arg3 + arg4)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The type of the sixteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16)); - - - - - - Defines the Raises verb. - - - - - Specifies the event that will be raised - when the setup is met. - - An expression that represents an event attach or detach action. - The event arguments to pass for the raised event. - - The following example shows how to raise an event when - the setup is met: - - var mock = new Mock<IContainer>(); - - mock.Setup(add => add.Add(It.IsAny<string>(), It.IsAny<object>())) - .Raises(add => add.Added += null, EventArgs.Empty); - - - - - - Specifies the event that will be raised - when the setup is matched. - - An expression that represents an event attach or detach action. - A function that will build the - to pass when raising the event. - - - - - Specifies the custom event that will be raised - when the setup is matched. - - An expression that represents an event attach or detach action. - The arguments to pass to the custom delegate (non EventHandler-compatible). - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - The type of the fourteenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - The type of the fourteenth argument received by the expected invocation. - The type of the fifteenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - The type of the fourteenth argument received by the expected invocation. - The type of the fifteenth argument received by the expected invocation. - The type of the sixteenth argument received by the expected invocation. - - - - - Defines the Returns verb. - - Mocked type. - Type of the return value from the expression. - - - - Specifies the value to return. - - The value to return, or . - - Return a true value from the method call: - - mock.Setup(x => x.Execute("ping")) - .Returns(true); - - - - - - Specifies a function that will calculate the value to return from the method. - - The function that will calculate the return value. - - Return a calculated value when the method is called: - - mock.Setup(x => x.Execute("ping")) - .Returns(() => returnValues[0]); - - The lambda expression to retrieve the return value is lazy-executed, - meaning that its value may change depending on the moment the method - is executed and the value the returnValues array has at - that moment. - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the argument of the invoked method. - The function that will calculate the return value. - - Return a calculated value which is evaluated lazily at the time of the invocation. - - The lookup list can change between invocations and the setup - will return different values accordingly. Also, notice how the specific - string argument is retrieved by simply declaring it as part of the lambda - expression: - - - mock.Setup(x => x.Execute(It.IsAny<string>())) - .Returns((string command) => returnValues[command]); - - - - - - Calls the real method of the object and returns its return value. - - The value calculated by the real method of the object. - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2) => arg1 + arg2); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3) => arg1 + arg2 + arg3); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4) => arg1 + arg2 + arg3 + arg4); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5) => arg1 + arg2 + arg3 + arg4 + arg5); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The type of the sixteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15, string arg16) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16); - - - - - - Hook used to tells Castle which methods to proxy in mocked classes. - - Here we proxy the default methods Castle suggests (everything Object's methods) - plus Object.ToString(), so we can give mocks useful default names. - - This is required to allow Moq to mock ToString on proxy *class* implementations. - - - - - Extends AllMethodsHook.ShouldInterceptMethod to also intercept Object.ToString(). - - - - - The base class used for all our interface-inheriting proxies, which overrides the default - Object.ToString() behavior, to route it via the mock by default, unless overriden by a - real implementation. - - This is required to allow Moq to mock ToString on proxy *interface* implementations. - - - This is internal to Moq and should not be generally used. - - Unfortunately it must be public, due to cross-assembly visibility issues with reflection, - see github.com/Moq/moq4/issues/98 for details. - - - - - Overrides the default ToString implementation to instead find the mock for this mock.Object, - and return MockName + '.Object' as the mocked object's ToString, to make it easy to relate - mocks and mock object instances in error messages. - - - - - Language for ReturnSequence - - - - - Returns value - - - - - Throws an exception - - - - - Throws an exception - - - - - Calls original method - - - - - The first method call or member access will be the - last segment of the expression (depth-first traversal), - which is the one we have to Setup rather than FluentMock. - And the last one is the one we have to Mock.Get rather - than FluentMock. - - - - - Base class for mocks and static helper class with methods that - apply to mocked objects, such as to - retrieve a from an object instance. - - - - - Creates an mock object of the indicated type. - - The type of the mocked object. - The mocked object created. - - - - Creates an mock object of the indicated type. - - The predicate with the specification of how the mocked object should behave. - The type of the mocked object. - The mocked object created. - - - - Initializes a new instance of the class. - - - - - Retrieves the mock object for the given object instance. - - Type of the mock to retrieve. Can be omitted as it's inferred - from the object instance passed in as the instance. - The instance of the mocked object.The mock associated with the mocked object. - The received instance - was not created by Moq. - - The following example shows how to add a new setup to an object - instance which is not the original but rather - the object associated with it: - - // Typed instance, not the mock, is retrieved from some test API. - HttpContextBase context = GetMockContext(); - - // context.Request is the typed object from the "real" API - // so in order to add a setup to it, we need to get - // the mock that "owns" it - Mock<HttpRequestBase> request = Mock.Get(context.Request); - mock.Setup(req => req.AppRelativeCurrentExecutionFilePath) - .Returns(tempUrl); - - - - - - Returns the mocked object value. - - - - - Verifies that all verifiable expectations have been met. - - This example sets up an expectation and marks it as verifiable. After - the mock is used, a Verify() call is issued on the mock - to ensure the method in the setup was invoked: - - var mock = new Mock<IWarehouse>(); - this.Setup(x => x.HasInventory(TALISKER, 50)).Verifiable().Returns(true); - ... - // other test code - ... - // Will throw if the test code has didn't call HasInventory. - this.Verify(); - - Not all verifiable expectations were met. - - - - Verifies all expectations regardless of whether they have - been flagged as verifiable. - - This example sets up an expectation without marking it as verifiable. After - the mock is used, a call is issued on the mock - to ensure that all expectations are met: - - var mock = new Mock<IWarehouse>(); - this.Setup(x => x.HasInventory(TALISKER, 50)).Returns(true); - ... - // other test code - ... - // Will throw if the test code has didn't call HasInventory, even - // that expectation was not marked as verifiable. - this.VerifyAll(); - - At least one expectation was not met. - - - - Gets the interceptor target for the given expression and root mock, - building the intermediate hierarchy of mock objects if necessary. - - - - - Raises the associated event with the given - event argument data. - - - - - Raises the associated event with the given - event argument data. - - - - - Adds an interface implementation to the mock, - allowing setups to be specified for it. - - This method can only be called before the first use - of the mock property, at which - point the runtime type has already been generated - and no more interfaces can be added to it. - - Also, must be an - interface and not a class, which must be specified - when creating the mock instead. - - - The mock type - has already been generated by accessing the property. - - The specified - is not an interface. - - The following example creates a mock for the main interface - and later adds to it to verify - it's called by the consumer code: - - var mock = new Mock<IProcessor>(); - mock.Setup(x => x.Execute("ping")); - - // add IDisposable interface - var disposable = mock.As<IDisposable>(); - disposable.Setup(d => d.Dispose()).Verifiable(); - - Type of interface to cast the mock to. - - - - - - - Behavior of the mock, according to the value set in the constructor. - - - - - Whether the base member virtual implementation will be called - for mocked classes if no setup is matched. Defaults to . - - - - - Specifies the behavior to use when returning default values for - unexpected invocations on loose mocks. - - - - - Gets the mocked object instance. - - - - - Retrieves the type of the mocked object, its generic type argument. - This is used in the auto-mocking of hierarchy access. - - - - - If this is a mock of a delegate, this property contains the method - on the autogenerated interface so that we can convert setup + verify - expressions on the delegate into expressions on the interface proxy. - - - - - Allows to check whether expression conversion to the - must be performed on the mock, without causing unnecessarily early initialization of - the mock instance, which breaks As{T}. - - - - - Specifies the class that will determine the default - value to return when invocations are made that - have no setups and need to return a default - value (for loose mocks). - - - - - Exposes the list of extra interfaces implemented by the mock. - - - - - Utility repository class to use to construct multiple - mocks when consistent verification is - desired for all of them. - - - If multiple mocks will be created during a test, passing - the desired (if different than the - or the one - passed to the repository constructor) and later verifying each - mock can become repetitive and tedious. - - This repository class helps in that scenario by providing a - simplified creation of multiple mocks with a default - (unless overriden by calling - ) and posterior verification. - - - - The following is a straightforward example on how to - create and automatically verify strict mocks using a : - - var repository = new MockRepository(MockBehavior.Strict); - - var foo = repository.Create<IFoo>(); - var bar = repository.Create<IBar>(); - - // no need to call Verifiable() on the setup - // as we'll be validating all of them anyway. - foo.Setup(f => f.Do()); - bar.Setup(b => b.Redo()); - - // exercise the mocks here - - repository.VerifyAll(); - // At this point all setups are already checked - // and an optional MockException might be thrown. - // Note also that because the mocks are strict, any invocation - // that doesn't have a matching setup will also throw a MockException. - - The following examples shows how to setup the repository - to create loose mocks and later verify only verifiable setups: - - var repository = new MockRepository(MockBehavior.Loose); - - var foo = repository.Create<IFoo>(); - var bar = repository.Create<IBar>(); - - // this setup will be verified when we verify the repository - foo.Setup(f => f.Do()).Verifiable(); - - // this setup will NOT be verified - foo.Setup(f => f.Calculate()); - - // this setup will be verified when we verify the repository - bar.Setup(b => b.Redo()).Verifiable(); - - // exercise the mocks here - // note that because the mocks are Loose, members - // called in the interfaces for which no matching - // setups exist will NOT throw exceptions, - // and will rather return default values. - - repository.Verify(); - // At this point verifiable setups are already checked - // and an optional MockException might be thrown. - - The following examples shows how to setup the repository with a - default strict behavior, overriding that default for a - specific mock: - - var repository = new MockRepository(MockBehavior.Strict); - - // this particular one we want loose - var foo = repository.Create<IFoo>(MockBehavior.Loose); - var bar = repository.Create<IBar>(); - - // specify setups - - // exercise the mocks here - - repository.Verify(); - - - - - - - Utility factory class to use to construct multiple - mocks when consistent verification is - desired for all of them. - - - If multiple mocks will be created during a test, passing - the desired (if different than the - or the one - passed to the factory constructor) and later verifying each - mock can become repetitive and tedious. - - This factory class helps in that scenario by providing a - simplified creation of multiple mocks with a default - (unless overriden by calling - ) and posterior verification. - - - - The following is a straightforward example on how to - create and automatically verify strict mocks using a : - - var factory = new MockFactory(MockBehavior.Strict); - - var foo = factory.Create<IFoo>(); - var bar = factory.Create<IBar>(); - - // no need to call Verifiable() on the setup - // as we'll be validating all of them anyway. - foo.Setup(f => f.Do()); - bar.Setup(b => b.Redo()); - - // exercise the mocks here - - factory.VerifyAll(); - // At this point all setups are already checked - // and an optional MockException might be thrown. - // Note also that because the mocks are strict, any invocation - // that doesn't have a matching setup will also throw a MockException. - - The following examples shows how to setup the factory - to create loose mocks and later verify only verifiable setups: - - var factory = new MockFactory(MockBehavior.Loose); - - var foo = factory.Create<IFoo>(); - var bar = factory.Create<IBar>(); - - // this setup will be verified when we verify the factory - foo.Setup(f => f.Do()).Verifiable(); - - // this setup will NOT be verified - foo.Setup(f => f.Calculate()); - - // this setup will be verified when we verify the factory - bar.Setup(b => b.Redo()).Verifiable(); - - // exercise the mocks here - // note that because the mocks are Loose, members - // called in the interfaces for which no matching - // setups exist will NOT throw exceptions, - // and will rather return default values. - - factory.Verify(); - // At this point verifiable setups are already checked - // and an optional MockException might be thrown. - - The following examples shows how to setup the factory with a - default strict behavior, overriding that default for a - specific mock: - - var factory = new MockFactory(MockBehavior.Strict); - - // this particular one we want loose - var foo = factory.Create<IFoo>(MockBehavior.Loose); - var bar = factory.Create<IBar>(); - - // specify setups - - // exercise the mocks here - - factory.Verify(); - - - - - - - Initializes the factory with the given - for newly created mocks from the factory. - - The behavior to use for mocks created - using the factory method if not overriden - by using the overload. - - - - Creates a new mock with the default - specified at factory construction time. - - Type to mock. - A new . - - - var factory = new MockFactory(MockBehavior.Strict); - - var foo = factory.Create<IFoo>(); - // use mock on tests - - factory.VerifyAll(); - - - - - - Creates a new mock with the default - specified at factory construction time and with the - the given constructor arguments for the class. - - - The mock will try to find the best match constructor given the - constructor arguments, and invoke that to initialize the instance. - This applies only to classes, not interfaces. - - Type to mock. - Constructor arguments for mocked classes. - A new . - - - var factory = new MockFactory(MockBehavior.Default); - - var mock = factory.Create<MyBase>("Foo", 25, true); - // use mock on tests - - factory.Verify(); - - - - - - Creates a new mock with the given . - - Type to mock. - Behavior to use for the mock, which overrides - the default behavior specified at factory construction time. - A new . - - The following example shows how to create a mock with a different - behavior to that specified as the default for the factory: - - var factory = new MockFactory(MockBehavior.Strict); - - var foo = factory.Create<IFoo>(MockBehavior.Loose); - - - - - - Creates a new mock with the given - and with the the given constructor arguments for the class. - - - The mock will try to find the best match constructor given the - constructor arguments, and invoke that to initialize the instance. - This applies only to classes, not interfaces. - - Type to mock. - Behavior to use for the mock, which overrides - the default behavior specified at factory construction time. - Constructor arguments for mocked classes. - A new . - - The following example shows how to create a mock with a different - behavior to that specified as the default for the factory, passing - constructor arguments: - - var factory = new MockFactory(MockBehavior.Default); - - var mock = factory.Create<MyBase>(MockBehavior.Strict, "Foo", 25, true); - - - - - - Implements creation of a new mock within the factory. - - Type to mock. - The behavior for the new mock. - Optional arguments for the construction of the mock. - - - - Verifies all verifiable expectations on all mocks created - by this factory. - - - One or more mocks had expectations that were not satisfied. - - - - Verifies all verifiable expectations on all mocks created - by this factory. - - - One or more mocks had expectations that were not satisfied. - - - - Invokes for each mock - in , and accumulates the resulting - that might be - thrown from the action. - - The action to execute against - each mock. - - - - Whether the base member virtual implementation will be called - for mocked classes if no setup is matched. Defaults to . - - - - - Specifies the behavior to use when returning default values for - unexpected invocations on loose mocks. - - - - - Gets the mocks that have been created by this factory and - that will get verified together. - - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The type of the mocked object to query. - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The predicate with the setup expressions. - The type of the mocked object to query. - - - - Creates an mock object of the indicated type. - - The type of the mocked object. - The mocked object created. - - - - Creates an mock object of the indicated type. - - The predicate with the setup expressions. - The type of the mocked object. - The mocked object created. - - - - Creates the mock query with the underlying queriable implementation. - - - - - Wraps the enumerator inside a queryable. - - - - - Method that is turned into the actual call from .Query{T}, to - transform the queryable query into a normal enumerable query. - This method is never used directly by consumers. - - - - - Initializes the repository with the given - for newly created mocks from the repository. - - The behavior to use for mocks created - using the repository method if not overriden - by using the overload. - - - - A that returns an empty default value - for invocations that do not have setups or return values, with loose mocks. - This is the default behavior for a mock. - - - - - Interface to be implemented by classes that determine the - default value of non-expected invocations. - - - - - Defines the default value to return in all the methods returning . - The type of the return value.The value to set as default. - - - - Provides a value for the given member and arguments. - - The member to provide a default value for. - - - - - The intention of is to create a more readable - string representation for the failure message. - - - - - Implements the fluent API. - - - - - Defines the Throws verb. - - - - - Specifies the exception to throw when the method is invoked. - - Exception instance to throw. - - This example shows how to throw an exception when the method is - invoked with an empty string argument: - - mock.Setup(x => x.Execute("")) - .Throws(new ArgumentException()); - - - - - - Specifies the type of exception to throw when the method is invoked. - - Type of exception to instantiate and throw when the setup is matched. - - This example shows how to throw an exception when the method is - invoked with an empty string argument: - - mock.Setup(x => x.Execute("")) - .Throws<ArgumentException>(); - - - - - - Implements the fluent API. - - - - - Defines occurrence members to constraint setups. - - - - - The expected invocation can happen at most once. - - - - var mock = new Mock<ICommand>(); - mock.Setup(foo => foo.Execute("ping")) - .AtMostOnce(); - - - - - - The expected invocation can happen at most specified number of times. - - The number of times to accept calls. - - - var mock = new Mock<ICommand>(); - mock.Setup(foo => foo.Execute("ping")) - .AtMost( 5 ); - - - - - - Defines the Verifiable verb. - - - - - Marks the expectation as verifiable, meaning that a call - to will check if this particular - expectation was met. - - - The following example marks the expectation as verifiable: - - mock.Expect(x => x.Execute("ping")) - .Returns(true) - .Verifiable(); - - - - - - Marks the expectation as verifiable, meaning that a call - to will check if this particular - expectation was met, and specifies a message for failures. - - - The following example marks the expectation as verifiable: - - mock.Expect(x => x.Execute("ping")) - .Returns(true) - .Verifiable("Ping should be executed always!"); - - - - - - Implements the fluent API. - - - - - We need this non-generics base class so that - we can use from - generic code. - - - - - Implements the fluent API. - - - - - Implements the fluent API. - - - - - Implements the fluent API. - - - - - Defines the Callback verb for property getter setups. - - - Mocked type. - Type of the property. - - - - Specifies a callback to invoke when the property is retrieved. - - Callback method to invoke. - - Invokes the given callback with the property value being set. - - mock.SetupGet(x => x.Suspended) - .Callback(() => called = true) - .Returns(true); - - - - - - Implements the fluent API. - - - - - Defines the Returns verb for property get setups. - - Mocked type. - Type of the property. - - - - Specifies the value to return. - - The value to return, or . - - Return a true value from the property getter call: - - mock.SetupGet(x => x.Suspended) - .Returns(true); - - - - - - Specifies a function that will calculate the value to return for the property. - - The function that will calculate the return value. - - Return a calculated value when the property is retrieved: - - mock.SetupGet(x => x.Suspended) - .Returns(() => returnValues[0]); - - The lambda expression to retrieve the return value is lazy-executed, - meaning that its value may change depending on the moment the property - is retrieved and the value the returnValues array has at - that moment. - - - - - Calls the real property of the object and returns its return value. - - The value calculated by the real property of the object. - - - - Implements the fluent API. - - - - - Encapsulates a method that has five parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has five parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has six parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has six parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has seven parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has seven parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has eight parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has eight parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has nine parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has nine parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has ten parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has ten parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has eleven parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has eleven parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has twelve parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has twelve parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has thirteen parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has thirteen parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has fourteen parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the fourteenth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The fourteenth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has fourteen parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the fourteenth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The fourteenth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has fifteen parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the fourteenth parameter of the method that this delegate encapsulates. - The type of the fifteenth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The fourteenth parameter of the method that this delegate encapsulates. - The fifteenth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has fifteen parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the fourteenth parameter of the method that this delegate encapsulates. - The type of the fifteenth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The fourteenth parameter of the method that this delegate encapsulates. - The fifteenth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has sixteen parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the fourteenth parameter of the method that this delegate encapsulates. - The type of the fifteenth parameter of the method that this delegate encapsulates. - The type of the sixteenth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The fourteenth parameter of the method that this delegate encapsulates. - The fifteenth parameter of the method that this delegate encapsulates. - The sixteenth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has sixteen parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the fourteenth parameter of the method that this delegate encapsulates. - The type of the fifteenth parameter of the method that this delegate encapsulates. - The type of the sixteenth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The fourteenth parameter of the method that this delegate encapsulates. - The fifteenth parameter of the method that this delegate encapsulates. - The sixteenth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Provides additional methods on mocks. - - - Those methods are useful for Testeroids support. - - - - - Resets the calls previously made on the specified mock. - - The mock whose calls need to be reset. - - - - Helper class to setup a full trace between many mocks - - - - - Initialize a trace setup - - - - - Allow sequence to be repeated - - - - - define nice api - - - - - Perform an expectation in the trace. - - - - - Marks a method as a matcher, which allows complete replacement - of the built-in class with your own argument - matching rules. - - - This feature has been deprecated in favor of the new - and simpler . - - - The argument matching is used to determine whether a concrete - invocation in the mock matches a given setup. This - matching mechanism is fully extensible. - - - There are two parts of a matcher: the compiler matcher - and the runtime matcher. - - - Compiler matcher - Used to satisfy the compiler requirements for the - argument. Needs to be a method optionally receiving any arguments - you might need for the matching, but with a return type that - matches that of the argument. - - Let's say I want to match a lists of orders that contains - a particular one. I might create a compiler matcher like the following: - - - public static class Orders - { - [Matcher] - public static IEnumerable<Order> Contains(Order order) - { - return null; - } - } - - Now we can invoke this static method instead of an argument in an - invocation: - - var order = new Order { ... }; - var mock = new Mock<IRepository<Order>>(); - - mock.Setup(x => x.Save(Orders.Contains(order))) - .Throws<ArgumentException>(); - - Note that the return value from the compiler matcher is irrelevant. - This method will never be called, and is just used to satisfy the - compiler and to signal Moq that this is not a method that we want - to be invoked at runtime. - - - - Runtime matcher - - The runtime matcher is the one that will actually perform evaluation - when the test is run, and is defined by convention to have the - same signature as the compiler matcher, but where the return - value is the first argument to the call, which contains the - object received by the actual invocation at runtime: - - public static bool Contains(IEnumerable<Order> orders, Order order) - { - return orders.Contains(order); - } - - At runtime, the mocked method will be invoked with a specific - list of orders. This value will be passed to this runtime - matcher as the first argument, while the second argument is the - one specified in the setup (x.Save(Orders.Contains(order))). - - The boolean returned determines whether the given argument has been - matched. If all arguments to the expected method are matched, then - the setup matches and is evaluated. - - - - - - Using this extensible infrastructure, you can easily replace the entire - set of matchers with your own. You can also avoid the - typical (and annoying) lengthy expressions that result when you have - multiple arguments that use generics. - - - The following is the complete example explained above: - - public static class Orders - { - [Matcher] - public static IEnumerable<Order> Contains(Order order) - { - return null; - } - - public static bool Contains(IEnumerable<Order> orders, Order order) - { - return orders.Contains(order); - } - } - - And the concrete test using this matcher: - - var order = new Order { ... }; - var mock = new Mock<IRepository<Order>>(); - - mock.Setup(x => x.Save(Orders.Contains(order))) - .Throws<ArgumentException>(); - - // use mock, invoke Save, and have the matcher filter. - - - - - - Provides a mock implementation of . - - Any interface type can be used for mocking, but for classes, only abstract and virtual members can be mocked. - - The behavior of the mock with regards to the setups and the actual calls is determined - by the optional that can be passed to the - constructor. - - Type to mock, which can be an interface or a class. - The following example shows establishing setups with specific values - for method invocations: - - // Arrange - var order = new Order(TALISKER, 50); - var mock = new Mock<IWarehouse>(); - - mock.Setup(x => x.HasInventory(TALISKER, 50)).Returns(true); - - // Act - order.Fill(mock.Object); - - // Assert - Assert.True(order.IsFilled); - - The following example shows how to use the class - to specify conditions for arguments instead of specific values: - - // Arrange - var order = new Order(TALISKER, 50); - var mock = new Mock<IWarehouse>(); - - // shows how to expect a value within a range - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsInRange(0, 100, Range.Inclusive))) - .Returns(false); - - // shows how to throw for unexpected calls. - mock.Setup(x => x.Remove( - It.IsAny<string>(), - It.IsAny<int>())) - .Throws(new InvalidOperationException()); - - // Act - order.Fill(mock.Object); - - // Assert - Assert.False(order.IsFilled); - - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Ctor invoked by AsTInterface exclusively. - - - - - Initializes an instance of the mock with default behavior. - - var mock = new Mock<IFormatProvider>(); - - - - - Initializes an instance of the mock with default behavior and with - the given constructor arguments for the class. (Only valid when is a class) - - The mock will try to find the best match constructor given the constructor arguments, and invoke that - to initialize the instance. This applies only for classes, not interfaces. - - var mock = new Mock<MyProvider>(someArgument, 25); - Optional constructor arguments if the mocked type is a class. - - - - Initializes an instance of the mock with the specified behavior. - - var mock = new Mock<IFormatProvider>(MockBehavior.Relaxed); - Behavior of the mock. - - - - Initializes an instance of the mock with a specific behavior with - the given constructor arguments for the class. - - The mock will try to find the best match constructor given the constructor arguments, and invoke that - to initialize the instance. This applies only to classes, not interfaces. - - var mock = new Mock<MyProvider>(someArgument, 25); - Behavior of the mock.Optional constructor arguments if the mocked type is a class. - - - - Returns the name of the mock - - - - - Returns the mocked object value. - - - - - Specifies a setup on the mocked type for a call to - to a void method. - - If more than one setup is specified for the same method or property, - the latest one wins and is the one that will be executed. - Lambda expression that specifies the expected method invocation. - - var mock = new Mock<IProcessor>(); - mock.Setup(x => x.Execute("ping")); - - - - - - Specifies a setup on the mocked type for a call to - to a value returning method. - Type of the return value. Typically omitted as it can be inferred from the expression. - If more than one setup is specified for the same method or property, - the latest one wins and is the one that will be executed. - Lambda expression that specifies the method invocation. - - mock.Setup(x => x.HasInventory("Talisker", 50)).Returns(true); - - - - - - Specifies a setup on the mocked type for a call to - to a property getter. - - If more than one setup is set for the same property getter, - the latest one wins and is the one that will be executed. - Type of the property. Typically omitted as it can be inferred from the expression.Lambda expression that specifies the property getter. - - mock.SetupGet(x => x.Suspended) - .Returns(true); - - - - - - Specifies a setup on the mocked type for a call to - to a property setter. - - If more than one setup is set for the same property setter, - the latest one wins and is the one that will be executed. - - This overloads allows the use of a callback already - typed for the property type. - - Type of the property. Typically omitted as it can be inferred from the expression.The Lambda expression that sets a property to a value. - - mock.SetupSet(x => x.Suspended = true); - - - - - - Specifies a setup on the mocked type for a call to - to a property setter. - - If more than one setup is set for the same property setter, - the latest one wins and is the one that will be executed. - Lambda expression that sets a property to a value. - - mock.SetupSet(x => x.Suspended = true); - - - - - - Specifies that the given property should have "property behavior", - meaning that setting its value will cause it to be saved and - later returned when the property is requested. (this is also - known as "stubbing"). - - Type of the property, inferred from the property - expression (does not need to be specified). - Property expression to stub. - If you have an interface with an int property Value, you might - stub it using the following straightforward call: - - var mock = new Mock<IHaveValue>(); - mock.Stub(v => v.Value); - - After the Stub call has been issued, setting and - retrieving the object value will behave as expected: - - IHaveValue v = mock.Object; - - v.Value = 5; - Assert.Equal(5, v.Value); - - - - - - Specifies that the given property should have "property behavior", - meaning that setting its value will cause it to be saved and - later returned when the property is requested. This overload - allows setting the initial value for the property. (this is also - known as "stubbing"). - - Type of the property, inferred from the property - expression (does not need to be specified). - Property expression to stub.Initial value for the property. - If you have an interface with an int property Value, you might - stub it using the following straightforward call: - - var mock = new Mock<IHaveValue>(); - mock.SetupProperty(v => v.Value, 5); - - After the SetupProperty call has been issued, setting and - retrieving the object value will behave as expected: - - IHaveValue v = mock.Object; - // Initial value was stored - Assert.Equal(5, v.Value); - - // New value set which changes the initial value - v.Value = 6; - Assert.Equal(6, v.Value); - - - - - - Specifies that the all properties on the mock should have "property behavior", - meaning that setting its value will cause it to be saved and - later returned when the property is requested. (this is also - known as "stubbing"). The default value for each property will be the - one generated as specified by the property for the mock. - - If the mock is set to , - the mocked default values will also get all properties setup recursively. - - - - - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - This example assumes that the mock has been used, and later we want to verify that a given - invocation with specific parameters was performed: - - var mock = new Mock<IProcessor>(); - // exercise mock - //... - // Will throw if the test code didn't call Execute with a "ping" string argument. - mock.Verify(proc => proc.Execute("ping")); - - The invocation was not performed on the mock.Expression to verify. - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called. - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called. - - - - Verifies that a specific invocation matching the given expression was performed on the mock, - specifying a failure error message. Use in conjuntion with the default - . - - This example assumes that the mock has been used, and later we want to verify that a given - invocation with specific parameters was performed: - - var mock = new Mock<IProcessor>(); - // exercise mock - //... - // Will throw if the test code didn't call Execute with a "ping" string argument. - mock.Verify(proc => proc.Execute("ping")); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails. - - - - Verifies that a specific invocation matching the given expression was performed on the mock, - specifying a failure error message. Use in conjuntion with the default - . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Message to show if verification fails. - - - - Verifies that a specific invocation matching the given expression was performed on the mock, - specifying a failure error message. Use in conjuntion with the default - . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Message to show if verification fails. - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - This example assumes that the mock has been used, and later we want to verify that a given - invocation with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't call HasInventory. - mock.Verify(warehouse => warehouse.HasInventory(TALISKER, 50)); - - The invocation was not performed on the mock.Expression to verify.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock. Use in conjuntion - with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock. Use in conjuntion - with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock, specifying a failure - error message. - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't call HasInventory. - mock.Verify(warehouse => warehouse.HasInventory(TALISKER, 50), "When filling orders, inventory has to be checked"); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock, specifying a failure - error message. - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Message to show if verification fails.Type of return value from the expression. - - - - Verifies that a property was read on the mock. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was retrieved from it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't retrieve the IsClosed property. - mock.VerifyGet(warehouse => warehouse.IsClosed); - - The invocation was not performed on the mock.Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock, specifying a failure - error message. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was retrieved from it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't retrieve the IsClosed property. - mock.VerifyGet(warehouse => warehouse.IsClosed); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock, specifying a failure - error message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock, specifying a failure - error message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was set on the mock. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was set on it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed = true); - - The invocation was not performed on the mock.Expression to verify. - - - - Verifies that a property was set on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - - - - Verifies that a property was set on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - - - - Verifies that a property was set on the mock, specifying - a failure message. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was set on it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed = true, "Warehouse should always be closed after the action"); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails. - - - - Verifies that a property was set on the mock, specifying - a failure message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - - - - Verifies that a property was set on the mock, specifying - a failure message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - - - - Raises the event referenced in using - the given argument. - - The argument is - invalid for the target event invocation, or the is - not an event attach or detach expression. - - The following example shows how to raise a event: - - var mock = new Mock<IViewModel>(); - - mock.Raise(x => x.PropertyChanged -= null, new PropertyChangedEventArgs("Name")); - - - This example shows how to invoke an event with a custom event arguments - class in a view that will cause its corresponding presenter to - react by changing its state: - - var mockView = new Mock<IOrdersView>(); - var presenter = new OrdersPresenter(mockView.Object); - - // Check that the presenter has no selection by default - Assert.Null(presenter.SelectedOrder); - - // Raise the event with a specific arguments data - mockView.Raise(v => v.SelectionChanged += null, new OrderEventArgs { Order = new Order("moq", 500) }); - - // Now the presenter reacted to the event, and we have a selected order - Assert.NotNull(presenter.SelectedOrder); - Assert.Equal("moq", presenter.SelectedOrder.ProductName); - - - - - - Raises the event referenced in using - the given argument for a non-EventHandler typed event. - - The arguments are - invalid for the target event invocation, or the is - not an event attach or detach expression. - - The following example shows how to raise a custom event that does not adhere to - the standard EventHandler: - - var mock = new Mock<IViewModel>(); - - mock.Raise(x => x.MyEvent -= null, "Name", bool, 25); - - - - - - Exposes the mocked object instance. - - - - - Allows naming of your mocks, so they can be easily identified in error messages (e.g. from failed assertions). - - - - - - - - Provides legacy API members as extensions so that - existing code continues to compile, but new code - doesn't see then. - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Provides additional methods on mocks. - - - Provided as extension methods as they confuse the compiler - with the overloads taking Action. - - - - - Specifies a setup on the mocked type for a call to - to a property setter, regardless of its value. - - - If more than one setup is set for the same property setter, - the latest one wins and is the one that will be executed. - - Type of the property. Typically omitted as it can be inferred from the expression. - Type of the mock. - The target mock for the setup. - Lambda expression that specifies the property setter. - - - mock.SetupSet(x => x.Suspended); - - - - This method is not legacy, but must be on an extension method to avoid - confusing the compiler with the new Action syntax. - - - - - Verifies that a property has been set on the mock, regarless of its value. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - Expression to verify. - The mock instance. - Mocked type. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Verifies that a property has been set on the mock, specifying a failure - error message. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - Expression to verify. - Message to show if verification fails. - The mock instance. - Mocked type. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Verifies that a property has been set on the mock, regardless - of the value but only the specified number of times. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - The invocation was not call the times specified by - . - The mock instance. - Mocked type. - The number of times a method is allowed to be called. - Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Verifies that a property has been set on the mock, regardless - of the value but only the specified number of times, and specifying a failure - error message. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - The invocation was not call the times specified by - . - The mock instance. - Mocked type. - The number of times a method is allowed to be called. - Message to show if verification fails. - Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Helper for sequencing return values in the same method. - - - - - Return a sequence of values, once per call. - - - - - Casts the expression to a lambda expression, removing - a cast if there's any. - - - - - Casts the body of the lambda expression to a . - - If the body is not a method call. - - - - Converts the body of the lambda expression into the referenced by it. - - - - - Checks whether the body of the lambda expression is a property access. - - - - - Checks whether the expression is a property access. - - - - - Checks whether the body of the lambda expression is a property indexer, which is true - when the expression is an whose - has - equal to . - - - - - Checks whether the expression is a property indexer, which is true - when the expression is an whose - has - equal to . - - - - - Creates an expression that casts the given expression to the - type. - - - - - TODO: remove this code when https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=331583 - is fixed. - - - - - Extracts, into a common form, information from a - around either a (for a normal method call) - or a (for a delegate invocation). - - - - - Tests if a type is a delegate type (subclasses ). - - - - - Provides partial evaluation of subtrees, whenever they can be evaluated locally. - - Matt Warren: http://blogs.msdn.com/mattwar - Documented by InSTEDD: http://www.instedd.org - - - - Performs evaluation and replacement of independent sub-trees - - The root of the expression tree. - A function that decides whether a given expression - node can be part of the local function. - A new tree with sub-trees evaluated and replaced. - - - - Performs evaluation and replacement of independent sub-trees - - The root of the expression tree. - A new tree with sub-trees evaluated and replaced. - - - - Evaluates and replaces sub-trees when first candidate is reached (top-down) - - - - - Performs bottom-up analysis to determine which nodes can possibly - be part of an evaluated sub-tree. - - - - - Ensures the given is not null. - Throws otherwise. - - - - - Ensures the given string is not null or empty. - Throws in the first case, or - in the latter. - - - - - Checks an argument to ensure it is in the specified range including the edges. - - Type of the argument to check, it must be an type. - - The expression containing the name of the argument. - The argument value to check. - The minimun allowed value for the argument. - The maximun allowed value for the argument. - - - - Checks an argument to ensure it is in the specified range excluding the edges. - - Type of the argument to check, it must be an type. - - The expression containing the name of the argument. - The argument value to check. - The minimun allowed value for the argument. - The maximun allowed value for the argument. - - - - Implemented by all generated mock object instances. - - - - - Implemented by all generated mock object instances. - - - - - Reference the Mock that contains this as the mock.Object value. - - - - - Reference the Mock that contains this as the mock.Object value. - - - - - Implements the actual interception and method invocation for - all mocks. - - - - - Implements the fluent API. - - - - - Defines the Callback verb for property setter setups. - - Type of the property. - - - - Specifies a callback to invoke when the property is set that receives the - property value being set. - - Callback method to invoke. - - Invokes the given callback with the property value being set. - - mock.SetupSet(x => x.Suspended) - .Callback((bool state) => Console.WriteLine(state)); - - - - - - Allows the specification of a matching condition for an - argument in a method invocation, rather than a specific - argument value. "It" refers to the argument being matched. - - This class allows the setup to match a method invocation - with an arbitrary value, with a value in a specified range, or - even one that matches a given predicate. - - - - - Matches any value of the given type. - - Typically used when the actual argument value for a method - call is not relevant. - - - // Throws an exception for a call to Remove with any string value. - mock.Setup(x => x.Remove(It.IsAny<string>())).Throws(new InvalidOperationException()); - - Type of the value. - - - - Matches any value of the given type, except null. - Type of the value. - - - - Matches any value that satisfies the given predicate. - Type of the argument to check.The predicate used to match the method argument. - Allows the specification of a predicate to perform matching - of method call arguments. - - This example shows how to return the value 1 whenever the argument to the - Do method is an even number. - - mock.Setup(x => x.Do(It.Is<int>(i => i % 2 == 0))) - .Returns(1); - - This example shows how to throw an exception if the argument to the - method is a negative number: - - mock.Setup(x => x.GetUser(It.Is<int>(i => i < 0))) - .Throws(new ArgumentException()); - - - - - - Matches any value that is in the range specified. - Type of the argument to check.The lower bound of the range.The upper bound of the range. - The kind of range. See . - - The following example shows how to expect a method call - with an integer argument within the 0..100 range. - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsInRange(0, 100, Range.Inclusive))) - .Returns(false); - - - - - - Matches any value that is present in the sequence specified. - Type of the argument to check.The sequence of possible values. - The following example shows how to expect a method call - with an integer argument with value from a list. - - var values = new List<int> { 1, 2, 3 }; - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsIn(values))) - .Returns(false); - - - - - - Matches any value that is present in the sequence specified. - Type of the argument to check.The sequence of possible values. - The following example shows how to expect a method call - with an integer argument with a value of 1, 2, or 3. - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsIn(1, 2, 3))) - .Returns(false); - - - - - - Matches any value that is not found in the sequence specified. - Type of the argument to check.The sequence of disallowed values. - The following example shows how to expect a method call - with an integer argument with value not found from a list. - - var values = new List<int> { 1, 2, 3 }; - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsNotIn(values))) - .Returns(false); - - - - - - Matches any value that is not found in the sequence specified. - Type of the argument to check.The sequence of disallowed values. - The following example shows how to expect a method call - with an integer argument of any value except 1, 2, or 3. - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsNotIn(1, 2, 3))) - .Returns(false); - - - - - - Matches a string argument if it matches the given regular expression pattern. - The pattern to use to match the string argument value. - The following example shows how to expect a call to a method where the - string argument matches the given regular expression: - - mock.Setup(x => x.Check(It.IsRegex("[a-z]+"))).Returns(1); - - - - - - Matches a string argument if it matches the given regular expression pattern. - The pattern to use to match the string argument value.The options used to interpret the pattern. - The following example shows how to expect a call to a method where the - string argument matches the given regular expression, in a case insensitive way: - - mock.Setup(x => x.Check(It.IsRegex("[a-z]+", RegexOptions.IgnoreCase))).Returns(1); - - - - - - Matcher to treat static functions as matchers. - - mock.Setup(x => x.StringMethod(A.MagicString())); - - public static class A - { - [Matcher] - public static string MagicString() { return null; } - public static bool MagicString(string arg) - { - return arg == "magic"; - } - } - - Will succeed if: mock.Object.StringMethod("magic"); - and fail with any other call. - - - - - Options to customize the behavior of the mock. - - - - - Causes the mock to always throw - an exception for invocations that don't have a - corresponding setup. - - - - - Will never throw exceptions, returning default - values when necessary (null for reference types, - zero for value types or empty enumerables and arrays). - - - - - Default mock behavior, which equals . - - - - - Exception thrown by mocks when setups are not matched, - the mock is not properly setup, etc. - - - A distinct exception type is provided so that exceptions - thrown by the mock can be differentiated in tests that - expect other exceptions to be thrown (i.e. ArgumentException). - - Richer exception hierarchy/types are not provided as - tests typically should not catch or expect exceptions - from the mocks. These are typically the result of changes - in the tested class or its collaborators implementation, and - result in fixes in the mock setup so that they dissapear and - allow the test to pass. - - - - - - Supports the serialization infrastructure. - - Serialization information. - Streaming context. - - - - Supports the serialization infrastructure. - - Serialization information. - Streaming context. - - - - Indicates whether this exception is a verification fault raised by Verify() - - - - - Made internal as it's of no use for - consumers, but it's important for - our own tests. - - - - - Used by the mock factory to accumulate verification - failures. - - - - - Supports the serialization infrastructure. - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Mock type has already been initialized by accessing its Object property. Adding interfaces must be done before that.. - - - - - Looks up a localized string similar to Value cannot be an empty string.. - - - - - Looks up a localized string similar to Can only add interfaces to the mock.. - - - - - Looks up a localized string similar to Can't set return value for void method {0}.. - - - - - Looks up a localized string similar to Constructor arguments cannot be passed for delegate mocks.. - - - - - Looks up a localized string similar to Constructor arguments cannot be passed for interface mocks.. - - - - - Looks up a localized string similar to A matching constructor for the given arguments was not found on the mocked type.. - - - - - Looks up a localized string similar to Could not locate event for attach or detach method {0}.. - - - - - Looks up a localized string similar to Expression {0} involves a field access, which is not supported. Use properties instead.. - - - - - Looks up a localized string similar to Type to mock must be an interface or an abstract or non-sealed class. . - - - - - Looks up a localized string similar to Cannot retrieve a mock with the given object type {0} as it's not the main type of the mock or any of its additional interfaces. - Please cast the argument to one of the supported types: {1}. - Remember that there's no generics covariance in the CLR, so your object must be one of these types in order for the call to succeed.. - - - - - Looks up a localized string similar to The equals ("==" or "=" in VB) and the conditional 'and' ("&&" or "AndAlso" in VB) operators are the only ones supported in the query specification expression. Unsupported expression: {0}. - - - - - Looks up a localized string similar to LINQ method '{0}' not supported.. - - - - - Looks up a localized string similar to Expression contains a call to a method which is not virtual (overridable in VB) or abstract. Unsupported expression: {0}. - - - - - Looks up a localized string similar to Member {0}.{1} does not exist.. - - - - - Looks up a localized string similar to Method {0}.{1} is public. Use strong-typed Expect overload instead: - mock.Setup(x => x.{1}()); - . - - - - - Looks up a localized string similar to {0} invocation failed with mock behavior {1}. - {2}. - - - - - Looks up a localized string similar to Expected only {0} calls to {1}.. - - - - - Looks up a localized string similar to Expected only one call to {0}.. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at least {2} times, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at least once, but was never performed: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at most {3} times, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at most once, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock between {2} and {3} times (Exclusive), but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock between {2} and {3} times (Inclusive), but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock exactly {2} times, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock should never have been performed, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock once, but was {4} times: {1}. - - - - - Looks up a localized string similar to All invocations on the mock must have a corresponding setup.. - - - - - Looks up a localized string similar to Object instance was not created by Moq.. - - - - - Looks up a localized string similar to Out expression must evaluate to a constant value.. - - - - - Looks up a localized string similar to Property {0}.{1} does not have a getter.. - - - - - Looks up a localized string similar to Property {0}.{1} does not exist.. - - - - - Looks up a localized string similar to Property {0}.{1} is write-only.. - - - - - Looks up a localized string similar to Property {0}.{1} is read-only.. - - - - - Looks up a localized string similar to Property {0}.{1} does not have a setter.. - - - - - Looks up a localized string similar to Cannot raise a mocked event unless it has been associated (attached) to a concrete event in a mocked object.. - - - - - Looks up a localized string similar to Ref expression must evaluate to a constant value.. - - - - - Looks up a localized string similar to Invocation needs to return a value and therefore must have a corresponding setup that provides it.. - - - - - Looks up a localized string similar to A lambda expression is expected as the argument to It.Is<T>.. - - - - - Looks up a localized string similar to Invocation {0} should not have been made.. - - - - - Looks up a localized string similar to Expression is not a method invocation: {0}. - - - - - Looks up a localized string similar to Expression is not a property access: {0}. - - - - - Looks up a localized string similar to Expression is not a property setter invocation.. - - - - - Looks up a localized string similar to Expression references a method that does not belong to the mocked object: {0}. - - - - - Looks up a localized string similar to Invalid setup on a non-virtual (overridable in VB) member: {0}. - - - - - Looks up a localized string similar to Type {0} does not implement required interface {1}. - - - - - Looks up a localized string similar to Type {0} does not from required type {1}. - - - - - Looks up a localized string similar to To specify a setup for public property {0}.{1}, use the typed overloads, such as: - mock.Setup(x => x.{1}).Returns(value); - mock.SetupGet(x => x.{1}).Returns(value); //equivalent to previous one - mock.SetupSet(x => x.{1}).Callback(callbackDelegate); - . - - - - - Looks up a localized string similar to Unsupported expression: {0}. - - - - - Looks up a localized string similar to Only property accesses are supported in intermediate invocations on a setup. Unsupported expression {0}.. - - - - - Looks up a localized string similar to Expression contains intermediate property access {0}.{1} which is of type {2} and cannot be mocked. Unsupported expression {3}.. - - - - - Looks up a localized string similar to Setter expression cannot use argument matchers that receive parameters.. - - - - - Looks up a localized string similar to Member {0} is not supported for protected mocking.. - - - - - Looks up a localized string similar to Setter expression can only use static custom matchers.. - - - - - Looks up a localized string similar to The following setups were not matched: - {0}. - - - - - Looks up a localized string similar to Invalid verify on a non-virtual (overridable in VB) member: {0}. - - - - - Allows setups to be specified for protected members by using their - name as a string, rather than strong-typing them which is not possible - due to their visibility. - - - - - Specifies a setup for a void method invocation with the given - , optionally specifying arguments for the method call. - - The name of the void method to be invoked. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - - - - Specifies a setup for an invocation on a property or a non void method with the given - , optionally specifying arguments for the method call. - - The name of the method or property to be invoked. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - The return type of the method or property. - - - - Specifies a setup for an invocation on a property getter with the given - . - - The name of the property. - The type of the property. - - - - Specifies a setup for an invocation on a property setter with the given - . - - The name of the property. - The property value. If argument matchers are used, - remember to use rather than . - The type of the property. - - - - Specifies a verify for a void method with the given , - optionally specifying arguments for the method call. Use in conjuntion with the default - . - - The invocation was not call the times specified by - . - The name of the void method to be verified. - The number of times a method is allowed to be called. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - - - - Specifies a verify for an invocation on a property or a non void method with the given - , optionally specifying arguments for the method call. - - The invocation was not call the times specified by - . - The name of the method or property to be invoked. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - The number of times a method is allowed to be called. - The type of return value from the expression. - - - - Specifies a verify for an invocation on a property getter with the given - . - The invocation was not call the times specified by - . - - The name of the property. - The number of times a method is allowed to be called. - The type of the property. - - - - Specifies a setup for an invocation on a property setter with the given - . - - The invocation was not call the times specified by - . - The name of the property. - The number of times a method is allowed to be called. - The property value. - The type of the property. If argument matchers are used, - remember to use rather than . - - - - Allows the specification of a matching condition for an - argument in a protected member setup, rather than a specific - argument value. "ItExpr" refers to the argument being matched. - - - Use this variant of argument matching instead of - for protected setups. - This class allows the setup to match a method invocation - with an arbitrary value, with a value in a specified range, or - even one that matches a given predicate, or null. - - - - - Matches a null value of the given type. - - - Required for protected mocks as the null value cannot be used - directly as it prevents proper method overload selection. - - - - // Throws an exception for a call to Remove with a null string value. - mock.Protected() - .Setup("Remove", ItExpr.IsNull<string>()) - .Throws(new InvalidOperationException()); - - - Type of the value. - - - - Matches any value of the given type. - - - Typically used when the actual argument value for a method - call is not relevant. - - - - // Throws an exception for a call to Remove with any string value. - mock.Protected() - .Setup("Remove", ItExpr.IsAny<string>()) - .Throws(new InvalidOperationException()); - - - Type of the value. - - - - Matches any value that satisfies the given predicate. - - Type of the argument to check. - The predicate used to match the method argument. - - Allows the specification of a predicate to perform matching - of method call arguments. - - - This example shows how to return the value 1 whenever the argument to the - Do method is an even number. - - mock.Protected() - .Setup("Do", ItExpr.Is<int>(i => i % 2 == 0)) - .Returns(1); - - This example shows how to throw an exception if the argument to the - method is a negative number: - - mock.Protected() - .Setup("GetUser", ItExpr.Is<int>(i => i < 0)) - .Throws(new ArgumentException()); - - - - - - Matches any value that is in the range specified. - - Type of the argument to check. - The lower bound of the range. - The upper bound of the range. - The kind of range. See . - - The following example shows how to expect a method call - with an integer argument within the 0..100 range. - - mock.Protected() - .Setup("HasInventory", - ItExpr.IsAny<string>(), - ItExpr.IsInRange(0, 100, Range.Inclusive)) - .Returns(false); - - - - - - Matches a string argument if it matches the given regular expression pattern. - - The pattern to use to match the string argument value. - - The following example shows how to expect a call to a method where the - string argument matches the given regular expression: - - mock.Protected() - .Setup("Check", ItExpr.IsRegex("[a-z]+")) - .Returns(1); - - - - - - Matches a string argument if it matches the given regular expression pattern. - - The pattern to use to match the string argument value. - The options used to interpret the pattern. - - The following example shows how to expect a call to a method where the - string argument matches the given regular expression, in a case insensitive way: - - mock.Protected() - .Setup("Check", ItExpr.IsRegex("[a-z]+", RegexOptions.IgnoreCase)) - .Returns(1); - - - - - - Enables the Protected() method on , - allowing setups to be set for protected members by using their - name as a string, rather than strong-typing them which is not possible - due to their visibility. - - - - - Enable protected setups for the mock. - - Mocked object type. Typically omitted as it can be inferred from the mock instance. - The mock to set the protected setups on. - - - - - - - - - - - - Kind of range to use in a filter specified through - . - - - - - The range includes the to and - from values. - - - - - The range does not include the to and - from values. - - - - - Determines the way default values are generated - calculated for loose mocks. - - - - - Default behavior, which generates empty values for - value types (i.e. default(int)), empty array and - enumerables, and nulls for all other reference types. - - - - - Whenever the default value generated by - is null, replaces this value with a mock (if the type - can be mocked). - - - For sealed classes, a null value will be generated. - - - - - A default implementation of IQueryable for use with QueryProvider - - - - - The is a - static method that returns an IQueryable of Mocks of T which is used to - apply the linq specification to. - - - - - Allows creation custom value matchers that can be used on setups and verification, - completely replacing the built-in class with your own argument - matching rules. - - See also . - - - - - Provided for the sole purpose of rendering the delegate passed to the - matcher constructor if no friendly render lambda is provided. - - - - - Initializes the match with the condition that - will be checked in order to match invocation - values. - The condition to match against actual values. - - - - - - - - - This method is used to set an expression as the last matcher invoked, - which is used in the SetupSet to allow matchers in the prop = value - delegate expression. This delegate is executed in "fluent" mode in - order to capture the value being set, and construct the corresponding - methodcall. - This is also used in the MatcherFactory for each argument expression. - This method ensures that when we execute the delegate, we - also track the matcher that was invoked, so that when we create the - methodcall we build the expression using it, rather than the null/default - value returned from the actual invocation. - - - - - Allows creation custom value matchers that can be used on setups and verification, - completely replacing the built-in class with your own argument - matching rules. - Type of the value to match. - The argument matching is used to determine whether a concrete - invocation in the mock matches a given setup. This - matching mechanism is fully extensible. - - Creating a custom matcher is straightforward. You just need to create a method - that returns a value from a call to with - your matching condition and optional friendly render expression: - - [Matcher] - public Order IsBigOrder() - { - return Match<Order>.Create( - o => o.GrandTotal >= 5000, - /* a friendly expression to render on failures */ - () => IsBigOrder()); - } - - This method can be used in any mock setup invocation: - - mock.Setup(m => m.Submit(IsBigOrder()).Throws<UnauthorizedAccessException>(); - - At runtime, Moq knows that the return value was a matcher (note that the method MUST be - annotated with the [Matcher] attribute in order to determine this) and - evaluates your predicate with the actual value passed into your predicate. - - Another example might be a case where you want to match a lists of orders - that contains a particular one. You might create matcher like the following: - - - public static class Orders - { - [Matcher] - public static IEnumerable<Order> Contains(Order order) - { - return Match<IEnumerable<Order>>.Create(orders => orders.Contains(order)); - } - } - - Now we can invoke this static method instead of an argument in an - invocation: - - var order = new Order { ... }; - var mock = new Mock<IRepository<Order>>(); - - mock.Setup(x => x.Save(Orders.Contains(order))) - .Throws<ArgumentException>(); - - - - - - Tracks the current mock and interception context. - - - - - Having an active fluent mock context means that the invocation - is being performed in "trial" mode, just to gather the - target method and arguments that need to be matched later - when the actual invocation is made. - - - - - A that returns an empty default value - for non-mockeable types, and mocks for all other types (interfaces and - non-sealed classes) that can be mocked. - - - - - Allows querying the universe of mocks for those that behave - according to the LINQ query specification. - - - This entry-point into Linq to Mocks is the only one in the root Moq - namespace to ease discovery. But to get all the mocking extension - methods on Object, a using of Moq.Linq must be done, so that the - polluting of the intellisense for all objects is an explicit opt-in. - - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The type of the mocked object to query. - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The predicate with the setup expressions. - The type of the mocked object to query. - - - - Creates an mock object of the indicated type. - - The type of the mocked object. - The mocked object created. - - - - Creates an mock object of the indicated type. - - The predicate with the setup expressions. - The type of the mocked object. - The mocked object created. - - - - Creates the mock query with the underlying queriable implementation. - - - - - Wraps the enumerator inside a queryable. - - - - - Method that is turned into the actual call from .Query{T}, to - transform the queryable query into a normal enumerable query. - This method is never used directly by consumers. - - - - - Extension method used to support Linq-like setup properties that are not virtual but do have - a getter and a setter, thereby allowing the use of Linq to Mocks to quickly initialize Dtos too :) - - - - - Helper extensions that are used by the query translator. - - - - - Retrieves a fluent mock from the given setup expression. - - - - - Gets an autogenerated interface with a method on it that matches the signature of the specified - . - - - Such an interface can then be mocked, and a delegate pointed at the method on the mocked instance. - This is how we support delegate mocking. The factory caches such interfaces and reuses them - for repeated requests for the same delegate type. - - The delegate type for which an interface is required. - The method on the autogenerated interface. - - - - - - - - - - Defines the number of invocations allowed by a mocked method. - - - - - Specifies that a mocked method should be invoked times as minimum. - The minimun number of times.An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked one time as minimum. - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked time as maximun. - The maximun number of times.An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked one time as maximun. - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked between and - times. - The minimun number of times.The maximun number of times. - The kind of range. See . - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked exactly times. - The times that a method or property can be called.An object defining the allowed number of invocations. - - - - Specifies that a mocked method should not be invoked. - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked exactly one time. - An object defining the allowed number of invocations. - - - - Determines whether the specified is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Returns a hash code for this instance. - - A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - - - - - Determines whether two specified objects have the same value. - - The first . - - The second . - - true if the value of left is the same as the value of right; otherwise, false. - - - - - Determines whether two specified objects have different values. - - The first . - - The second . - - true if the value of left is different from the value of right; otherwise, false. - - - - diff --git a/packages/Moq.4.2.1502.0911/lib/net40/Moq.dll b/packages/Moq.4.2.1502.0911/lib/net40/Moq.dll deleted file mode 100644 index b2a728223..000000000 Binary files a/packages/Moq.4.2.1502.0911/lib/net40/Moq.dll and /dev/null differ diff --git a/packages/Moq.4.2.1502.0911/lib/net40/Moq.xml b/packages/Moq.4.2.1502.0911/lib/net40/Moq.xml deleted file mode 100644 index 0431a9e4c..000000000 --- a/packages/Moq.4.2.1502.0911/lib/net40/Moq.xml +++ /dev/null @@ -1,5449 +0,0 @@ - - - - Moq - - - - - Implements the fluent API. - - - - - The expectation will be considered only in the former condition. - - - - - - - The expectation will be considered only in the former condition. - - - - - - - - Setups the get. - - The type of the property. - The expression. - - - - - Setups the set. - - The type of the property. - The setter expression. - - - - - Setups the set. - - The setter expression. - - - - - Handle interception - - the current invocation context - shared data for the interceptor as a whole - shared data among the strategies during a single interception - InterceptionAction.Continue if further interception has to be processed, otherwise InterceptionAction.Stop - - - - Covarient interface for Mock<T> such that casts between IMock<Employee> to IMock<Person> - are possible. Only covers the covariant members of Mock<T>. - - - - - Exposes the mocked object instance. - - - - - Behavior of the mock, according to the value set in the constructor. - - - - - Whether the base member virtual implementation will be called - for mocked classes if no setup is matched. Defaults to . - - - - - Specifies the behavior to use when returning default values for - unexpected invocations on loose mocks. - - - - - Get an eventInfo for a given event name. Search type ancestors depth first if necessary. - - Name of the event, with the set_ or get_ prefix already removed - - - - Get an eventInfo for a given event name. Search type ancestors depth first if necessary. - Searches also in non public events. - - Name of the event, with the set_ or get_ prefix already removed - - - - Given a type return all of its ancestors, both types and interfaces. - - The type to find immediate ancestors of - - - - Defines the Callback verb and overloads. - - - - - Helper interface used to hide the base - members from the fluent API to make it much cleaner - in Visual Studio intellisense. - - - - - - - - - - - - - - - - - Specifies a callback to invoke when the method is called. - - The callback method to invoke. - - The following example specifies a callback to set a boolean - value that can be used later: - - var called = false; - mock.Setup(x => x.Execute()) - .Callback(() => called = true); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The argument type of the invoked method. - The callback method to invoke. - - Invokes the given callback with the concrete invocation argument value. - - Notice how the specific string argument is retrieved by simply declaring - it as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute(It.IsAny<string>())) - .Callback((string command) => Console.WriteLine(command)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2) => Console.WriteLine(arg1 + arg2)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3) => Console.WriteLine(arg1 + arg2 + arg3)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4) => Console.WriteLine(arg1 + arg2 + arg3 + arg4)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The type of the sixteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15, string arg16) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16)); - - - - - - Defines the Callback verb and overloads for callbacks on - setups that return a value. - - Mocked type. - Type of the return value of the setup. - - - - Specifies a callback to invoke when the method is called. - - The callback method to invoke. - - The following example specifies a callback to set a boolean value that can be used later: - - var called = false; - mock.Setup(x => x.Execute()) - .Callback(() => called = true) - .Returns(true); - - Note that in the case of value-returning methods, after the Callback - call you can still specify the return value. - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the argument of the invoked method. - Callback method to invoke. - - Invokes the given callback with the concrete invocation argument value. - - Notice how the specific string argument is retrieved by simply declaring - it as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute(It.IsAny<string>())) - .Callback(command => Console.WriteLine(command)) - .Returns(true); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2) => Console.WriteLine(arg1 + arg2)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3) => Console.WriteLine(arg1 + arg2 + arg3)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4) => Console.WriteLine(arg1 + arg2 + arg3 + arg4)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The type of the sixteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16)); - - - - - - Defines the Raises verb. - - - - - Specifies the event that will be raised - when the setup is met. - - An expression that represents an event attach or detach action. - The event arguments to pass for the raised event. - - The following example shows how to raise an event when - the setup is met: - - var mock = new Mock<IContainer>(); - - mock.Setup(add => add.Add(It.IsAny<string>(), It.IsAny<object>())) - .Raises(add => add.Added += null, EventArgs.Empty); - - - - - - Specifies the event that will be raised - when the setup is matched. - - An expression that represents an event attach or detach action. - A function that will build the - to pass when raising the event. - - - - - Specifies the custom event that will be raised - when the setup is matched. - - An expression that represents an event attach or detach action. - The arguments to pass to the custom delegate (non EventHandler-compatible). - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - The type of the fourteenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - The type of the fourteenth argument received by the expected invocation. - The type of the fifteenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - The type of the fourteenth argument received by the expected invocation. - The type of the fifteenth argument received by the expected invocation. - The type of the sixteenth argument received by the expected invocation. - - - - - Defines the Returns verb. - - Mocked type. - Type of the return value from the expression. - - - - Specifies the value to return. - - The value to return, or . - - Return a true value from the method call: - - mock.Setup(x => x.Execute("ping")) - .Returns(true); - - - - - - Specifies a function that will calculate the value to return from the method. - - The function that will calculate the return value. - - Return a calculated value when the method is called: - - mock.Setup(x => x.Execute("ping")) - .Returns(() => returnValues[0]); - - The lambda expression to retrieve the return value is lazy-executed, - meaning that its value may change depending on the moment the method - is executed and the value the returnValues array has at - that moment. - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the argument of the invoked method. - The function that will calculate the return value. - - Return a calculated value which is evaluated lazily at the time of the invocation. - - The lookup list can change between invocations and the setup - will return different values accordingly. Also, notice how the specific - string argument is retrieved by simply declaring it as part of the lambda - expression: - - - mock.Setup(x => x.Execute(It.IsAny<string>())) - .Returns((string command) => returnValues[command]); - - - - - - Calls the real method of the object and returns its return value. - - The value calculated by the real method of the object. - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2) => arg1 + arg2); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3) => arg1 + arg2 + arg3); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4) => arg1 + arg2 + arg3 + arg4); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5) => arg1 + arg2 + arg3 + arg4 + arg5); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The type of the sixteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15, string arg16) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16); - - - - - - Hook used to tells Castle which methods to proxy in mocked classes. - - Here we proxy the default methods Castle suggests (everything Object's methods) - plus Object.ToString(), so we can give mocks useful default names. - - This is required to allow Moq to mock ToString on proxy *class* implementations. - - - - - Extends AllMethodsHook.ShouldInterceptMethod to also intercept Object.ToString(). - - - - - The base class used for all our interface-inheriting proxies, which overrides the default - Object.ToString() behavior, to route it via the mock by default, unless overriden by a - real implementation. - - This is required to allow Moq to mock ToString on proxy *interface* implementations. - - - This is internal to Moq and should not be generally used. - - Unfortunately it must be public, due to cross-assembly visibility issues with reflection, - see github.com/Moq/moq4/issues/98 for details. - - - - - Overrides the default ToString implementation to instead find the mock for this mock.Object, - and return MockName + '.Object' as the mocked object's ToString, to make it easy to relate - mocks and mock object instances in error messages. - - - - - Defines async extension methods on IReturns. - - - - - Allows to specify the return value of an asynchronous method. - - - - - Allows to specify the exception thrown by an asynchronous method. - - - - - Language for ReturnSequence - - - - - Returns value - - - - - Throws an exception - - - - - Throws an exception - - - - - Calls original method - - - - - The first method call or member access will be the - last segment of the expression (depth-first traversal), - which is the one we have to Setup rather than FluentMock. - And the last one is the one we have to Mock.Get rather - than FluentMock. - - - - - Base class for mocks and static helper class with methods that - apply to mocked objects, such as to - retrieve a from an object instance. - - - - - Creates an mock object of the indicated type. - - The type of the mocked object. - The mocked object created. - - - - Creates an mock object of the indicated type. - - The predicate with the specification of how the mocked object should behave. - The type of the mocked object. - The mocked object created. - - - - Initializes a new instance of the class. - - - - - Retrieves the mock object for the given object instance. - - Type of the mock to retrieve. Can be omitted as it's inferred - from the object instance passed in as the instance. - The instance of the mocked object.The mock associated with the mocked object. - The received instance - was not created by Moq. - - The following example shows how to add a new setup to an object - instance which is not the original but rather - the object associated with it: - - // Typed instance, not the mock, is retrieved from some test API. - HttpContextBase context = GetMockContext(); - - // context.Request is the typed object from the "real" API - // so in order to add a setup to it, we need to get - // the mock that "owns" it - Mock<HttpRequestBase> request = Mock.Get(context.Request); - mock.Setup(req => req.AppRelativeCurrentExecutionFilePath) - .Returns(tempUrl); - - - - - - Returns the mocked object value. - - - - - Verifies that all verifiable expectations have been met. - - This example sets up an expectation and marks it as verifiable. After - the mock is used, a Verify() call is issued on the mock - to ensure the method in the setup was invoked: - - var mock = new Mock<IWarehouse>(); - this.Setup(x => x.HasInventory(TALISKER, 50)).Verifiable().Returns(true); - ... - // other test code - ... - // Will throw if the test code has didn't call HasInventory. - this.Verify(); - - Not all verifiable expectations were met. - - - - Verifies all expectations regardless of whether they have - been flagged as verifiable. - - This example sets up an expectation without marking it as verifiable. After - the mock is used, a call is issued on the mock - to ensure that all expectations are met: - - var mock = new Mock<IWarehouse>(); - this.Setup(x => x.HasInventory(TALISKER, 50)).Returns(true); - ... - // other test code - ... - // Will throw if the test code has didn't call HasInventory, even - // that expectation was not marked as verifiable. - this.VerifyAll(); - - At least one expectation was not met. - - - - Gets the interceptor target for the given expression and root mock, - building the intermediate hierarchy of mock objects if necessary. - - - - - Raises the associated event with the given - event argument data. - - - - - Raises the associated event with the given - event argument data. - - - - - Adds an interface implementation to the mock, - allowing setups to be specified for it. - - This method can only be called before the first use - of the mock property, at which - point the runtime type has already been generated - and no more interfaces can be added to it. - - Also, must be an - interface and not a class, which must be specified - when creating the mock instead. - - - The mock type - has already been generated by accessing the property. - - The specified - is not an interface. - - The following example creates a mock for the main interface - and later adds to it to verify - it's called by the consumer code: - - var mock = new Mock<IProcessor>(); - mock.Setup(x => x.Execute("ping")); - - // add IDisposable interface - var disposable = mock.As<IDisposable>(); - disposable.Setup(d => d.Dispose()).Verifiable(); - - Type of interface to cast the mock to. - - - - - - - Behavior of the mock, according to the value set in the constructor. - - - - - Whether the base member virtual implementation will be called - for mocked classes if no setup is matched. Defaults to . - - - - - Specifies the behavior to use when returning default values for - unexpected invocations on loose mocks. - - - - - Gets the mocked object instance. - - - - - Retrieves the type of the mocked object, its generic type argument. - This is used in the auto-mocking of hierarchy access. - - - - - If this is a mock of a delegate, this property contains the method - on the autogenerated interface so that we can convert setup + verify - expressions on the delegate into expressions on the interface proxy. - - - - - Allows to check whether expression conversion to the - must be performed on the mock, without causing unnecessarily early initialization of - the mock instance, which breaks As{T}. - - - - - Specifies the class that will determine the default - value to return when invocations are made that - have no setups and need to return a default - value (for loose mocks). - - - - - Exposes the list of extra interfaces implemented by the mock. - - - - - Utility repository class to use to construct multiple - mocks when consistent verification is - desired for all of them. - - - If multiple mocks will be created during a test, passing - the desired (if different than the - or the one - passed to the repository constructor) and later verifying each - mock can become repetitive and tedious. - - This repository class helps in that scenario by providing a - simplified creation of multiple mocks with a default - (unless overriden by calling - ) and posterior verification. - - - - The following is a straightforward example on how to - create and automatically verify strict mocks using a : - - var repository = new MockRepository(MockBehavior.Strict); - - var foo = repository.Create<IFoo>(); - var bar = repository.Create<IBar>(); - - // no need to call Verifiable() on the setup - // as we'll be validating all of them anyway. - foo.Setup(f => f.Do()); - bar.Setup(b => b.Redo()); - - // exercise the mocks here - - repository.VerifyAll(); - // At this point all setups are already checked - // and an optional MockException might be thrown. - // Note also that because the mocks are strict, any invocation - // that doesn't have a matching setup will also throw a MockException. - - The following examples shows how to setup the repository - to create loose mocks and later verify only verifiable setups: - - var repository = new MockRepository(MockBehavior.Loose); - - var foo = repository.Create<IFoo>(); - var bar = repository.Create<IBar>(); - - // this setup will be verified when we verify the repository - foo.Setup(f => f.Do()).Verifiable(); - - // this setup will NOT be verified - foo.Setup(f => f.Calculate()); - - // this setup will be verified when we verify the repository - bar.Setup(b => b.Redo()).Verifiable(); - - // exercise the mocks here - // note that because the mocks are Loose, members - // called in the interfaces for which no matching - // setups exist will NOT throw exceptions, - // and will rather return default values. - - repository.Verify(); - // At this point verifiable setups are already checked - // and an optional MockException might be thrown. - - The following examples shows how to setup the repository with a - default strict behavior, overriding that default for a - specific mock: - - var repository = new MockRepository(MockBehavior.Strict); - - // this particular one we want loose - var foo = repository.Create<IFoo>(MockBehavior.Loose); - var bar = repository.Create<IBar>(); - - // specify setups - - // exercise the mocks here - - repository.Verify(); - - - - - - - Utility factory class to use to construct multiple - mocks when consistent verification is - desired for all of them. - - - If multiple mocks will be created during a test, passing - the desired (if different than the - or the one - passed to the factory constructor) and later verifying each - mock can become repetitive and tedious. - - This factory class helps in that scenario by providing a - simplified creation of multiple mocks with a default - (unless overriden by calling - ) and posterior verification. - - - - The following is a straightforward example on how to - create and automatically verify strict mocks using a : - - var factory = new MockFactory(MockBehavior.Strict); - - var foo = factory.Create<IFoo>(); - var bar = factory.Create<IBar>(); - - // no need to call Verifiable() on the setup - // as we'll be validating all of them anyway. - foo.Setup(f => f.Do()); - bar.Setup(b => b.Redo()); - - // exercise the mocks here - - factory.VerifyAll(); - // At this point all setups are already checked - // and an optional MockException might be thrown. - // Note also that because the mocks are strict, any invocation - // that doesn't have a matching setup will also throw a MockException. - - The following examples shows how to setup the factory - to create loose mocks and later verify only verifiable setups: - - var factory = new MockFactory(MockBehavior.Loose); - - var foo = factory.Create<IFoo>(); - var bar = factory.Create<IBar>(); - - // this setup will be verified when we verify the factory - foo.Setup(f => f.Do()).Verifiable(); - - // this setup will NOT be verified - foo.Setup(f => f.Calculate()); - - // this setup will be verified when we verify the factory - bar.Setup(b => b.Redo()).Verifiable(); - - // exercise the mocks here - // note that because the mocks are Loose, members - // called in the interfaces for which no matching - // setups exist will NOT throw exceptions, - // and will rather return default values. - - factory.Verify(); - // At this point verifiable setups are already checked - // and an optional MockException might be thrown. - - The following examples shows how to setup the factory with a - default strict behavior, overriding that default for a - specific mock: - - var factory = new MockFactory(MockBehavior.Strict); - - // this particular one we want loose - var foo = factory.Create<IFoo>(MockBehavior.Loose); - var bar = factory.Create<IBar>(); - - // specify setups - - // exercise the mocks here - - factory.Verify(); - - - - - - - Initializes the factory with the given - for newly created mocks from the factory. - - The behavior to use for mocks created - using the factory method if not overriden - by using the overload. - - - - Creates a new mock with the default - specified at factory construction time. - - Type to mock. - A new . - - - var factory = new MockFactory(MockBehavior.Strict); - - var foo = factory.Create<IFoo>(); - // use mock on tests - - factory.VerifyAll(); - - - - - - Creates a new mock with the default - specified at factory construction time and with the - the given constructor arguments for the class. - - - The mock will try to find the best match constructor given the - constructor arguments, and invoke that to initialize the instance. - This applies only to classes, not interfaces. - - Type to mock. - Constructor arguments for mocked classes. - A new . - - - var factory = new MockFactory(MockBehavior.Default); - - var mock = factory.Create<MyBase>("Foo", 25, true); - // use mock on tests - - factory.Verify(); - - - - - - Creates a new mock with the given . - - Type to mock. - Behavior to use for the mock, which overrides - the default behavior specified at factory construction time. - A new . - - The following example shows how to create a mock with a different - behavior to that specified as the default for the factory: - - var factory = new MockFactory(MockBehavior.Strict); - - var foo = factory.Create<IFoo>(MockBehavior.Loose); - - - - - - Creates a new mock with the given - and with the the given constructor arguments for the class. - - - The mock will try to find the best match constructor given the - constructor arguments, and invoke that to initialize the instance. - This applies only to classes, not interfaces. - - Type to mock. - Behavior to use for the mock, which overrides - the default behavior specified at factory construction time. - Constructor arguments for mocked classes. - A new . - - The following example shows how to create a mock with a different - behavior to that specified as the default for the factory, passing - constructor arguments: - - var factory = new MockFactory(MockBehavior.Default); - - var mock = factory.Create<MyBase>(MockBehavior.Strict, "Foo", 25, true); - - - - - - Implements creation of a new mock within the factory. - - Type to mock. - The behavior for the new mock. - Optional arguments for the construction of the mock. - - - - Verifies all verifiable expectations on all mocks created - by this factory. - - - One or more mocks had expectations that were not satisfied. - - - - Verifies all verifiable expectations on all mocks created - by this factory. - - - One or more mocks had expectations that were not satisfied. - - - - Invokes for each mock - in , and accumulates the resulting - that might be - thrown from the action. - - The action to execute against - each mock. - - - - Whether the base member virtual implementation will be called - for mocked classes if no setup is matched. Defaults to . - - - - - Specifies the behavior to use when returning default values for - unexpected invocations on loose mocks. - - - - - Gets the mocks that have been created by this factory and - that will get verified together. - - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The type of the mocked object to query. - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The predicate with the setup expressions. - The type of the mocked object to query. - - - - Creates an mock object of the indicated type. - - The type of the mocked object. - The mocked object created. - - - - Creates an mock object of the indicated type. - - The predicate with the setup expressions. - The type of the mocked object. - The mocked object created. - - - - Creates the mock query with the underlying queriable implementation. - - - - - Wraps the enumerator inside a queryable. - - - - - Method that is turned into the actual call from .Query{T}, to - transform the queryable query into a normal enumerable query. - This method is never used directly by consumers. - - - - - Initializes the repository with the given - for newly created mocks from the repository. - - The behavior to use for mocks created - using the repository method if not overriden - by using the overload. - - - - A that returns an empty default value - for invocations that do not have setups or return values, with loose mocks. - This is the default behavior for a mock. - - - - - Interface to be implemented by classes that determine the - default value of non-expected invocations. - - - - - Defines the default value to return in all the methods returning . - The type of the return value.The value to set as default. - - - - Provides a value for the given member and arguments. - - The member to provide a default value for. - - - - - The intention of is to create a more readable - string representation for the failure message. - - - - - Implements the fluent API. - - - - - Defines the Throws verb. - - - - - Specifies the exception to throw when the method is invoked. - - Exception instance to throw. - - This example shows how to throw an exception when the method is - invoked with an empty string argument: - - mock.Setup(x => x.Execute("")) - .Throws(new ArgumentException()); - - - - - - Specifies the type of exception to throw when the method is invoked. - - Type of exception to instantiate and throw when the setup is matched. - - This example shows how to throw an exception when the method is - invoked with an empty string argument: - - mock.Setup(x => x.Execute("")) - .Throws<ArgumentException>(); - - - - - - Implements the fluent API. - - - - - Defines occurrence members to constraint setups. - - - - - The expected invocation can happen at most once. - - - - var mock = new Mock<ICommand>(); - mock.Setup(foo => foo.Execute("ping")) - .AtMostOnce(); - - - - - - The expected invocation can happen at most specified number of times. - - The number of times to accept calls. - - - var mock = new Mock<ICommand>(); - mock.Setup(foo => foo.Execute("ping")) - .AtMost( 5 ); - - - - - - Defines the Verifiable verb. - - - - - Marks the expectation as verifiable, meaning that a call - to will check if this particular - expectation was met. - - - The following example marks the expectation as verifiable: - - mock.Expect(x => x.Execute("ping")) - .Returns(true) - .Verifiable(); - - - - - - Marks the expectation as verifiable, meaning that a call - to will check if this particular - expectation was met, and specifies a message for failures. - - - The following example marks the expectation as verifiable: - - mock.Expect(x => x.Execute("ping")) - .Returns(true) - .Verifiable("Ping should be executed always!"); - - - - - - Implements the fluent API. - - - - - We need this non-generics base class so that - we can use from - generic code. - - - - - Implements the fluent API. - - - - - Implements the fluent API. - - - - - Implements the fluent API. - - - - - Defines the Callback verb for property getter setups. - - - Mocked type. - Type of the property. - - - - Specifies a callback to invoke when the property is retrieved. - - Callback method to invoke. - - Invokes the given callback with the property value being set. - - mock.SetupGet(x => x.Suspended) - .Callback(() => called = true) - .Returns(true); - - - - - - Implements the fluent API. - - - - - Defines the Returns verb for property get setups. - - Mocked type. - Type of the property. - - - - Specifies the value to return. - - The value to return, or . - - Return a true value from the property getter call: - - mock.SetupGet(x => x.Suspended) - .Returns(true); - - - - - - Specifies a function that will calculate the value to return for the property. - - The function that will calculate the return value. - - Return a calculated value when the property is retrieved: - - mock.SetupGet(x => x.Suspended) - .Returns(() => returnValues[0]); - - The lambda expression to retrieve the return value is lazy-executed, - meaning that its value may change depending on the moment the property - is retrieved and the value the returnValues array has at - that moment. - - - - - Calls the real property of the object and returns its return value. - - The value calculated by the real property of the object. - - - - Implements the fluent API. - - - - - Provides additional methods on mocks. - - - Those methods are useful for Testeroids support. - - - - - Resets the calls previously made on the specified mock. - - The mock whose calls need to be reset. - - - - Helper class to setup a full trace between many mocks - - - - - Initialize a trace setup - - - - - Allow sequence to be repeated - - - - - define nice api - - - - - Perform an expectation in the trace. - - - - - Marks a method as a matcher, which allows complete replacement - of the built-in class with your own argument - matching rules. - - - This feature has been deprecated in favor of the new - and simpler . - - - The argument matching is used to determine whether a concrete - invocation in the mock matches a given setup. This - matching mechanism is fully extensible. - - - There are two parts of a matcher: the compiler matcher - and the runtime matcher. - - - Compiler matcher - Used to satisfy the compiler requirements for the - argument. Needs to be a method optionally receiving any arguments - you might need for the matching, but with a return type that - matches that of the argument. - - Let's say I want to match a lists of orders that contains - a particular one. I might create a compiler matcher like the following: - - - public static class Orders - { - [Matcher] - public static IEnumerable<Order> Contains(Order order) - { - return null; - } - } - - Now we can invoke this static method instead of an argument in an - invocation: - - var order = new Order { ... }; - var mock = new Mock<IRepository<Order>>(); - - mock.Setup(x => x.Save(Orders.Contains(order))) - .Throws<ArgumentException>(); - - Note that the return value from the compiler matcher is irrelevant. - This method will never be called, and is just used to satisfy the - compiler and to signal Moq that this is not a method that we want - to be invoked at runtime. - - - - Runtime matcher - - The runtime matcher is the one that will actually perform evaluation - when the test is run, and is defined by convention to have the - same signature as the compiler matcher, but where the return - value is the first argument to the call, which contains the - object received by the actual invocation at runtime: - - public static bool Contains(IEnumerable<Order> orders, Order order) - { - return orders.Contains(order); - } - - At runtime, the mocked method will be invoked with a specific - list of orders. This value will be passed to this runtime - matcher as the first argument, while the second argument is the - one specified in the setup (x.Save(Orders.Contains(order))). - - The boolean returned determines whether the given argument has been - matched. If all arguments to the expected method are matched, then - the setup matches and is evaluated. - - - - - - Using this extensible infrastructure, you can easily replace the entire - set of matchers with your own. You can also avoid the - typical (and annoying) lengthy expressions that result when you have - multiple arguments that use generics. - - - The following is the complete example explained above: - - public static class Orders - { - [Matcher] - public static IEnumerable<Order> Contains(Order order) - { - return null; - } - - public static bool Contains(IEnumerable<Order> orders, Order order) - { - return orders.Contains(order); - } - } - - And the concrete test using this matcher: - - var order = new Order { ... }; - var mock = new Mock<IRepository<Order>>(); - - mock.Setup(x => x.Save(Orders.Contains(order))) - .Throws<ArgumentException>(); - - // use mock, invoke Save, and have the matcher filter. - - - - - - Provides a mock implementation of . - - Any interface type can be used for mocking, but for classes, only abstract and virtual members can be mocked. - - The behavior of the mock with regards to the setups and the actual calls is determined - by the optional that can be passed to the - constructor. - - Type to mock, which can be an interface or a class. - The following example shows establishing setups with specific values - for method invocations: - - // Arrange - var order = new Order(TALISKER, 50); - var mock = new Mock<IWarehouse>(); - - mock.Setup(x => x.HasInventory(TALISKER, 50)).Returns(true); - - // Act - order.Fill(mock.Object); - - // Assert - Assert.True(order.IsFilled); - - The following example shows how to use the class - to specify conditions for arguments instead of specific values: - - // Arrange - var order = new Order(TALISKER, 50); - var mock = new Mock<IWarehouse>(); - - // shows how to expect a value within a range - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsInRange(0, 100, Range.Inclusive))) - .Returns(false); - - // shows how to throw for unexpected calls. - mock.Setup(x => x.Remove( - It.IsAny<string>(), - It.IsAny<int>())) - .Throws(new InvalidOperationException()); - - // Act - order.Fill(mock.Object); - - // Assert - Assert.False(order.IsFilled); - - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Ctor invoked by AsTInterface exclusively. - - - - - Initializes an instance of the mock with default behavior. - - var mock = new Mock<IFormatProvider>(); - - - - - Initializes an instance of the mock with default behavior and with - the given constructor arguments for the class. (Only valid when is a class) - - The mock will try to find the best match constructor given the constructor arguments, and invoke that - to initialize the instance. This applies only for classes, not interfaces. - - var mock = new Mock<MyProvider>(someArgument, 25); - Optional constructor arguments if the mocked type is a class. - - - - Initializes an instance of the mock with the specified behavior. - - var mock = new Mock<IFormatProvider>(MockBehavior.Relaxed); - Behavior of the mock. - - - - Initializes an instance of the mock with a specific behavior with - the given constructor arguments for the class. - - The mock will try to find the best match constructor given the constructor arguments, and invoke that - to initialize the instance. This applies only to classes, not interfaces. - - var mock = new Mock<MyProvider>(someArgument, 25); - Behavior of the mock.Optional constructor arguments if the mocked type is a class. - - - - Returns the name of the mock - - - - - Returns the mocked object value. - - - - - Specifies a setup on the mocked type for a call to - to a void method. - - If more than one setup is specified for the same method or property, - the latest one wins and is the one that will be executed. - Lambda expression that specifies the expected method invocation. - - var mock = new Mock<IProcessor>(); - mock.Setup(x => x.Execute("ping")); - - - - - - Specifies a setup on the mocked type for a call to - to a value returning method. - Type of the return value. Typically omitted as it can be inferred from the expression. - If more than one setup is specified for the same method or property, - the latest one wins and is the one that will be executed. - Lambda expression that specifies the method invocation. - - mock.Setup(x => x.HasInventory("Talisker", 50)).Returns(true); - - - - - - Specifies a setup on the mocked type for a call to - to a property getter. - - If more than one setup is set for the same property getter, - the latest one wins and is the one that will be executed. - Type of the property. Typically omitted as it can be inferred from the expression.Lambda expression that specifies the property getter. - - mock.SetupGet(x => x.Suspended) - .Returns(true); - - - - - - Specifies a setup on the mocked type for a call to - to a property setter. - - If more than one setup is set for the same property setter, - the latest one wins and is the one that will be executed. - - This overloads allows the use of a callback already - typed for the property type. - - Type of the property. Typically omitted as it can be inferred from the expression.The Lambda expression that sets a property to a value. - - mock.SetupSet(x => x.Suspended = true); - - - - - - Specifies a setup on the mocked type for a call to - to a property setter. - - If more than one setup is set for the same property setter, - the latest one wins and is the one that will be executed. - Lambda expression that sets a property to a value. - - mock.SetupSet(x => x.Suspended = true); - - - - - - Specifies that the given property should have "property behavior", - meaning that setting its value will cause it to be saved and - later returned when the property is requested. (this is also - known as "stubbing"). - - Type of the property, inferred from the property - expression (does not need to be specified). - Property expression to stub. - If you have an interface with an int property Value, you might - stub it using the following straightforward call: - - var mock = new Mock<IHaveValue>(); - mock.Stub(v => v.Value); - - After the Stub call has been issued, setting and - retrieving the object value will behave as expected: - - IHaveValue v = mock.Object; - - v.Value = 5; - Assert.Equal(5, v.Value); - - - - - - Specifies that the given property should have "property behavior", - meaning that setting its value will cause it to be saved and - later returned when the property is requested. This overload - allows setting the initial value for the property. (this is also - known as "stubbing"). - - Type of the property, inferred from the property - expression (does not need to be specified). - Property expression to stub.Initial value for the property. - If you have an interface with an int property Value, you might - stub it using the following straightforward call: - - var mock = new Mock<IHaveValue>(); - mock.SetupProperty(v => v.Value, 5); - - After the SetupProperty call has been issued, setting and - retrieving the object value will behave as expected: - - IHaveValue v = mock.Object; - // Initial value was stored - Assert.Equal(5, v.Value); - - // New value set which changes the initial value - v.Value = 6; - Assert.Equal(6, v.Value); - - - - - - Specifies that the all properties on the mock should have "property behavior", - meaning that setting its value will cause it to be saved and - later returned when the property is requested. (this is also - known as "stubbing"). The default value for each property will be the - one generated as specified by the property for the mock. - - If the mock is set to , - the mocked default values will also get all properties setup recursively. - - - - - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - This example assumes that the mock has been used, and later we want to verify that a given - invocation with specific parameters was performed: - - var mock = new Mock<IProcessor>(); - // exercise mock - //... - // Will throw if the test code didn't call Execute with a "ping" string argument. - mock.Verify(proc => proc.Execute("ping")); - - The invocation was not performed on the mock.Expression to verify. - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called. - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called. - - - - Verifies that a specific invocation matching the given expression was performed on the mock, - specifying a failure error message. Use in conjuntion with the default - . - - This example assumes that the mock has been used, and later we want to verify that a given - invocation with specific parameters was performed: - - var mock = new Mock<IProcessor>(); - // exercise mock - //... - // Will throw if the test code didn't call Execute with a "ping" string argument. - mock.Verify(proc => proc.Execute("ping")); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails. - - - - Verifies that a specific invocation matching the given expression was performed on the mock, - specifying a failure error message. Use in conjuntion with the default - . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Message to show if verification fails. - - - - Verifies that a specific invocation matching the given expression was performed on the mock, - specifying a failure error message. Use in conjuntion with the default - . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Message to show if verification fails. - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - This example assumes that the mock has been used, and later we want to verify that a given - invocation with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't call HasInventory. - mock.Verify(warehouse => warehouse.HasInventory(TALISKER, 50)); - - The invocation was not performed on the mock.Expression to verify.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock. Use in conjuntion - with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock. Use in conjuntion - with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock, specifying a failure - error message. - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't call HasInventory. - mock.Verify(warehouse => warehouse.HasInventory(TALISKER, 50), "When filling orders, inventory has to be checked"); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock, specifying a failure - error message. - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Message to show if verification fails.Type of return value from the expression. - - - - Verifies that a property was read on the mock. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was retrieved from it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't retrieve the IsClosed property. - mock.VerifyGet(warehouse => warehouse.IsClosed); - - The invocation was not performed on the mock.Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock, specifying a failure - error message. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was retrieved from it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't retrieve the IsClosed property. - mock.VerifyGet(warehouse => warehouse.IsClosed); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock, specifying a failure - error message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock, specifying a failure - error message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was set on the mock. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was set on it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed = true); - - The invocation was not performed on the mock.Expression to verify. - - - - Verifies that a property was set on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - - - - Verifies that a property was set on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - - - - Verifies that a property was set on the mock, specifying - a failure message. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was set on it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed = true, "Warehouse should always be closed after the action"); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails. - - - - Verifies that a property was set on the mock, specifying - a failure message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - - - - Verifies that a property was set on the mock, specifying - a failure message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - - - - Raises the event referenced in using - the given argument. - - The argument is - invalid for the target event invocation, or the is - not an event attach or detach expression. - - The following example shows how to raise a event: - - var mock = new Mock<IViewModel>(); - - mock.Raise(x => x.PropertyChanged -= null, new PropertyChangedEventArgs("Name")); - - - This example shows how to invoke an event with a custom event arguments - class in a view that will cause its corresponding presenter to - react by changing its state: - - var mockView = new Mock<IOrdersView>(); - var presenter = new OrdersPresenter(mockView.Object); - - // Check that the presenter has no selection by default - Assert.Null(presenter.SelectedOrder); - - // Raise the event with a specific arguments data - mockView.Raise(v => v.SelectionChanged += null, new OrderEventArgs { Order = new Order("moq", 500) }); - - // Now the presenter reacted to the event, and we have a selected order - Assert.NotNull(presenter.SelectedOrder); - Assert.Equal("moq", presenter.SelectedOrder.ProductName); - - - - - - Raises the event referenced in using - the given argument for a non-EventHandler typed event. - - The arguments are - invalid for the target event invocation, or the is - not an event attach or detach expression. - - The following example shows how to raise a custom event that does not adhere to - the standard EventHandler: - - var mock = new Mock<IViewModel>(); - - mock.Raise(x => x.MyEvent -= null, "Name", bool, 25); - - - - - - Exposes the mocked object instance. - - - - - Allows naming of your mocks, so they can be easily identified in error messages (e.g. from failed assertions). - - - - - - - - Provides legacy API members as extensions so that - existing code continues to compile, but new code - doesn't see then. - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Provides additional methods on mocks. - - - Provided as extension methods as they confuse the compiler - with the overloads taking Action. - - - - - Specifies a setup on the mocked type for a call to - to a property setter, regardless of its value. - - - If more than one setup is set for the same property setter, - the latest one wins and is the one that will be executed. - - Type of the property. Typically omitted as it can be inferred from the expression. - Type of the mock. - The target mock for the setup. - Lambda expression that specifies the property setter. - - - mock.SetupSet(x => x.Suspended); - - - - This method is not legacy, but must be on an extension method to avoid - confusing the compiler with the new Action syntax. - - - - - Verifies that a property has been set on the mock, regarless of its value. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - Expression to verify. - The mock instance. - Mocked type. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Verifies that a property has been set on the mock, specifying a failure - error message. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - Expression to verify. - Message to show if verification fails. - The mock instance. - Mocked type. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Verifies that a property has been set on the mock, regardless - of the value but only the specified number of times. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - The invocation was not call the times specified by - . - The mock instance. - Mocked type. - The number of times a method is allowed to be called. - Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Verifies that a property has been set on the mock, regardless - of the value but only the specified number of times, and specifying a failure - error message. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - The invocation was not call the times specified by - . - The mock instance. - Mocked type. - The number of times a method is allowed to be called. - Message to show if verification fails. - Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Helper for sequencing return values in the same method. - - - - - Return a sequence of values, once per call. - - - - - Casts the expression to a lambda expression, removing - a cast if there's any. - - - - - Casts the body of the lambda expression to a . - - If the body is not a method call. - - - - Converts the body of the lambda expression into the referenced by it. - - - - - Checks whether the body of the lambda expression is a property access. - - - - - Checks whether the expression is a property access. - - - - - Checks whether the body of the lambda expression is a property indexer, which is true - when the expression is an whose - has - equal to . - - - - - Checks whether the expression is a property indexer, which is true - when the expression is an whose - has - equal to . - - - - - Creates an expression that casts the given expression to the - type. - - - - - TODO: remove this code when https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=331583 - is fixed. - - - - - Extracts, into a common form, information from a - around either a (for a normal method call) - or a (for a delegate invocation). - - - - - Tests if a type is a delegate type (subclasses ). - - - - - Provides partial evaluation of subtrees, whenever they can be evaluated locally. - - Matt Warren: http://blogs.msdn.com/mattwar - Documented by InSTEDD: http://www.instedd.org - - - - Performs evaluation and replacement of independent sub-trees - - The root of the expression tree. - A function that decides whether a given expression - node can be part of the local function. - A new tree with sub-trees evaluated and replaced. - - - - Performs evaluation and replacement of independent sub-trees - - The root of the expression tree. - A new tree with sub-trees evaluated and replaced. - - - - Evaluates and replaces sub-trees when first candidate is reached (top-down) - - - - - Performs bottom-up analysis to determine which nodes can possibly - be part of an evaluated sub-tree. - - - - - Ensures the given is not null. - Throws otherwise. - - - - - Ensures the given string is not null or empty. - Throws in the first case, or - in the latter. - - - - - Checks an argument to ensure it is in the specified range including the edges. - - Type of the argument to check, it must be an type. - - The expression containing the name of the argument. - The argument value to check. - The minimun allowed value for the argument. - The maximun allowed value for the argument. - - - - Checks an argument to ensure it is in the specified range excluding the edges. - - Type of the argument to check, it must be an type. - - The expression containing the name of the argument. - The argument value to check. - The minimun allowed value for the argument. - The maximun allowed value for the argument. - - - - Implemented by all generated mock object instances. - - - - - Implemented by all generated mock object instances. - - - - - Reference the Mock that contains this as the mock.Object value. - - - - - Reference the Mock that contains this as the mock.Object value. - - - - - Implements the actual interception and method invocation for - all mocks. - - - - - Implements the fluent API. - - - - - Defines the Callback verb for property setter setups. - - Type of the property. - - - - Specifies a callback to invoke when the property is set that receives the - property value being set. - - Callback method to invoke. - - Invokes the given callback with the property value being set. - - mock.SetupSet(x => x.Suspended) - .Callback((bool state) => Console.WriteLine(state)); - - - - - - Allows the specification of a matching condition for an - argument in a method invocation, rather than a specific - argument value. "It" refers to the argument being matched. - - This class allows the setup to match a method invocation - with an arbitrary value, with a value in a specified range, or - even one that matches a given predicate. - - - - - Matches any value of the given type. - - Typically used when the actual argument value for a method - call is not relevant. - - - // Throws an exception for a call to Remove with any string value. - mock.Setup(x => x.Remove(It.IsAny<string>())).Throws(new InvalidOperationException()); - - Type of the value. - - - - Matches any value of the given type, except null. - Type of the value. - - - - Matches any value that satisfies the given predicate. - Type of the argument to check.The predicate used to match the method argument. - Allows the specification of a predicate to perform matching - of method call arguments. - - This example shows how to return the value 1 whenever the argument to the - Do method is an even number. - - mock.Setup(x => x.Do(It.Is<int>(i => i % 2 == 0))) - .Returns(1); - - This example shows how to throw an exception if the argument to the - method is a negative number: - - mock.Setup(x => x.GetUser(It.Is<int>(i => i < 0))) - .Throws(new ArgumentException()); - - - - - - Matches any value that is in the range specified. - Type of the argument to check.The lower bound of the range.The upper bound of the range. - The kind of range. See . - - The following example shows how to expect a method call - with an integer argument within the 0..100 range. - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsInRange(0, 100, Range.Inclusive))) - .Returns(false); - - - - - - Matches any value that is present in the sequence specified. - Type of the argument to check.The sequence of possible values. - The following example shows how to expect a method call - with an integer argument with value from a list. - - var values = new List<int> { 1, 2, 3 }; - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsIn(values))) - .Returns(false); - - - - - - Matches any value that is present in the sequence specified. - Type of the argument to check.The sequence of possible values. - The following example shows how to expect a method call - with an integer argument with a value of 1, 2, or 3. - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsIn(1, 2, 3))) - .Returns(false); - - - - - - Matches any value that is not found in the sequence specified. - Type of the argument to check.The sequence of disallowed values. - The following example shows how to expect a method call - with an integer argument with value not found from a list. - - var values = new List<int> { 1, 2, 3 }; - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsNotIn(values))) - .Returns(false); - - - - - - Matches any value that is not found in the sequence specified. - Type of the argument to check.The sequence of disallowed values. - The following example shows how to expect a method call - with an integer argument of any value except 1, 2, or 3. - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsNotIn(1, 2, 3))) - .Returns(false); - - - - - - Matches a string argument if it matches the given regular expression pattern. - The pattern to use to match the string argument value. - The following example shows how to expect a call to a method where the - string argument matches the given regular expression: - - mock.Setup(x => x.Check(It.IsRegex("[a-z]+"))).Returns(1); - - - - - - Matches a string argument if it matches the given regular expression pattern. - The pattern to use to match the string argument value.The options used to interpret the pattern. - The following example shows how to expect a call to a method where the - string argument matches the given regular expression, in a case insensitive way: - - mock.Setup(x => x.Check(It.IsRegex("[a-z]+", RegexOptions.IgnoreCase))).Returns(1); - - - - - - Matcher to treat static functions as matchers. - - mock.Setup(x => x.StringMethod(A.MagicString())); - - public static class A - { - [Matcher] - public static string MagicString() { return null; } - public static bool MagicString(string arg) - { - return arg == "magic"; - } - } - - Will succeed if: mock.Object.StringMethod("magic"); - and fail with any other call. - - - - - Options to customize the behavior of the mock. - - - - - Causes the mock to always throw - an exception for invocations that don't have a - corresponding setup. - - - - - Will never throw exceptions, returning default - values when necessary (null for reference types, - zero for value types or empty enumerables and arrays). - - - - - Default mock behavior, which equals . - - - - - Exception thrown by mocks when setups are not matched, - the mock is not properly setup, etc. - - - A distinct exception type is provided so that exceptions - thrown by the mock can be differentiated in tests that - expect other exceptions to be thrown (i.e. ArgumentException). - - Richer exception hierarchy/types are not provided as - tests typically should not catch or expect exceptions - from the mocks. These are typically the result of changes - in the tested class or its collaborators implementation, and - result in fixes in the mock setup so that they dissapear and - allow the test to pass. - - - - - - Supports the serialization infrastructure. - - Serialization information. - Streaming context. - - - - Supports the serialization infrastructure. - - Serialization information. - Streaming context. - - - - Indicates whether this exception is a verification fault raised by Verify() - - - - - Made internal as it's of no use for - consumers, but it's important for - our own tests. - - - - - Used by the mock factory to accumulate verification - failures. - - - - - Supports the serialization infrastructure. - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Mock type has already been initialized by accessing its Object property. Adding interfaces must be done before that.. - - - - - Looks up a localized string similar to Value cannot be an empty string.. - - - - - Looks up a localized string similar to Can only add interfaces to the mock.. - - - - - Looks up a localized string similar to Can't set return value for void method {0}.. - - - - - Looks up a localized string similar to Constructor arguments cannot be passed for delegate mocks.. - - - - - Looks up a localized string similar to Constructor arguments cannot be passed for interface mocks.. - - - - - Looks up a localized string similar to A matching constructor for the given arguments was not found on the mocked type.. - - - - - Looks up a localized string similar to Could not locate event for attach or detach method {0}.. - - - - - Looks up a localized string similar to Expression {0} involves a field access, which is not supported. Use properties instead.. - - - - - Looks up a localized string similar to Type to mock must be an interface or an abstract or non-sealed class. . - - - - - Looks up a localized string similar to Cannot retrieve a mock with the given object type {0} as it's not the main type of the mock or any of its additional interfaces. - Please cast the argument to one of the supported types: {1}. - Remember that there's no generics covariance in the CLR, so your object must be one of these types in order for the call to succeed.. - - - - - Looks up a localized string similar to The equals ("==" or "=" in VB) and the conditional 'and' ("&&" or "AndAlso" in VB) operators are the only ones supported in the query specification expression. Unsupported expression: {0}. - - - - - Looks up a localized string similar to LINQ method '{0}' not supported.. - - - - - Looks up a localized string similar to Expression contains a call to a method which is not virtual (overridable in VB) or abstract. Unsupported expression: {0}. - - - - - Looks up a localized string similar to Member {0}.{1} does not exist.. - - - - - Looks up a localized string similar to Method {0}.{1} is public. Use strong-typed Expect overload instead: - mock.Setup(x => x.{1}()); - . - - - - - Looks up a localized string similar to {0} invocation failed with mock behavior {1}. - {2}. - - - - - Looks up a localized string similar to Expected only {0} calls to {1}.. - - - - - Looks up a localized string similar to Expected only one call to {0}.. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at least {2} times, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at least once, but was never performed: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at most {3} times, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at most once, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock between {2} and {3} times (Exclusive), but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock between {2} and {3} times (Inclusive), but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock exactly {2} times, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock should never have been performed, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock once, but was {4} times: {1}. - - - - - Looks up a localized string similar to All invocations on the mock must have a corresponding setup.. - - - - - Looks up a localized string similar to Object instance was not created by Moq.. - - - - - Looks up a localized string similar to Out expression must evaluate to a constant value.. - - - - - Looks up a localized string similar to Property {0}.{1} does not have a getter.. - - - - - Looks up a localized string similar to Property {0}.{1} does not exist.. - - - - - Looks up a localized string similar to Property {0}.{1} is write-only.. - - - - - Looks up a localized string similar to Property {0}.{1} is read-only.. - - - - - Looks up a localized string similar to Property {0}.{1} does not have a setter.. - - - - - Looks up a localized string similar to Cannot raise a mocked event unless it has been associated (attached) to a concrete event in a mocked object.. - - - - - Looks up a localized string similar to Ref expression must evaluate to a constant value.. - - - - - Looks up a localized string similar to Invocation needs to return a value and therefore must have a corresponding setup that provides it.. - - - - - Looks up a localized string similar to A lambda expression is expected as the argument to It.Is<T>.. - - - - - Looks up a localized string similar to Invocation {0} should not have been made.. - - - - - Looks up a localized string similar to Expression is not a method invocation: {0}. - - - - - Looks up a localized string similar to Expression is not a property access: {0}. - - - - - Looks up a localized string similar to Expression is not a property setter invocation.. - - - - - Looks up a localized string similar to Expression references a method that does not belong to the mocked object: {0}. - - - - - Looks up a localized string similar to Invalid setup on a non-virtual (overridable in VB) member: {0}. - - - - - Looks up a localized string similar to Type {0} does not implement required interface {1}. - - - - - Looks up a localized string similar to Type {0} does not from required type {1}. - - - - - Looks up a localized string similar to To specify a setup for public property {0}.{1}, use the typed overloads, such as: - mock.Setup(x => x.{1}).Returns(value); - mock.SetupGet(x => x.{1}).Returns(value); //equivalent to previous one - mock.SetupSet(x => x.{1}).Callback(callbackDelegate); - . - - - - - Looks up a localized string similar to Unsupported expression: {0}. - - - - - Looks up a localized string similar to Only property accesses are supported in intermediate invocations on a setup. Unsupported expression {0}.. - - - - - Looks up a localized string similar to Expression contains intermediate property access {0}.{1} which is of type {2} and cannot be mocked. Unsupported expression {3}.. - - - - - Looks up a localized string similar to Setter expression cannot use argument matchers that receive parameters.. - - - - - Looks up a localized string similar to Member {0} is not supported for protected mocking.. - - - - - Looks up a localized string similar to Setter expression can only use static custom matchers.. - - - - - Looks up a localized string similar to The following setups were not matched: - {0}. - - - - - Looks up a localized string similar to Invalid verify on a non-virtual (overridable in VB) member: {0}. - - - - - Allows setups to be specified for protected members by using their - name as a string, rather than strong-typing them which is not possible - due to their visibility. - - - - - Specifies a setup for a void method invocation with the given - , optionally specifying arguments for the method call. - - The name of the void method to be invoked. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - - - - Specifies a setup for an invocation on a property or a non void method with the given - , optionally specifying arguments for the method call. - - The name of the method or property to be invoked. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - The return type of the method or property. - - - - Specifies a setup for an invocation on a property getter with the given - . - - The name of the property. - The type of the property. - - - - Specifies a setup for an invocation on a property setter with the given - . - - The name of the property. - The property value. If argument matchers are used, - remember to use rather than . - The type of the property. - - - - Specifies a verify for a void method with the given , - optionally specifying arguments for the method call. Use in conjuntion with the default - . - - The invocation was not call the times specified by - . - The name of the void method to be verified. - The number of times a method is allowed to be called. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - - - - Specifies a verify for an invocation on a property or a non void method with the given - , optionally specifying arguments for the method call. - - The invocation was not call the times specified by - . - The name of the method or property to be invoked. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - The number of times a method is allowed to be called. - The type of return value from the expression. - - - - Specifies a verify for an invocation on a property getter with the given - . - The invocation was not call the times specified by - . - - The name of the property. - The number of times a method is allowed to be called. - The type of the property. - - - - Specifies a setup for an invocation on a property setter with the given - . - - The invocation was not call the times specified by - . - The name of the property. - The number of times a method is allowed to be called. - The property value. - The type of the property. If argument matchers are used, - remember to use rather than . - - - - Allows the specification of a matching condition for an - argument in a protected member setup, rather than a specific - argument value. "ItExpr" refers to the argument being matched. - - - Use this variant of argument matching instead of - for protected setups. - This class allows the setup to match a method invocation - with an arbitrary value, with a value in a specified range, or - even one that matches a given predicate, or null. - - - - - Matches a null value of the given type. - - - Required for protected mocks as the null value cannot be used - directly as it prevents proper method overload selection. - - - - // Throws an exception for a call to Remove with a null string value. - mock.Protected() - .Setup("Remove", ItExpr.IsNull<string>()) - .Throws(new InvalidOperationException()); - - - Type of the value. - - - - Matches any value of the given type. - - - Typically used when the actual argument value for a method - call is not relevant. - - - - // Throws an exception for a call to Remove with any string value. - mock.Protected() - .Setup("Remove", ItExpr.IsAny<string>()) - .Throws(new InvalidOperationException()); - - - Type of the value. - - - - Matches any value that satisfies the given predicate. - - Type of the argument to check. - The predicate used to match the method argument. - - Allows the specification of a predicate to perform matching - of method call arguments. - - - This example shows how to return the value 1 whenever the argument to the - Do method is an even number. - - mock.Protected() - .Setup("Do", ItExpr.Is<int>(i => i % 2 == 0)) - .Returns(1); - - This example shows how to throw an exception if the argument to the - method is a negative number: - - mock.Protected() - .Setup("GetUser", ItExpr.Is<int>(i => i < 0)) - .Throws(new ArgumentException()); - - - - - - Matches any value that is in the range specified. - - Type of the argument to check. - The lower bound of the range. - The upper bound of the range. - The kind of range. See . - - The following example shows how to expect a method call - with an integer argument within the 0..100 range. - - mock.Protected() - .Setup("HasInventory", - ItExpr.IsAny<string>(), - ItExpr.IsInRange(0, 100, Range.Inclusive)) - .Returns(false); - - - - - - Matches a string argument if it matches the given regular expression pattern. - - The pattern to use to match the string argument value. - - The following example shows how to expect a call to a method where the - string argument matches the given regular expression: - - mock.Protected() - .Setup("Check", ItExpr.IsRegex("[a-z]+")) - .Returns(1); - - - - - - Matches a string argument if it matches the given regular expression pattern. - - The pattern to use to match the string argument value. - The options used to interpret the pattern. - - The following example shows how to expect a call to a method where the - string argument matches the given regular expression, in a case insensitive way: - - mock.Protected() - .Setup("Check", ItExpr.IsRegex("[a-z]+", RegexOptions.IgnoreCase)) - .Returns(1); - - - - - - Enables the Protected() method on , - allowing setups to be set for protected members by using their - name as a string, rather than strong-typing them which is not possible - due to their visibility. - - - - - Enable protected setups for the mock. - - Mocked object type. Typically omitted as it can be inferred from the mock instance. - The mock to set the protected setups on. - - - - - - - - - - - - Kind of range to use in a filter specified through - . - - - - - The range includes the to and - from values. - - - - - The range does not include the to and - from values. - - - - - Determines the way default values are generated - calculated for loose mocks. - - - - - Default behavior, which generates empty values for - value types (i.e. default(int)), empty array and - enumerables, and nulls for all other reference types. - - - - - Whenever the default value generated by - is null, replaces this value with a mock (if the type - can be mocked). - - - For sealed classes, a null value will be generated. - - - - - A default implementation of IQueryable for use with QueryProvider - - - - - The is a - static method that returns an IQueryable of Mocks of T which is used to - apply the linq specification to. - - - - - Allows creation custom value matchers that can be used on setups and verification, - completely replacing the built-in class with your own argument - matching rules. - - See also . - - - - - Provided for the sole purpose of rendering the delegate passed to the - matcher constructor if no friendly render lambda is provided. - - - - - Initializes the match with the condition that - will be checked in order to match invocation - values. - The condition to match against actual values. - - - - - - - - - This method is used to set an expression as the last matcher invoked, - which is used in the SetupSet to allow matchers in the prop = value - delegate expression. This delegate is executed in "fluent" mode in - order to capture the value being set, and construct the corresponding - methodcall. - This is also used in the MatcherFactory for each argument expression. - This method ensures that when we execute the delegate, we - also track the matcher that was invoked, so that when we create the - methodcall we build the expression using it, rather than the null/default - value returned from the actual invocation. - - - - - Allows creation custom value matchers that can be used on setups and verification, - completely replacing the built-in class with your own argument - matching rules. - Type of the value to match. - The argument matching is used to determine whether a concrete - invocation in the mock matches a given setup. This - matching mechanism is fully extensible. - - Creating a custom matcher is straightforward. You just need to create a method - that returns a value from a call to with - your matching condition and optional friendly render expression: - - [Matcher] - public Order IsBigOrder() - { - return Match<Order>.Create( - o => o.GrandTotal >= 5000, - /* a friendly expression to render on failures */ - () => IsBigOrder()); - } - - This method can be used in any mock setup invocation: - - mock.Setup(m => m.Submit(IsBigOrder()).Throws<UnauthorizedAccessException>(); - - At runtime, Moq knows that the return value was a matcher (note that the method MUST be - annotated with the [Matcher] attribute in order to determine this) and - evaluates your predicate with the actual value passed into your predicate. - - Another example might be a case where you want to match a lists of orders - that contains a particular one. You might create matcher like the following: - - - public static class Orders - { - [Matcher] - public static IEnumerable<Order> Contains(Order order) - { - return Match<IEnumerable<Order>>.Create(orders => orders.Contains(order)); - } - } - - Now we can invoke this static method instead of an argument in an - invocation: - - var order = new Order { ... }; - var mock = new Mock<IRepository<Order>>(); - - mock.Setup(x => x.Save(Orders.Contains(order))) - .Throws<ArgumentException>(); - - - - - - Tracks the current mock and interception context. - - - - - Having an active fluent mock context means that the invocation - is being performed in "trial" mode, just to gather the - target method and arguments that need to be matched later - when the actual invocation is made. - - - - - A that returns an empty default value - for non-mockeable types, and mocks for all other types (interfaces and - non-sealed classes) that can be mocked. - - - - - Allows querying the universe of mocks for those that behave - according to the LINQ query specification. - - - This entry-point into Linq to Mocks is the only one in the root Moq - namespace to ease discovery. But to get all the mocking extension - methods on Object, a using of Moq.Linq must be done, so that the - polluting of the intellisense for all objects is an explicit opt-in. - - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The type of the mocked object to query. - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The predicate with the setup expressions. - The type of the mocked object to query. - - - - Creates an mock object of the indicated type. - - The type of the mocked object. - The mocked object created. - - - - Creates an mock object of the indicated type. - - The predicate with the setup expressions. - The type of the mocked object. - The mocked object created. - - - - Creates the mock query with the underlying queriable implementation. - - - - - Wraps the enumerator inside a queryable. - - - - - Method that is turned into the actual call from .Query{T}, to - transform the queryable query into a normal enumerable query. - This method is never used directly by consumers. - - - - - Extension method used to support Linq-like setup properties that are not virtual but do have - a getter and a setter, thereby allowing the use of Linq to Mocks to quickly initialize Dtos too :) - - - - - Helper extensions that are used by the query translator. - - - - - Retrieves a fluent mock from the given setup expression. - - - - - Gets an autogenerated interface with a method on it that matches the signature of the specified - . - - - Such an interface can then be mocked, and a delegate pointed at the method on the mocked instance. - This is how we support delegate mocking. The factory caches such interfaces and reuses them - for repeated requests for the same delegate type. - - The delegate type for which an interface is required. - The method on the autogenerated interface. - - - - - - - - - - Defines the number of invocations allowed by a mocked method. - - - - - Specifies that a mocked method should be invoked times as minimum. - The minimun number of times.An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked one time as minimum. - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked time as maximun. - The maximun number of times.An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked one time as maximun. - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked between and - times. - The minimun number of times.The maximun number of times. - The kind of range. See . - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked exactly times. - The times that a method or property can be called.An object defining the allowed number of invocations. - - - - Specifies that a mocked method should not be invoked. - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked exactly one time. - An object defining the allowed number of invocations. - - - - Determines whether the specified is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Returns a hash code for this instance. - - A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - - - - - Determines whether two specified objects have the same value. - - The first . - - The second . - - true if the value of left is the same as the value of right; otherwise, false. - - - - - Determines whether two specified objects have different values. - - The first . - - The second . - - true if the value of left is different from the value of right; otherwise, false. - - - - diff --git a/packages/Moq.4.2.1502.0911/lib/sl4/Moq.Silverlight.dll b/packages/Moq.4.2.1502.0911/lib/sl4/Moq.Silverlight.dll deleted file mode 100644 index 1ecb48ab8..000000000 Binary files a/packages/Moq.4.2.1502.0911/lib/sl4/Moq.Silverlight.dll and /dev/null differ diff --git a/packages/Moq.4.2.1502.0911/lib/sl4/Moq.Silverlight.xml b/packages/Moq.4.2.1502.0911/lib/sl4/Moq.Silverlight.xml deleted file mode 100644 index 26046c491..000000000 --- a/packages/Moq.4.2.1502.0911/lib/sl4/Moq.Silverlight.xml +++ /dev/null @@ -1,5401 +0,0 @@ - - - - Moq.Silverlight - - - - - Provides a mock implementation of . - - Any interface type can be used for mocking, but for classes, only abstract and virtual members can be mocked. - - The behavior of the mock with regards to the setups and the actual calls is determined - by the optional that can be passed to the - constructor. - - Type to mock, which can be an interface or a class. - The following example shows establishing setups with specific values - for method invocations: - - // Arrange - var order = new Order(TALISKER, 50); - var mock = new Mock<IWarehouse>(); - - mock.Setup(x => x.HasInventory(TALISKER, 50)).Returns(true); - - // Act - order.Fill(mock.Object); - - // Assert - Assert.True(order.IsFilled); - - The following example shows how to use the class - to specify conditions for arguments instead of specific values: - - // Arrange - var order = new Order(TALISKER, 50); - var mock = new Mock<IWarehouse>(); - - // shows how to expect a value within a range - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsInRange(0, 100, Range.Inclusive))) - .Returns(false); - - // shows how to throw for unexpected calls. - mock.Setup(x => x.Remove( - It.IsAny<string>(), - It.IsAny<int>())) - .Throws(new InvalidOperationException()); - - // Act - order.Fill(mock.Object); - - // Assert - Assert.False(order.IsFilled); - - - - - - Base class for mocks and static helper class with methods that - apply to mocked objects, such as to - retrieve a from an object instance. - - - - - Helper interface used to hide the base - members from the fluent API to make it much cleaner - in Visual Studio intellisense. - - - - - - - - - - - - - - - - - Creates an mock object of the indicated type. - - The type of the mocked object. - The mocked object created. - - - - Creates an mock object of the indicated type. - - The predicate with the specification of how the mocked object should behave. - The type of the mocked object. - The mocked object created. - - - - Initializes a new instance of the class. - - - - - Retrieves the mock object for the given object instance. - - Type of the mock to retrieve. Can be omitted as it's inferred - from the object instance passed in as the instance. - The instance of the mocked object.The mock associated with the mocked object. - The received instance - was not created by Moq. - - The following example shows how to add a new setup to an object - instance which is not the original but rather - the object associated with it: - - // Typed instance, not the mock, is retrieved from some test API. - HttpContextBase context = GetMockContext(); - - // context.Request is the typed object from the "real" API - // so in order to add a setup to it, we need to get - // the mock that "owns" it - Mock<HttpRequestBase> request = Mock.Get(context.Request); - mock.Setup(req => req.AppRelativeCurrentExecutionFilePath) - .Returns(tempUrl); - - - - - - Returns the mocked object value. - - - - - Verifies that all verifiable expectations have been met. - - This example sets up an expectation and marks it as verifiable. After - the mock is used, a Verify() call is issued on the mock - to ensure the method in the setup was invoked: - - var mock = new Mock<IWarehouse>(); - this.Setup(x => x.HasInventory(TALISKER, 50)).Verifiable().Returns(true); - ... - // other test code - ... - // Will throw if the test code has didn't call HasInventory. - this.Verify(); - - Not all verifiable expectations were met. - - - - Verifies all expectations regardless of whether they have - been flagged as verifiable. - - This example sets up an expectation without marking it as verifiable. After - the mock is used, a call is issued on the mock - to ensure that all expectations are met: - - var mock = new Mock<IWarehouse>(); - this.Setup(x => x.HasInventory(TALISKER, 50)).Returns(true); - ... - // other test code - ... - // Will throw if the test code has didn't call HasInventory, even - // that expectation was not marked as verifiable. - this.VerifyAll(); - - At least one expectation was not met. - - - - Gets the interceptor target for the given expression and root mock, - building the intermediate hierarchy of mock objects if necessary. - - - - - Raises the associated event with the given - event argument data. - - - - - Raises the associated event with the given - event argument data. - - - - - Adds an interface implementation to the mock, - allowing setups to be specified for it. - - This method can only be called before the first use - of the mock property, at which - point the runtime type has already been generated - and no more interfaces can be added to it. - - Also, must be an - interface and not a class, which must be specified - when creating the mock instead. - - - The mock type - has already been generated by accessing the property. - - The specified - is not an interface. - - The following example creates a mock for the main interface - and later adds to it to verify - it's called by the consumer code: - - var mock = new Mock<IProcessor>(); - mock.Setup(x => x.Execute("ping")); - - // add IDisposable interface - var disposable = mock.As<IDisposable>(); - disposable.Setup(d => d.Dispose()).Verifiable(); - - Type of interface to cast the mock to. - - - - - - - Behavior of the mock, according to the value set in the constructor. - - - - - Whether the base member virtual implementation will be called - for mocked classes if no setup is matched. Defaults to . - - - - - Specifies the behavior to use when returning default values for - unexpected invocations on loose mocks. - - - - - Gets the mocked object instance. - - - - - Retrieves the type of the mocked object, its generic type argument. - This is used in the auto-mocking of hierarchy access. - - - - - If this is a mock of a delegate, this property contains the method - on the autogenerated interface so that we can convert setup + verify - expressions on the delegate into expressions on the interface proxy. - - - - - Allows to check whether expression conversion to the - must be performed on the mock, without causing unnecessarily early initialization of - the mock instance, which breaks As{T}. - - - - - Specifies the class that will determine the default - value to return when invocations are made that - have no setups and need to return a default - value (for loose mocks). - - - - - Exposes the list of extra interfaces implemented by the mock. - - - - - Covarient interface for Mock<T> such that casts between IMock<Employee> to IMock<Person> - are possible. Only covers the covariant members of Mock<T>. - - - - - Exposes the mocked object instance. - - - - - Behavior of the mock, according to the value set in the constructor. - - - - - Whether the base member virtual implementation will be called - for mocked classes if no setup is matched. Defaults to . - - - - - Specifies the behavior to use when returning default values for - unexpected invocations on loose mocks. - - - - - Ctor invoked by AsTInterface exclusively. - - - - - Initializes an instance of the mock with default behavior. - - var mock = new Mock<IFormatProvider>(); - - - - - Initializes an instance of the mock with default behavior and with - the given constructor arguments for the class. (Only valid when is a class) - - The mock will try to find the best match constructor given the constructor arguments, and invoke that - to initialize the instance. This applies only for classes, not interfaces. - - var mock = new Mock<MyProvider>(someArgument, 25); - Optional constructor arguments if the mocked type is a class. - - - - Initializes an instance of the mock with the specified behavior. - - var mock = new Mock<IFormatProvider>(MockBehavior.Relaxed); - Behavior of the mock. - - - - Initializes an instance of the mock with a specific behavior with - the given constructor arguments for the class. - - The mock will try to find the best match constructor given the constructor arguments, and invoke that - to initialize the instance. This applies only to classes, not interfaces. - - var mock = new Mock<MyProvider>(someArgument, 25); - Behavior of the mock.Optional constructor arguments if the mocked type is a class. - - - - Returns the name of the mock - - - - - Returns the mocked object value. - - - - - Specifies a setup on the mocked type for a call to - to a void method. - - If more than one setup is specified for the same method or property, - the latest one wins and is the one that will be executed. - Lambda expression that specifies the expected method invocation. - - var mock = new Mock<IProcessor>(); - mock.Setup(x => x.Execute("ping")); - - - - - - Specifies a setup on the mocked type for a call to - to a value returning method. - Type of the return value. Typically omitted as it can be inferred from the expression. - If more than one setup is specified for the same method or property, - the latest one wins and is the one that will be executed. - Lambda expression that specifies the method invocation. - - mock.Setup(x => x.HasInventory("Talisker", 50)).Returns(true); - - - - - - Specifies a setup on the mocked type for a call to - to a property getter. - - If more than one setup is set for the same property getter, - the latest one wins and is the one that will be executed. - Type of the property. Typically omitted as it can be inferred from the expression.Lambda expression that specifies the property getter. - - mock.SetupGet(x => x.Suspended) - .Returns(true); - - - - - - Specifies a setup on the mocked type for a call to - to a property setter. - - If more than one setup is set for the same property setter, - the latest one wins and is the one that will be executed. - - This overloads allows the use of a callback already - typed for the property type. - - Type of the property. Typically omitted as it can be inferred from the expression.The Lambda expression that sets a property to a value. - - mock.SetupSet(x => x.Suspended = true); - - - - - - Specifies a setup on the mocked type for a call to - to a property setter. - - If more than one setup is set for the same property setter, - the latest one wins and is the one that will be executed. - Lambda expression that sets a property to a value. - - mock.SetupSet(x => x.Suspended = true); - - - - - - Specifies that the given property should have "property behavior", - meaning that setting its value will cause it to be saved and - later returned when the property is requested. (this is also - known as "stubbing"). - - Type of the property, inferred from the property - expression (does not need to be specified). - Property expression to stub. - If you have an interface with an int property Value, you might - stub it using the following straightforward call: - - var mock = new Mock<IHaveValue>(); - mock.Stub(v => v.Value); - - After the Stub call has been issued, setting and - retrieving the object value will behave as expected: - - IHaveValue v = mock.Object; - - v.Value = 5; - Assert.Equal(5, v.Value); - - - - - - Specifies that the given property should have "property behavior", - meaning that setting its value will cause it to be saved and - later returned when the property is requested. This overload - allows setting the initial value for the property. (this is also - known as "stubbing"). - - Type of the property, inferred from the property - expression (does not need to be specified). - Property expression to stub.Initial value for the property. - If you have an interface with an int property Value, you might - stub it using the following straightforward call: - - var mock = new Mock<IHaveValue>(); - mock.SetupProperty(v => v.Value, 5); - - After the SetupProperty call has been issued, setting and - retrieving the object value will behave as expected: - - IHaveValue v = mock.Object; - // Initial value was stored - Assert.Equal(5, v.Value); - - // New value set which changes the initial value - v.Value = 6; - Assert.Equal(6, v.Value); - - - - - - Specifies that the all properties on the mock should have "property behavior", - meaning that setting its value will cause it to be saved and - later returned when the property is requested. (this is also - known as "stubbing"). The default value for each property will be the - one generated as specified by the property for the mock. - - If the mock is set to , - the mocked default values will also get all properties setup recursively. - - - - - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - This example assumes that the mock has been used, and later we want to verify that a given - invocation with specific parameters was performed: - - var mock = new Mock<IProcessor>(); - // exercise mock - //... - // Will throw if the test code didn't call Execute with a "ping" string argument. - mock.Verify(proc => proc.Execute("ping")); - - The invocation was not performed on the mock.Expression to verify. - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called. - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called. - - - - Verifies that a specific invocation matching the given expression was performed on the mock, - specifying a failure error message. Use in conjuntion with the default - . - - This example assumes that the mock has been used, and later we want to verify that a given - invocation with specific parameters was performed: - - var mock = new Mock<IProcessor>(); - // exercise mock - //... - // Will throw if the test code didn't call Execute with a "ping" string argument. - mock.Verify(proc => proc.Execute("ping")); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails. - - - - Verifies that a specific invocation matching the given expression was performed on the mock, - specifying a failure error message. Use in conjuntion with the default - . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Message to show if verification fails. - - - - Verifies that a specific invocation matching the given expression was performed on the mock, - specifying a failure error message. Use in conjuntion with the default - . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Message to show if verification fails. - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - This example assumes that the mock has been used, and later we want to verify that a given - invocation with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't call HasInventory. - mock.Verify(warehouse => warehouse.HasInventory(TALISKER, 50)); - - The invocation was not performed on the mock.Expression to verify.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock. Use in conjuntion - with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock. Use in conjuntion - with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock, specifying a failure - error message. - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't call HasInventory. - mock.Verify(warehouse => warehouse.HasInventory(TALISKER, 50), "When filling orders, inventory has to be checked"); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock, specifying a failure - error message. - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Message to show if verification fails.Type of return value from the expression. - - - - Verifies that a property was read on the mock. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was retrieved from it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't retrieve the IsClosed property. - mock.VerifyGet(warehouse => warehouse.IsClosed); - - The invocation was not performed on the mock.Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock, specifying a failure - error message. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was retrieved from it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't retrieve the IsClosed property. - mock.VerifyGet(warehouse => warehouse.IsClosed); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock, specifying a failure - error message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock, specifying a failure - error message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was set on the mock. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was set on it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed = true); - - The invocation was not performed on the mock.Expression to verify. - - - - Verifies that a property was set on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - - - - Verifies that a property was set on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - - - - Verifies that a property was set on the mock, specifying - a failure message. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was set on it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed = true, "Warehouse should always be closed after the action"); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails. - - - - Verifies that a property was set on the mock, specifying - a failure message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - - - - Verifies that a property was set on the mock, specifying - a failure message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - - - - Raises the event referenced in using - the given argument. - - The argument is - invalid for the target event invocation, or the is - not an event attach or detach expression. - - The following example shows how to raise a event: - - var mock = new Mock<IViewModel>(); - - mock.Raise(x => x.PropertyChanged -= null, new PropertyChangedEventArgs("Name")); - - - This example shows how to invoke an event with a custom event arguments - class in a view that will cause its corresponding presenter to - react by changing its state: - - var mockView = new Mock<IOrdersView>(); - var presenter = new OrdersPresenter(mockView.Object); - - // Check that the presenter has no selection by default - Assert.Null(presenter.SelectedOrder); - - // Raise the event with a specific arguments data - mockView.Raise(v => v.SelectionChanged += null, new OrderEventArgs { Order = new Order("moq", 500) }); - - // Now the presenter reacted to the event, and we have a selected order - Assert.NotNull(presenter.SelectedOrder); - Assert.Equal("moq", presenter.SelectedOrder.ProductName); - - - - - - Raises the event referenced in using - the given argument for a non-EventHandler typed event. - - The arguments are - invalid for the target event invocation, or the is - not an event attach or detach expression. - - The following example shows how to raise a custom event that does not adhere to - the standard EventHandler: - - var mock = new Mock<IViewModel>(); - - mock.Raise(x => x.MyEvent -= null, "Name", bool, 25); - - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Exposes the mocked object instance. - - - - - Allows naming of your mocks, so they can be easily identified in error messages (e.g. from failed assertions). - - - - - - - - Implements the fluent API. - - - - - The expectation will be considered only in the former condition. - - - - - - - The expectation will be considered only in the former condition. - - - - - - - - Setups the get. - - The type of the property. - The expression. - - - - - Setups the set. - - The type of the property. - The setter expression. - - - - - Setups the set. - - The setter expression. - - - - - Determines the way default values are generated - calculated for loose mocks. - - - - - Default behavior, which generates empty values for - value types (i.e. default(int)), empty array and - enumerables, and nulls for all other reference types. - - - - - Whenever the default value generated by - is null, replaces this value with a mock (if the type - can be mocked). - - - For sealed classes, a null value will be generated. - - - - - A that returns an empty default value - for invocations that do not have setups or return values, with loose mocks. - This is the default behavior for a mock. - - - - - Interface to be implemented by classes that determine the - default value of non-expected invocations. - - - - - Defines the default value to return in all the methods returning . - The type of the return value.The value to set as default. - - - - Provides a value for the given member and arguments. - - The member to provide a default value for. - - - - - Provides partial evaluation of subtrees, whenever they can be evaluated locally. - - Matt Warren: http://blogs.msdn.com/mattwar - Documented by InSTEDD: http://www.instedd.org - - - - Performs evaluation and replacement of independent sub-trees - - The root of the expression tree. - A function that decides whether a given expression - node can be part of the local function. - A new tree with sub-trees evaluated and replaced. - - - - Performs evaluation and replacement of independent sub-trees - - The root of the expression tree. - A new tree with sub-trees evaluated and replaced. - - - - Evaluates and replaces sub-trees when first candidate is reached (top-down) - - - - - Performs bottom-up analysis to determine which nodes can possibly - be part of an evaluated sub-tree. - - - - - Casts the expression to a lambda expression, removing - a cast if there's any. - - - - - Casts the body of the lambda expression to a . - - If the body is not a method call. - - - - Converts the body of the lambda expression into the referenced by it. - - - - - Checks whether the body of the lambda expression is a property access. - - - - - Checks whether the expression is a property access. - - - - - Checks whether the body of the lambda expression is a property indexer, which is true - when the expression is an whose - has - equal to . - - - - - Checks whether the expression is a property indexer, which is true - when the expression is an whose - has - equal to . - - - - - Creates an expression that casts the given expression to the - type. - - - - - TODO: remove this code when https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=331583 - is fixed. - - - - - Extracts, into a common form, information from a - around either a (for a normal method call) - or a (for a delegate invocation). - - - - - The intention of is to create a more readable - string representation for the failure message. - - - - - Tests if a type is a delegate type (subclasses ). - - - - - Tracks the current mock and interception context. - - - - - Having an active fluent mock context means that the invocation - is being performed in "trial" mode, just to gather the - target method and arguments that need to be matched later - when the actual invocation is made. - - - - - Ensures the given is not null. - Throws otherwise. - - - - - Ensures the given string is not null or empty. - Throws in the first case, or - in the latter. - - - - - Checks an argument to ensure it is in the specified range including the edges. - - Type of the argument to check, it must be an type. - - The expression containing the name of the argument. - The argument value to check. - The minimun allowed value for the argument. - The maximun allowed value for the argument. - - - - Checks an argument to ensure it is in the specified range excluding the edges. - - Type of the argument to check, it must be an type. - - The expression containing the name of the argument. - The argument value to check. - The minimun allowed value for the argument. - The maximun allowed value for the argument. - - - - Handle interception - - the current invocation context - shared data for the interceptor as a whole - shared data among the strategies during a single interception - InterceptionAction.Continue if further interception has to be processed, otherwise InterceptionAction.Stop - - - - Implemented by all generated mock object instances. - - - - - Implemented by all generated mock object instances. - - - - - Reference the Mock that contains this as the mock.Object value. - - - - - Reference the Mock that contains this as the mock.Object value. - - - - - Implements the actual interception and method invocation for - all mocks. - - - - - Get an eventInfo for a given event name. Search type ancestors depth first if necessary. - - Name of the event, with the set_ or get_ prefix already removed - - - - Get an eventInfo for a given event name. Search type ancestors depth first if necessary. - Searches also in non public events. - - Name of the event, with the set_ or get_ prefix already removed - - - - Given a type return all of its ancestors, both types and interfaces. - - The type to find immediate ancestors of - - - - Allows the specification of a matching condition for an - argument in a method invocation, rather than a specific - argument value. "It" refers to the argument being matched. - - This class allows the setup to match a method invocation - with an arbitrary value, with a value in a specified range, or - even one that matches a given predicate. - - - - - Matches any value of the given type. - - Typically used when the actual argument value for a method - call is not relevant. - - - // Throws an exception for a call to Remove with any string value. - mock.Setup(x => x.Remove(It.IsAny<string>())).Throws(new InvalidOperationException()); - - Type of the value. - - - - Matches any value of the given type, except null. - Type of the value. - - - - Matches any value that satisfies the given predicate. - Type of the argument to check.The predicate used to match the method argument. - Allows the specification of a predicate to perform matching - of method call arguments. - - This example shows how to return the value 1 whenever the argument to the - Do method is an even number. - - mock.Setup(x => x.Do(It.Is<int>(i => i % 2 == 0))) - .Returns(1); - - This example shows how to throw an exception if the argument to the - method is a negative number: - - mock.Setup(x => x.GetUser(It.Is<int>(i => i < 0))) - .Throws(new ArgumentException()); - - - - - - Matches any value that is in the range specified. - Type of the argument to check.The lower bound of the range.The upper bound of the range. - The kind of range. See . - - The following example shows how to expect a method call - with an integer argument within the 0..100 range. - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsInRange(0, 100, Range.Inclusive))) - .Returns(false); - - - - - - Matches any value that is present in the sequence specified. - Type of the argument to check.The sequence of possible values. - The following example shows how to expect a method call - with an integer argument with value from a list. - - var values = new List<int> { 1, 2, 3 }; - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsIn(values))) - .Returns(false); - - - - - - Matches any value that is present in the sequence specified. - Type of the argument to check.The sequence of possible values. - The following example shows how to expect a method call - with an integer argument with a value of 1, 2, or 3. - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsIn(1, 2, 3))) - .Returns(false); - - - - - - Matches any value that is not found in the sequence specified. - Type of the argument to check.The sequence of disallowed values. - The following example shows how to expect a method call - with an integer argument with value not found from a list. - - var values = new List<int> { 1, 2, 3 }; - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsNotIn(values))) - .Returns(false); - - - - - - Matches any value that is not found in the sequence specified. - Type of the argument to check.The sequence of disallowed values. - The following example shows how to expect a method call - with an integer argument of any value except 1, 2, or 3. - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsNotIn(1, 2, 3))) - .Returns(false); - - - - - - Matches a string argument if it matches the given regular expression pattern. - The pattern to use to match the string argument value. - The following example shows how to expect a call to a method where the - string argument matches the given regular expression: - - mock.Setup(x => x.Check(It.IsRegex("[a-z]+"))).Returns(1); - - - - - - Matches a string argument if it matches the given regular expression pattern. - The pattern to use to match the string argument value.The options used to interpret the pattern. - The following example shows how to expect a call to a method where the - string argument matches the given regular expression, in a case insensitive way: - - mock.Setup(x => x.Check(It.IsRegex("[a-z]+", RegexOptions.IgnoreCase))).Returns(1); - - - - - - Implements the fluent API. - - - - - Defines the Callback verb and overloads. - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2) => Console.WriteLine(arg1 + arg2)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3) => Console.WriteLine(arg1 + arg2 + arg3)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4) => Console.WriteLine(arg1 + arg2 + arg3 + arg4)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The type of the sixteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15, string arg16) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16)); - - - - - - Specifies a callback to invoke when the method is called. - - The callback method to invoke. - - The following example specifies a callback to set a boolean - value that can be used later: - - var called = false; - mock.Setup(x => x.Execute()) - .Callback(() => called = true); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The argument type of the invoked method. - The callback method to invoke. - - Invokes the given callback with the concrete invocation argument value. - - Notice how the specific string argument is retrieved by simply declaring - it as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute(It.IsAny<string>())) - .Callback((string command) => Console.WriteLine(command)); - - - - - - Defines occurrence members to constraint setups. - - - - - The expected invocation can happen at most once. - - - - var mock = new Mock<ICommand>(); - mock.Setup(foo => foo.Execute("ping")) - .AtMostOnce(); - - - - - - The expected invocation can happen at most specified number of times. - - The number of times to accept calls. - - - var mock = new Mock<ICommand>(); - mock.Setup(foo => foo.Execute("ping")) - .AtMost( 5 ); - - - - - - Defines the Raises verb. - - - - - Specifies the event that will be raised - when the setup is met. - - An expression that represents an event attach or detach action. - The event arguments to pass for the raised event. - - The following example shows how to raise an event when - the setup is met: - - var mock = new Mock<IContainer>(); - - mock.Setup(add => add.Add(It.IsAny<string>(), It.IsAny<object>())) - .Raises(add => add.Added += null, EventArgs.Empty); - - - - - - Specifies the event that will be raised - when the setup is matched. - - An expression that represents an event attach or detach action. - A function that will build the - to pass when raising the event. - - - - - Specifies the custom event that will be raised - when the setup is matched. - - An expression that represents an event attach or detach action. - The arguments to pass to the custom delegate (non EventHandler-compatible). - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - The type of the fourteenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - The type of the fourteenth argument received by the expected invocation. - The type of the fifteenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - The type of the fourteenth argument received by the expected invocation. - The type of the fifteenth argument received by the expected invocation. - The type of the sixteenth argument received by the expected invocation. - - - - - Defines the Verifiable verb. - - - - - Marks the expectation as verifiable, meaning that a call - to will check if this particular - expectation was met. - - - The following example marks the expectation as verifiable: - - mock.Expect(x => x.Execute("ping")) - .Returns(true) - .Verifiable(); - - - - - - Marks the expectation as verifiable, meaning that a call - to will check if this particular - expectation was met, and specifies a message for failures. - - - The following example marks the expectation as verifiable: - - mock.Expect(x => x.Execute("ping")) - .Returns(true) - .Verifiable("Ping should be executed always!"); - - - - - - Implements the fluent API. - - - - - Implements the fluent API. - - - - - Defines the Throws verb. - - - - - Specifies the exception to throw when the method is invoked. - - Exception instance to throw. - - This example shows how to throw an exception when the method is - invoked with an empty string argument: - - mock.Setup(x => x.Execute("")) - .Throws(new ArgumentException()); - - - - - - Specifies the type of exception to throw when the method is invoked. - - Type of exception to instantiate and throw when the setup is matched. - - This example shows how to throw an exception when the method is - invoked with an empty string argument: - - mock.Setup(x => x.Execute("")) - .Throws<ArgumentException>(); - - - - - - Implements the fluent API. - - - - - Implements the fluent API. - - - - - Defines the Callback verb and overloads for callbacks on - setups that return a value. - - Mocked type. - Type of the return value of the setup. - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2) => Console.WriteLine(arg1 + arg2)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3) => Console.WriteLine(arg1 + arg2 + arg3)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4) => Console.WriteLine(arg1 + arg2 + arg3 + arg4)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The type of the sixteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16)); - - - - - - Specifies a callback to invoke when the method is called. - - The callback method to invoke. - - The following example specifies a callback to set a boolean value that can be used later: - - var called = false; - mock.Setup(x => x.Execute()) - .Callback(() => called = true) - .Returns(true); - - Note that in the case of value-returning methods, after the Callback - call you can still specify the return value. - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the argument of the invoked method. - Callback method to invoke. - - Invokes the given callback with the concrete invocation argument value. - - Notice how the specific string argument is retrieved by simply declaring - it as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute(It.IsAny<string>())) - .Callback(command => Console.WriteLine(command)) - .Returns(true); - - - - - - Implements the fluent API. - - - - - Defines the Returns verb. - - Mocked type. - Type of the return value from the expression. - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2) => arg1 + arg2); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3) => arg1 + arg2 + arg3); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4) => arg1 + arg2 + arg3 + arg4); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5) => arg1 + arg2 + arg3 + arg4 + arg5); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The type of the sixteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15, string arg16) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16); - - - - - - Specifies the value to return. - - The value to return, or . - - Return a true value from the method call: - - mock.Setup(x => x.Execute("ping")) - .Returns(true); - - - - - - Specifies a function that will calculate the value to return from the method. - - The function that will calculate the return value. - - Return a calculated value when the method is called: - - mock.Setup(x => x.Execute("ping")) - .Returns(() => returnValues[0]); - - The lambda expression to retrieve the return value is lazy-executed, - meaning that its value may change depending on the moment the method - is executed and the value the returnValues array has at - that moment. - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the argument of the invoked method. - The function that will calculate the return value. - - Return a calculated value which is evaluated lazily at the time of the invocation. - - The lookup list can change between invocations and the setup - will return different values accordingly. Also, notice how the specific - string argument is retrieved by simply declaring it as part of the lambda - expression: - - - mock.Setup(x => x.Execute(It.IsAny<string>())) - .Returns((string command) => returnValues[command]); - - - - - - Calls the real method of the object and returns its return value. - - The value calculated by the real method of the object. - - - - Implements the fluent API. - - - - - Defines the Callback verb for property getter setups. - - - Mocked type. - Type of the property. - - - - Specifies a callback to invoke when the property is retrieved. - - Callback method to invoke. - - Invokes the given callback with the property value being set. - - mock.SetupGet(x => x.Suspended) - .Callback(() => called = true) - .Returns(true); - - - - - - Implements the fluent API. - - - - - Defines the Returns verb for property get setups. - - Mocked type. - Type of the property. - - - - Specifies the value to return. - - The value to return, or . - - Return a true value from the property getter call: - - mock.SetupGet(x => x.Suspended) - .Returns(true); - - - - - - Specifies a function that will calculate the value to return for the property. - - The function that will calculate the return value. - - Return a calculated value when the property is retrieved: - - mock.SetupGet(x => x.Suspended) - .Returns(() => returnValues[0]); - - The lambda expression to retrieve the return value is lazy-executed, - meaning that its value may change depending on the moment the property - is retrieved and the value the returnValues array has at - that moment. - - - - - Calls the real property of the object and returns its return value. - - The value calculated by the real property of the object. - - - - Implements the fluent API. - - - - - Defines the Callback verb for property setter setups. - - Type of the property. - - - - Specifies a callback to invoke when the property is set that receives the - property value being set. - - Callback method to invoke. - - Invokes the given callback with the property value being set. - - mock.SetupSet(x => x.Suspended) - .Callback((bool state) => Console.WriteLine(state)); - - - - - - Language for ReturnSequence - - - - - Returns value - - - - - Throws an exception - - - - - Throws an exception - - - - - Calls original method - - - - - The first method call or member access will be the - last segment of the expression (depth-first traversal), - which is the one we have to Setup rather than FluentMock. - And the last one is the one we have to Mock.Get rather - than FluentMock. - - - - - A default implementation of IQueryable for use with QueryProvider - - - - - The is a - static method that returns an IQueryable of Mocks of T which is used to - apply the linq specification to. - - - - - Utility repository class to use to construct multiple - mocks when consistent verification is - desired for all of them. - - - If multiple mocks will be created during a test, passing - the desired (if different than the - or the one - passed to the repository constructor) and later verifying each - mock can become repetitive and tedious. - - This repository class helps in that scenario by providing a - simplified creation of multiple mocks with a default - (unless overriden by calling - ) and posterior verification. - - - - The following is a straightforward example on how to - create and automatically verify strict mocks using a : - - var repository = new MockRepository(MockBehavior.Strict); - - var foo = repository.Create<IFoo>(); - var bar = repository.Create<IBar>(); - - // no need to call Verifiable() on the setup - // as we'll be validating all of them anyway. - foo.Setup(f => f.Do()); - bar.Setup(b => b.Redo()); - - // exercise the mocks here - - repository.VerifyAll(); - // At this point all setups are already checked - // and an optional MockException might be thrown. - // Note also that because the mocks are strict, any invocation - // that doesn't have a matching setup will also throw a MockException. - - The following examples shows how to setup the repository - to create loose mocks and later verify only verifiable setups: - - var repository = new MockRepository(MockBehavior.Loose); - - var foo = repository.Create<IFoo>(); - var bar = repository.Create<IBar>(); - - // this setup will be verified when we verify the repository - foo.Setup(f => f.Do()).Verifiable(); - - // this setup will NOT be verified - foo.Setup(f => f.Calculate()); - - // this setup will be verified when we verify the repository - bar.Setup(b => b.Redo()).Verifiable(); - - // exercise the mocks here - // note that because the mocks are Loose, members - // called in the interfaces for which no matching - // setups exist will NOT throw exceptions, - // and will rather return default values. - - repository.Verify(); - // At this point verifiable setups are already checked - // and an optional MockException might be thrown. - - The following examples shows how to setup the repository with a - default strict behavior, overriding that default for a - specific mock: - - var repository = new MockRepository(MockBehavior.Strict); - - // this particular one we want loose - var foo = repository.Create<IFoo>(MockBehavior.Loose); - var bar = repository.Create<IBar>(); - - // specify setups - - // exercise the mocks here - - repository.Verify(); - - - - - - - Utility factory class to use to construct multiple - mocks when consistent verification is - desired for all of them. - - - If multiple mocks will be created during a test, passing - the desired (if different than the - or the one - passed to the factory constructor) and later verifying each - mock can become repetitive and tedious. - - This factory class helps in that scenario by providing a - simplified creation of multiple mocks with a default - (unless overriden by calling - ) and posterior verification. - - - - The following is a straightforward example on how to - create and automatically verify strict mocks using a : - - var factory = new MockFactory(MockBehavior.Strict); - - var foo = factory.Create<IFoo>(); - var bar = factory.Create<IBar>(); - - // no need to call Verifiable() on the setup - // as we'll be validating all of them anyway. - foo.Setup(f => f.Do()); - bar.Setup(b => b.Redo()); - - // exercise the mocks here - - factory.VerifyAll(); - // At this point all setups are already checked - // and an optional MockException might be thrown. - // Note also that because the mocks are strict, any invocation - // that doesn't have a matching setup will also throw a MockException. - - The following examples shows how to setup the factory - to create loose mocks and later verify only verifiable setups: - - var factory = new MockFactory(MockBehavior.Loose); - - var foo = factory.Create<IFoo>(); - var bar = factory.Create<IBar>(); - - // this setup will be verified when we verify the factory - foo.Setup(f => f.Do()).Verifiable(); - - // this setup will NOT be verified - foo.Setup(f => f.Calculate()); - - // this setup will be verified when we verify the factory - bar.Setup(b => b.Redo()).Verifiable(); - - // exercise the mocks here - // note that because the mocks are Loose, members - // called in the interfaces for which no matching - // setups exist will NOT throw exceptions, - // and will rather return default values. - - factory.Verify(); - // At this point verifiable setups are already checked - // and an optional MockException might be thrown. - - The following examples shows how to setup the factory with a - default strict behavior, overriding that default for a - specific mock: - - var factory = new MockFactory(MockBehavior.Strict); - - // this particular one we want loose - var foo = factory.Create<IFoo>(MockBehavior.Loose); - var bar = factory.Create<IBar>(); - - // specify setups - - // exercise the mocks here - - factory.Verify(); - - - - - - - Initializes the factory with the given - for newly created mocks from the factory. - - The behavior to use for mocks created - using the factory method if not overriden - by using the overload. - - - - Creates a new mock with the default - specified at factory construction time. - - Type to mock. - A new . - - - var factory = new MockFactory(MockBehavior.Strict); - - var foo = factory.Create<IFoo>(); - // use mock on tests - - factory.VerifyAll(); - - - - - - Creates a new mock with the default - specified at factory construction time and with the - the given constructor arguments for the class. - - - The mock will try to find the best match constructor given the - constructor arguments, and invoke that to initialize the instance. - This applies only to classes, not interfaces. - - Type to mock. - Constructor arguments for mocked classes. - A new . - - - var factory = new MockFactory(MockBehavior.Default); - - var mock = factory.Create<MyBase>("Foo", 25, true); - // use mock on tests - - factory.Verify(); - - - - - - Creates a new mock with the given . - - Type to mock. - Behavior to use for the mock, which overrides - the default behavior specified at factory construction time. - A new . - - The following example shows how to create a mock with a different - behavior to that specified as the default for the factory: - - var factory = new MockFactory(MockBehavior.Strict); - - var foo = factory.Create<IFoo>(MockBehavior.Loose); - - - - - - Creates a new mock with the given - and with the the given constructor arguments for the class. - - - The mock will try to find the best match constructor given the - constructor arguments, and invoke that to initialize the instance. - This applies only to classes, not interfaces. - - Type to mock. - Behavior to use for the mock, which overrides - the default behavior specified at factory construction time. - Constructor arguments for mocked classes. - A new . - - The following example shows how to create a mock with a different - behavior to that specified as the default for the factory, passing - constructor arguments: - - var factory = new MockFactory(MockBehavior.Default); - - var mock = factory.Create<MyBase>(MockBehavior.Strict, "Foo", 25, true); - - - - - - Implements creation of a new mock within the factory. - - Type to mock. - The behavior for the new mock. - Optional arguments for the construction of the mock. - - - - Verifies all verifiable expectations on all mocks created - by this factory. - - - One or more mocks had expectations that were not satisfied. - - - - Verifies all verifiable expectations on all mocks created - by this factory. - - - One or more mocks had expectations that were not satisfied. - - - - Invokes for each mock - in , and accumulates the resulting - that might be - thrown from the action. - - The action to execute against - each mock. - - - - Whether the base member virtual implementation will be called - for mocked classes if no setup is matched. Defaults to . - - - - - Specifies the behavior to use when returning default values for - unexpected invocations on loose mocks. - - - - - Gets the mocks that have been created by this factory and - that will get verified together. - - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The type of the mocked object to query. - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The predicate with the setup expressions. - The type of the mocked object to query. - - - - Creates an mock object of the indicated type. - - The type of the mocked object. - The mocked object created. - - - - Creates an mock object of the indicated type. - - The predicate with the setup expressions. - The type of the mocked object. - The mocked object created. - - - - Creates the mock query with the underlying queriable implementation. - - - - - Wraps the enumerator inside a queryable. - - - - - Method that is turned into the actual call from .Query{T}, to - transform the queryable query into a normal enumerable query. - This method is never used directly by consumers. - - - - - Initializes the repository with the given - for newly created mocks from the repository. - - The behavior to use for mocks created - using the repository method if not overriden - by using the overload. - - - - Allows querying the universe of mocks for those that behave - according to the LINQ query specification. - - - This entry-point into Linq to Mocks is the only one in the root Moq - namespace to ease discovery. But to get all the mocking extension - methods on Object, a using of Moq.Linq must be done, so that the - polluting of the intellisense for all objects is an explicit opt-in. - - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The type of the mocked object to query. - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The predicate with the setup expressions. - The type of the mocked object to query. - - - - Creates an mock object of the indicated type. - - The type of the mocked object. - The mocked object created. - - - - Creates an mock object of the indicated type. - - The predicate with the setup expressions. - The type of the mocked object. - The mocked object created. - - - - Creates the mock query with the underlying queriable implementation. - - - - - Wraps the enumerator inside a queryable. - - - - - Method that is turned into the actual call from .Query{T}, to - transform the queryable query into a normal enumerable query. - This method is never used directly by consumers. - - - - - Extension method used to support Linq-like setup properties that are not virtual but do have - a getter and a setter, thereby allowing the use of Linq to Mocks to quickly initialize Dtos too :) - - - - - Helper extensions that are used by the query translator. - - - - - Retrieves a fluent mock from the given setup expression. - - - - - Allows creation custom value matchers that can be used on setups and verification, - completely replacing the built-in class with your own argument - matching rules. - - See also . - - - - - Provided for the sole purpose of rendering the delegate passed to the - matcher constructor if no friendly render lambda is provided. - - - - - Initializes the match with the condition that - will be checked in order to match invocation - values. - The condition to match against actual values. - - - - - - - - - This method is used to set an expression as the last matcher invoked, - which is used in the SetupSet to allow matchers in the prop = value - delegate expression. This delegate is executed in "fluent" mode in - order to capture the value being set, and construct the corresponding - methodcall. - This is also used in the MatcherFactory for each argument expression. - This method ensures that when we execute the delegate, we - also track the matcher that was invoked, so that when we create the - methodcall we build the expression using it, rather than the null/default - value returned from the actual invocation. - - - - - Allows creation custom value matchers that can be used on setups and verification, - completely replacing the built-in class with your own argument - matching rules. - Type of the value to match. - The argument matching is used to determine whether a concrete - invocation in the mock matches a given setup. This - matching mechanism is fully extensible. - - Creating a custom matcher is straightforward. You just need to create a method - that returns a value from a call to with - your matching condition and optional friendly render expression: - - [Matcher] - public Order IsBigOrder() - { - return Match<Order>.Create( - o => o.GrandTotal >= 5000, - /* a friendly expression to render on failures */ - () => IsBigOrder()); - } - - This method can be used in any mock setup invocation: - - mock.Setup(m => m.Submit(IsBigOrder()).Throws<UnauthorizedAccessException>(); - - At runtime, Moq knows that the return value was a matcher (note that the method MUST be - annotated with the [Matcher] attribute in order to determine this) and - evaluates your predicate with the actual value passed into your predicate. - - Another example might be a case where you want to match a lists of orders - that contains a particular one. You might create matcher like the following: - - - public static class Orders - { - [Matcher] - public static IEnumerable<Order> Contains(Order order) - { - return Match<IEnumerable<Order>>.Create(orders => orders.Contains(order)); - } - } - - Now we can invoke this static method instead of an argument in an - invocation: - - var order = new Order { ... }; - var mock = new Mock<IRepository<Order>>(); - - mock.Setup(x => x.Save(Orders.Contains(order))) - .Throws<ArgumentException>(); - - - - - - Marks a method as a matcher, which allows complete replacement - of the built-in class with your own argument - matching rules. - - - This feature has been deprecated in favor of the new - and simpler . - - - The argument matching is used to determine whether a concrete - invocation in the mock matches a given setup. This - matching mechanism is fully extensible. - - - There are two parts of a matcher: the compiler matcher - and the runtime matcher. - - - Compiler matcher - Used to satisfy the compiler requirements for the - argument. Needs to be a method optionally receiving any arguments - you might need for the matching, but with a return type that - matches that of the argument. - - Let's say I want to match a lists of orders that contains - a particular one. I might create a compiler matcher like the following: - - - public static class Orders - { - [Matcher] - public static IEnumerable<Order> Contains(Order order) - { - return null; - } - } - - Now we can invoke this static method instead of an argument in an - invocation: - - var order = new Order { ... }; - var mock = new Mock<IRepository<Order>>(); - - mock.Setup(x => x.Save(Orders.Contains(order))) - .Throws<ArgumentException>(); - - Note that the return value from the compiler matcher is irrelevant. - This method will never be called, and is just used to satisfy the - compiler and to signal Moq that this is not a method that we want - to be invoked at runtime. - - - - Runtime matcher - - The runtime matcher is the one that will actually perform evaluation - when the test is run, and is defined by convention to have the - same signature as the compiler matcher, but where the return - value is the first argument to the call, which contains the - object received by the actual invocation at runtime: - - public static bool Contains(IEnumerable<Order> orders, Order order) - { - return orders.Contains(order); - } - - At runtime, the mocked method will be invoked with a specific - list of orders. This value will be passed to this runtime - matcher as the first argument, while the second argument is the - one specified in the setup (x.Save(Orders.Contains(order))). - - The boolean returned determines whether the given argument has been - matched. If all arguments to the expected method are matched, then - the setup matches and is evaluated. - - - - - - Using this extensible infrastructure, you can easily replace the entire - set of matchers with your own. You can also avoid the - typical (and annoying) lengthy expressions that result when you have - multiple arguments that use generics. - - - The following is the complete example explained above: - - public static class Orders - { - [Matcher] - public static IEnumerable<Order> Contains(Order order) - { - return null; - } - - public static bool Contains(IEnumerable<Order> orders, Order order) - { - return orders.Contains(order); - } - } - - And the concrete test using this matcher: - - var order = new Order { ... }; - var mock = new Mock<IRepository<Order>>(); - - mock.Setup(x => x.Save(Orders.Contains(order))) - .Throws<ArgumentException>(); - - // use mock, invoke Save, and have the matcher filter. - - - - - - Matcher to treat static functions as matchers. - - mock.Setup(x => x.StringMethod(A.MagicString())); - - public static class A - { - [Matcher] - public static string MagicString() { return null; } - public static bool MagicString(string arg) - { - return arg == "magic"; - } - } - - Will succeed if: mock.Object.StringMethod("magic"); - and fail with any other call. - - - - - We need this non-generics base class so that - we can use from - generic code. - - - - - Options to customize the behavior of the mock. - - - - - Causes the mock to always throw - an exception for invocations that don't have a - corresponding setup. - - - - - Will never throw exceptions, returning default - values when necessary (null for reference types, - zero for value types or empty enumerables and arrays). - - - - - Default mock behavior, which equals . - - - - - A that returns an empty default value - for non-mockeable types, and mocks for all other types (interfaces and - non-sealed classes) that can be mocked. - - - - - Exception thrown by mocks when setups are not matched, - the mock is not properly setup, etc. - - - A distinct exception type is provided so that exceptions - thrown by the mock can be differentiated in tests that - expect other exceptions to be thrown (i.e. ArgumentException). - - Richer exception hierarchy/types are not provided as - tests typically should not catch or expect exceptions - from the mocks. These are typically the result of changes - in the tested class or its collaborators implementation, and - result in fixes in the mock setup so that they dissapear and - allow the test to pass. - - - - - - Indicates whether this exception is a verification fault raised by Verify() - - - - - Made internal as it's of no use for - consumers, but it's important for - our own tests. - - - - - Used by the mock factory to accumulate verification - failures. - - - - - Helper class to setup a full trace between many mocks - - - - - Initialize a trace setup - - - - - Allow sequence to be repeated - - - - - define nice api - - - - - Perform an expectation in the trace. - - - - - Provides legacy API members as extensions so that - existing code continues to compile, but new code - doesn't see then. - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Provides additional methods on mocks. - - - Provided as extension methods as they confuse the compiler - with the overloads taking Action. - - - - - Specifies a setup on the mocked type for a call to - to a property setter, regardless of its value. - - - If more than one setup is set for the same property setter, - the latest one wins and is the one that will be executed. - - Type of the property. Typically omitted as it can be inferred from the expression. - Type of the mock. - The target mock for the setup. - Lambda expression that specifies the property setter. - - - mock.SetupSet(x => x.Suspended); - - - - This method is not legacy, but must be on an extension method to avoid - confusing the compiler with the new Action syntax. - - - - - Verifies that a property has been set on the mock, regarless of its value. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - Expression to verify. - The mock instance. - Mocked type. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Verifies that a property has been set on the mock, specifying a failure - error message. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - Expression to verify. - Message to show if verification fails. - The mock instance. - Mocked type. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Verifies that a property has been set on the mock, regardless - of the value but only the specified number of times. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - The invocation was not call the times specified by - . - The mock instance. - Mocked type. - The number of times a method is allowed to be called. - Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Verifies that a property has been set on the mock, regardless - of the value but only the specified number of times, and specifying a failure - error message. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - The invocation was not call the times specified by - . - The mock instance. - Mocked type. - The number of times a method is allowed to be called. - Message to show if verification fails. - Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Allows setups to be specified for protected members by using their - name as a string, rather than strong-typing them which is not possible - due to their visibility. - - - - - Specifies a setup for a void method invocation with the given - , optionally specifying arguments for the method call. - - The name of the void method to be invoked. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - - - - Specifies a setup for an invocation on a property or a non void method with the given - , optionally specifying arguments for the method call. - - The name of the method or property to be invoked. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - The return type of the method or property. - - - - Specifies a setup for an invocation on a property getter with the given - . - - The name of the property. - The type of the property. - - - - Specifies a setup for an invocation on a property setter with the given - . - - The name of the property. - The property value. If argument matchers are used, - remember to use rather than . - The type of the property. - - - - Specifies a verify for a void method with the given , - optionally specifying arguments for the method call. Use in conjuntion with the default - . - - The invocation was not call the times specified by - . - The name of the void method to be verified. - The number of times a method is allowed to be called. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - - - - Specifies a verify for an invocation on a property or a non void method with the given - , optionally specifying arguments for the method call. - - The invocation was not call the times specified by - . - The name of the method or property to be invoked. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - The number of times a method is allowed to be called. - The type of return value from the expression. - - - - Specifies a verify for an invocation on a property getter with the given - . - The invocation was not call the times specified by - . - - The name of the property. - The number of times a method is allowed to be called. - The type of the property. - - - - Specifies a setup for an invocation on a property setter with the given - . - - The invocation was not call the times specified by - . - The name of the property. - The number of times a method is allowed to be called. - The property value. - The type of the property. If argument matchers are used, - remember to use rather than . - - - - Allows the specification of a matching condition for an - argument in a protected member setup, rather than a specific - argument value. "ItExpr" refers to the argument being matched. - - - Use this variant of argument matching instead of - for protected setups. - This class allows the setup to match a method invocation - with an arbitrary value, with a value in a specified range, or - even one that matches a given predicate, or null. - - - - - Matches a null value of the given type. - - - Required for protected mocks as the null value cannot be used - directly as it prevents proper method overload selection. - - - - // Throws an exception for a call to Remove with a null string value. - mock.Protected() - .Setup("Remove", ItExpr.IsNull<string>()) - .Throws(new InvalidOperationException()); - - - Type of the value. - - - - Matches any value of the given type. - - - Typically used when the actual argument value for a method - call is not relevant. - - - - // Throws an exception for a call to Remove with any string value. - mock.Protected() - .Setup("Remove", ItExpr.IsAny<string>()) - .Throws(new InvalidOperationException()); - - - Type of the value. - - - - Matches any value that satisfies the given predicate. - - Type of the argument to check. - The predicate used to match the method argument. - - Allows the specification of a predicate to perform matching - of method call arguments. - - - This example shows how to return the value 1 whenever the argument to the - Do method is an even number. - - mock.Protected() - .Setup("Do", ItExpr.Is<int>(i => i % 2 == 0)) - .Returns(1); - - This example shows how to throw an exception if the argument to the - method is a negative number: - - mock.Protected() - .Setup("GetUser", ItExpr.Is<int>(i => i < 0)) - .Throws(new ArgumentException()); - - - - - - Matches any value that is in the range specified. - - Type of the argument to check. - The lower bound of the range. - The upper bound of the range. - The kind of range. See . - - The following example shows how to expect a method call - with an integer argument within the 0..100 range. - - mock.Protected() - .Setup("HasInventory", - ItExpr.IsAny<string>(), - ItExpr.IsInRange(0, 100, Range.Inclusive)) - .Returns(false); - - - - - - Matches a string argument if it matches the given regular expression pattern. - - The pattern to use to match the string argument value. - - The following example shows how to expect a call to a method where the - string argument matches the given regular expression: - - mock.Protected() - .Setup("Check", ItExpr.IsRegex("[a-z]+")) - .Returns(1); - - - - - - Matches a string argument if it matches the given regular expression pattern. - - The pattern to use to match the string argument value. - The options used to interpret the pattern. - - The following example shows how to expect a call to a method where the - string argument matches the given regular expression, in a case insensitive way: - - mock.Protected() - .Setup("Check", ItExpr.IsRegex("[a-z]+", RegexOptions.IgnoreCase)) - .Returns(1); - - - - - - Enables the Protected() method on , - allowing setups to be set for protected members by using their - name as a string, rather than strong-typing them which is not possible - due to their visibility. - - - - - Enable protected setups for the mock. - - Mocked object type. Typically omitted as it can be inferred from the mock instance. - The mock to set the protected setups on. - - - - - - - - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Mock type has already been initialized by accessing its Object property. Adding interfaces must be done before that.. - - - - - Looks up a localized string similar to Value cannot be an empty string.. - - - - - Looks up a localized string similar to Can only add interfaces to the mock.. - - - - - Looks up a localized string similar to Can't set return value for void method {0}.. - - - - - Looks up a localized string similar to Constructor arguments cannot be passed for delegate mocks.. - - - - - Looks up a localized string similar to Constructor arguments cannot be passed for interface mocks.. - - - - - Looks up a localized string similar to A matching constructor for the given arguments was not found on the mocked type.. - - - - - Looks up a localized string similar to Could not locate event for attach or detach method {0}.. - - - - - Looks up a localized string similar to Expression {0} involves a field access, which is not supported. Use properties instead.. - - - - - Looks up a localized string similar to Type to mock must be an interface or an abstract or non-sealed class. . - - - - - Looks up a localized string similar to Cannot retrieve a mock with the given object type {0} as it's not the main type of the mock or any of its additional interfaces. - Please cast the argument to one of the supported types: {1}. - Remember that there's no generics covariance in the CLR, so your object must be one of these types in order for the call to succeed.. - - - - - Looks up a localized string similar to The equals ("==" or "=" in VB) and the conditional 'and' ("&&" or "AndAlso" in VB) operators are the only ones supported in the query specification expression. Unsupported expression: {0}. - - - - - Looks up a localized string similar to LINQ method '{0}' not supported.. - - - - - Looks up a localized string similar to Expression contains a call to a method which is not virtual (overridable in VB) or abstract. Unsupported expression: {0}. - - - - - Looks up a localized string similar to Member {0}.{1} does not exist.. - - - - - Looks up a localized string similar to Method {0}.{1} is public. Use strong-typed Expect overload instead: - mock.Setup(x => x.{1}()); - . - - - - - Looks up a localized string similar to {0} invocation failed with mock behavior {1}. - {2}. - - - - - Looks up a localized string similar to Expected only {0} calls to {1}.. - - - - - Looks up a localized string similar to Expected only one call to {0}.. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at least {2} times, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at least once, but was never performed: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at most {3} times, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at most once, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock between {2} and {3} times (Exclusive), but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock between {2} and {3} times (Inclusive), but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock exactly {2} times, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock should never have been performed, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock once, but was {4} times: {1}. - - - - - Looks up a localized string similar to All invocations on the mock must have a corresponding setup.. - - - - - Looks up a localized string similar to Object instance was not created by Moq.. - - - - - Looks up a localized string similar to Out expression must evaluate to a constant value.. - - - - - Looks up a localized string similar to Property {0}.{1} does not have a getter.. - - - - - Looks up a localized string similar to Property {0}.{1} does not exist.. - - - - - Looks up a localized string similar to Property {0}.{1} is write-only.. - - - - - Looks up a localized string similar to Property {0}.{1} is read-only.. - - - - - Looks up a localized string similar to Property {0}.{1} does not have a setter.. - - - - - Looks up a localized string similar to Cannot raise a mocked event unless it has been associated (attached) to a concrete event in a mocked object.. - - - - - Looks up a localized string similar to Ref expression must evaluate to a constant value.. - - - - - Looks up a localized string similar to Invocation needs to return a value and therefore must have a corresponding setup that provides it.. - - - - - Looks up a localized string similar to A lambda expression is expected as the argument to It.Is<T>.. - - - - - Looks up a localized string similar to Invocation {0} should not have been made.. - - - - - Looks up a localized string similar to Expression is not a method invocation: {0}. - - - - - Looks up a localized string similar to Expression is not a property access: {0}. - - - - - Looks up a localized string similar to Expression is not a property setter invocation.. - - - - - Looks up a localized string similar to Expression references a method that does not belong to the mocked object: {0}. - - - - - Looks up a localized string similar to Invalid setup on a non-virtual (overridable in VB) member: {0}. - - - - - Looks up a localized string similar to Type {0} does not implement required interface {1}. - - - - - Looks up a localized string similar to Type {0} does not from required type {1}. - - - - - Looks up a localized string similar to To specify a setup for public property {0}.{1}, use the typed overloads, such as: - mock.Setup(x => x.{1}).Returns(value); - mock.SetupGet(x => x.{1}).Returns(value); //equivalent to previous one - mock.SetupSet(x => x.{1}).Callback(callbackDelegate); - . - - - - - Looks up a localized string similar to Unsupported expression: {0}. - - - - - Looks up a localized string similar to Only property accesses are supported in intermediate invocations on a setup. Unsupported expression {0}.. - - - - - Looks up a localized string similar to Expression contains intermediate property access {0}.{1} which is of type {2} and cannot be mocked. Unsupported expression {3}.. - - - - - Looks up a localized string similar to Setter expression cannot use argument matchers that receive parameters.. - - - - - Looks up a localized string similar to Member {0} is not supported for protected mocking.. - - - - - Looks up a localized string similar to Setter expression can only use static custom matchers.. - - - - - Looks up a localized string similar to The following setups were not matched: - {0}. - - - - - Looks up a localized string similar to Invalid verify on a non-virtual (overridable in VB) member: {0}. - - - - - Gets an autogenerated interface with a method on it that matches the signature of the specified - . - - - Such an interface can then be mocked, and a delegate pointed at the method on the mocked instance. - This is how we support delegate mocking. The factory caches such interfaces and reuses them - for repeated requests for the same delegate type. - - The delegate type for which an interface is required. - The method on the autogenerated interface. - - - - - - - - - - Hook used to tells Castle which methods to proxy in mocked classes. - - Here we proxy the default methods Castle suggests (everything Object's methods) - plus Object.ToString(), so we can give mocks useful default names. - - This is required to allow Moq to mock ToString on proxy *class* implementations. - - - - - Extends AllMethodsHook.ShouldInterceptMethod to also intercept Object.ToString(). - - - - - The base class used for all our interface-inheriting proxies, which overrides the default - Object.ToString() behavior, to route it via the mock by default, unless overriden by a - real implementation. - - This is required to allow Moq to mock ToString on proxy *interface* implementations. - - - This is internal to Moq and should not be generally used. - - Unfortunately it must be public, due to cross-assembly visibility issues with reflection, - see github.com/Moq/moq4/issues/98 for details. - - - - - Overrides the default ToString implementation to instead find the mock for this mock.Object, - and return MockName + '.Object' as the mocked object's ToString, to make it easy to relate - mocks and mock object instances in error messages. - - - - - Kind of range to use in a filter specified through - . - - - - - The range includes the to and - from values. - - - - - The range does not include the to and - from values. - - - - - Helper for sequencing return values in the same method. - - - - - Return a sequence of values, once per call. - - - - - Defines the number of invocations allowed by a mocked method. - - - - - Specifies that a mocked method should be invoked times as minimum. - The minimun number of times.An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked one time as minimum. - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked time as maximun. - The maximun number of times.An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked one time as maximun. - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked between and - times. - The minimun number of times.The maximun number of times. - The kind of range. See . - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked exactly times. - The times that a method or property can be called.An object defining the allowed number of invocations. - - - - Specifies that a mocked method should not be invoked. - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked exactly one time. - An object defining the allowed number of invocations. - - - - Determines whether the specified is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Returns a hash code for this instance. - - A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - - - - - Determines whether two specified objects have the same value. - - The first . - - The second . - - true if the value of left is the same as the value of right; otherwise, false. - - - - - Determines whether two specified objects have different values. - - The first . - - The second . - - true if the value of left is different from the value of right; otherwise, false. - - - - diff --git a/packages/NUnit.3.0.0/CHANGES.txt b/packages/NUnit.3.0.0/CHANGES.txt deleted file mode 100644 index 44f640ec5..000000000 --- a/packages/NUnit.3.0.0/CHANGES.txt +++ /dev/null @@ -1,896 +0,0 @@ -NUnit 3.0.0 Final Release - November 15, 2015 - -Issues Resolved - - * 635 Mono 4.0 Support - -NUnit 3.0.0 Release Candidate 3 - November 13, 2015 - -Engine - - * The engine now only sets the config file for project.nunit to project.config if project.config exists. Otherwise, each assembly uses its own config, provided it is run in a separate AppDomain by itself. - - NOTE: It is not possible for multiple assemblies in the same AppDomain to use different configs. This is not an NUnit limitation, it's just how configs work! - -Issues Resolved - - * 856 Extensions support for third party runners in NUnit 3.0 - * 1003 Delete TeamCityEventHandler as it is not used - * 1015 Specifying .nunit project and --framework on command line causes crash - * 1017 Remove Assert.Multiple from framework - -NUnit 3.0.0 Release Candidate 2 - November 8, 2015 - -Engine - - * The IDriverFactory extensibility interface has been modified. - -Issues Resolved - - * 970 Define PARALLEL in CF build of nunitlite - * 978 It should be possible to determine version of NUnit using nunit console tool - * 983 Inconsistent return codes depending on ProcessModel - * 986 Update docs for parallel execution - * 988 Don't run portable tests from NUnit Console - * 990 V2 driver is passing invalid filter elements to NUnit - * 991 Mono.Options should not be exposed to public directly - * 993 Give error message when a regex filter is used with NUnit V2 - * 997 Add missing XML Documentation - * 1008 NUnitLite namespace not updated in the NuGet Packages - -NUnit 3.0.0 Release Candidate - November 1, 2015 - -Framework - - * The portable build now supports ASP.NET 5 and the new Core CLR. - - NOTE: The `nunit3-console` runner cannot run tests that reference the portable build. - You may run such tests using NUnitLite or a platform-specific runner. - - * `TestCaseAttribute` and `TestCaseData` now allow modifying the test name without replacing it entirely. - * The Silverlight packages are now separate downloads. - -NUnitLite - - * The NUnitLite runner now produces the same output display and XML results as the console runner. - -Engine - - * The format of the XML result file has been finalized and documented. - -Console Runner - - * The console runner program is now called `nunit3-console`. - * Console runner output has been modified so that the summary comes at the end, to reduce the need for scrolling. - -Issues Resolved - - * 59 Length of generated test names should be limited - * 68 Customization of test case name generation - * 404 Split tests between nunitlite.runner and nunit.framework - * 575 Add support for ASP.NET 5 and the new Core CLR - * 783 Package separately for Silverlight - * 833 Intermittent failure of WorkItemQueueTests.StopQueue_WithWorkers - * 859 NUnit-Console output - move Test Run Summary to end - * 867 Remove Warnings from Ignored tests - * 868 Review skipped tests - * 887 Move environment and settings elements to the assembly suite in the result file - * 899 Colors for ColorConsole on grey background are too light - * 904 InternalPreserveStackTrace is not supported on all Portable platforms - * 914 Unclear error message from console runner when assembly has no tests - * 916 Console runner dies when test agent dies - * 918 Console runner --where parameter is case sensitive - * 920 Remove addins\nunit.engine.api.dll from NuGet package - * 929 Rename nunit-console.exe - * 931 Remove beta warnings from NuGet packages - * 936 Explicit skipped tests not displayed - * 939 Installer complains about .NET even if already installed - * 940 Confirm or modify list of packages for release - * 947 Breaking API change in ValueSourceAttribute - * 949 Update copyright in NUnit Console - * 954 NUnitLite XML output is not consistent with the engine's - * 955 NUnitLite does not display the where clause - * 959 Restore filter options for NUnitLite portable build - * 960 Intermittent failure of CategoryFilterTests - * 967 Run Settings Report is not being displayed. - -NUnit 3.0.0 Beta 5 - October 16, 2015 - -Framework - - * Parameterized test cases now support nullable arguments. - * The NUnit framework may now be built for the .NET Core framework. Note that this is only available through building the source code. A binary will be available in the next release. - -Engine - - * The engine now runs multiple test assemblies in parallel by default - * The output XML now includes more information about the test run, including the text of the command used, any engine settings and the filter used to select tests. - * Extensions may now specify data in an identifying attribute, for use by the engine in deciding whether to load that extension. - - -Console Runner - - * The console now displays all settings used by the engine to run tests as well as the filter used to select tests. - * The console runner accepts a new option --maxagents. If multiple assemblies are run in separate processes, this value may be used to limit the number that are executed simultaneously in parallel. - * The console runner no longer accepts the --include and --exclude options. Instead, the new --where option provides a more general way to express which tests will be executed, such as --where "cat==Fast && Priority==High". See the docs for details of the syntax. - * The new --debug option causes NUnit to break in the debugger immediately before tests are run. This simplifies debugging, especially when the test is run in a separate process. - -Issues Resolved - - * 41 Check for zeroes in Assert messages - * 254 Finalize XML format for test results - * 275 NUnitEqualityComparer fails to compare IEquatable where second object is derived from T - * 304 Run test Assemblies in parallel - * 374 New syntax for selecting tests to be run - * 515 OSPlatform.IsMacOSX doesn't work - * 573 nunit-console hangs on Mac OS X after all tests have run - * 669 TeamCity service message should have assembly name as a part of test name. - * 689 The TeamCity service message "testFinished" should have an integer value in the "duration" attribute - * 713 Include command information in XML - * 719 We have no way to configure tests for several assemblies using NUnit project file and the common installation from msi file - * 735 Workers number in xml report file cannot be found - * 784 Build Portable Framework on Linux - * 790 Allow Extensions to provide data through an attribute - * 794 Make it easier to debug tests as well as NUnit itself - * 801 NUnit calls Dispose multiple times - * 814 Support nullable types with TestCase - * 818 Possible error in Merge Pull Request #797 - * 821 Wrapped method results in loss of result information - * 822 Test for Debugger in NUnitTestAssemblyRunner probably should not be in CF build - * 824 Remove unused System.Reflection using statements - * 826 Randomizer uniqueness tests fail randomly! - * 828 Merge pull request #827 (issue 826) - * 830 Add ability to report test results synchronously to test runners - * 837 Enumerators not disposed when comparing IEnumerables - * 840 Add missing copyright notices - * 844 Pull Request #835 (Issue #814) does not build in CF - * 847 Add new --process:inprocess and --inprocess options - * 850 Test runner fails if test name contains invalid xml characters - * 851 'Exclude' console option is not working in NUnit Lite - * 853 Cannot run NUnit Console from another directory - * 860 Use CDATA section for message, stack-trace and output elements of XML - * 863 Eliminate core engine - * 865 Intermittent failures of StopWatchTests - * 869 Tests that use directory separator char to determine platform misreport Linux on MaxOSX - * 870 NUnit Console Runtime Environment misreports on MacOSX - * 874 Add .NET Core Framework - * 878 Cannot exclude MacOSX or XBox platforms when running on CF - * 892 Fixed test runner returning early when executing more than one test run. - * 894 Give nunit.engine and nunit.engine.api assemblies strong names - * 896 NUnit 3.0 console runner not placing test result xml in --work directory - -NUnit 3.0.0 Beta 4 - August 25, 2015 - -Framework - - * A new RetryAttribute allows retrying of failing tests. - * New SupersetConstraint and Is.SupersetOf syntax complement SubsetConstraint. - * Tests skipped due to ExplicitAttribute are now reported as skipped. - -Engine - - * We now use Cecil to examine assemblies prior to loading them. - * Extensions are no longer based on Mono.Addins but use our own extension framework. - -Issues Resolved - - * 125 3rd-party dependencies should be downloaded on demand - * 283 What should we do when a user extension does something bad? - * 585 RetryAttribute - * 642 Restructure MSBuild script - * 649 Change how we zip packages - * 654 ReflectionOnlyLoad and ReflectionOnlyLoadFrom - * 664 Invalid "id" attribute in the report for case "test started" - * 685 In the some cases when tests cannot be started NUnit returns exit code "0" - * 728 Missing Assert.That overload - * 741 Explicit Tests get run when using --exclude - * 746 Framework should send events for all tests - * 747 NUnit should apply attributes even if test is non-runnable - * 749 Review Use of Mono.Addins for Engine Extensibility - * 750 Include Explicit Tests in Test Results - * 753 Feature request: Is.SupersetOf() assertion constraint - * 755 TimeOut attribute doesn't work with TestCaseSource Attribute - * 757 Implement some way to wait for execution to complete in ITestEngineRunner - * 760 Packaging targets do not run on Linux - * 766 Added overloads for True()/False() accepting booleans - * 778 Build and build.cmd scripts invoke nuget.exe improperly - * 780 Teamcity fix - * 782 No sources for 2.6.4 - -NUnit 3.0.0 Beta 3 - July 15, 2015 - -Framework - - * The RangeAttribute has been extended to support more data types including - uint, long and ulong - * Added platform support for Windows 10 and fixed issues with Windows 8 and - 8.1 support - * Added async support to the portable version of NUnit Framework - * The named members of the TestCaseSource and ValueSource attributes must now be - static. - * RandomAttribute has been extended to add support for new data types including - uint, long, ulong, short, ushort, float, byte and sbyte - * TestContext.Random has also been extended to add support for new data types including - uint, long, ulong, short, ushort, float, byte, sbyte and decimal - * Removed the dependency on Microsoft.Bcl.Async from the NUnit Framework assembly - targeting .NET 4.0. If you want to write async tests in .NET 4.0, you will need - to reference the NuGet package yourself. - * Added a new TestFixtureSource attribute which is the equivalent to TestCaseSource - but provides for instantiation of fixtures. - * Significant improvements have been made in how NUnit deduces the type arguments of - generic methods based on the arguments provided. - -Engine - - * If the target framework is not specified, test assemblies that are compiled - to target .NET 4.5 will no longer run in .NET 4.0 compatibility mode - - Console - - * If the console is run without arguments, it will now display help - -Issues Resolved - - * 47 Extensions to RangeAttribute - * 237 System.Uri .ctor works not properly under Nunit - * 244 NUnit should properly distinguish between .NET 4.0 and 4.5 - * 310 Target framework not specified on the AppDomain when running against .Net 4.5 - * 321 Rationalize how we count tests - * 472 Overflow exception and DivideByZero exception from the RangeAttribute - * 524 int and char do not compare correctly? - * 539 Truncation of string arguments - * 544 AsyncTestMethodTests for 4.5 Framework fails frequently on Travis CI - * 656 Unused parameter in Console.WriteLine found - * 670 Failing Tests in TeamCity Build - * 673 Ensure proper disposal of engine objects - * 674 Engine does not release test assemblies - * 679 Windows 10 Support - * 682 Add Async Support to Portable Framework - * 683 Make FrameworkController available in portable build - * 687 TestAgency does not launch agent process correctly if runtime type is not specified (i.e. v4.0) - * 692 PlatformAttribute_OperatingSystemBitNess fails when running in 32-bit process - * 693 Generic Test Method cannot determine type arguments for fixture when passed as IEnumerable - * 698 Require TestCaseSource and ValueSource named members to be static - * 703 TeamCity non-equal flowid for 'testStarted' and 'testFinished' messages - * 712 Extensions to RandomAttribute - * 715 Provide a data source attribute at TestFixture Level - * 718 RangeConstraint gives error with from and two args of differing types - * 723 Does nunit.nuspec require dependency on Microsoft.Bcl.Async? - * 724 Adds support for Nullable to Assert.IsTrue and Assert.IsFalse - * 734 Console without parameters doesn't show help - -NUnit 3.0.0 Beta 2 - May 12, 2015 - -Framework - - * The Compact Framework version of the framework is now packaged separately - and will be distributed as a ZIP file and as a NuGet package. - * The NUnit 2.x RepeatAttribute was added back into the framework. - * Added Throws.ArgumentNullException - * Added GetString methods to NUnit.Framework.Internal.RandomGenerator to - create repeatable random strings for testing - * When checking the equality of DateTimeOffset, you can now use the - WithSameOffset modifier - * Some classes intended for internal usage that were public for testing - have now been made internal. Additional classes will be made internal - for the final 3.0 release. - -Engine - - * Added a core engine which is a non-extensible, minimal engine for use by - devices and similar situations where reduced functionality is compensated - for by reduced size and simplicity of usage. See - https://github.com/nunit/dev/wiki/Core-Engine for more information. - -Issues Resolved - - * 22 Add OSArchitecture Attribute to Environment node in result xml - * 24 Assert on Dictionary Content - * 48 Explicit seems to conflict with Ignore - * 168 Create NUnit 3.0 documentation - * 196 Compare DateTimeOffsets including the offset in the comparison - * 217 New icon for the 3.0 release - * 316 NUnitLite TextUI Runner - * 320 No Tests found: Using parametrized Fixture and TestCaseSource - * 360 Better exception message when using non-BCL class in property - * 454 Rare registry configurations may cause NUnit to fail - * 478 RepeatAttribute - * 481 Testing multiple assemblies in nunitlite - * 538 Potential bug using TestContext in constructors - * 546 Enable Parallel in NUnitLite/CF (or more) builds - * 551 TextRunner not passing the NumWorkers option to the ITestAssemblyRunner - * 556 Executed tests should always return a non-zero duration - * 559 Fix text of NuGet packages - * 560 Fix PackageVersion property on wix install projects - * 562 Program.cs in NUnitLite NuGet package is incorrect - * 564 NUnitLite Nuget package is Beta 1a, Framework is Beta 1 - * 565 NUnitLite Nuget package adds Program.cs to a VB Project - * 568 Isolate packaging from building - * 570 ThrowsConstraint failure message should include stack trace of actual exception - * 576 Throws.ArgumentNullException would be nice - * 577 Documentation on some members of Throws falsely claims that they return `TargetInvocationException` constraints - * 579 No documentation for recommended usage of TestCaseSourceAttribute - * 580 TeamCity Service Message Uses Incorrect Test Name with NUnit2Driver - * 582 Test Ids Are Not Unique - * 583 TeamCity service messages to support parallel test execution - * 584 Non-runnable assembly has incorrect ResultState - * 609 Add support for integration with TeamCity - * 611 Remove unused --teamcity option from CF build of NUnitLite - * 612 MaxTime doesn't work when used for TestCase - * 621 Core Engine - * 622 nunit-console fails when use --output - * 628 Modify IService interface and simplify ServiceContext - * 631 Separate packaging for the compact framework - * 646 ConfigurationManager.AppSettings Params Return Null under Beta 1 - * 648 Passing 2 or more test assemblies targeting > .NET 2.0 to nunit-console fails - -NUnit 3.0.0 Beta 1 - March 25, 2015 - -General - - * There is now a master windows installer for the framework, engine and console runner. - -Framework - - * We no longer create a separate framework build for .NET 3.5. The 2.0 and - 3.5 builds were essentially the same, so the former should now be used - under both runtimes. - * A new Constraint, DictionaryContainsKeyConstraint, may be used to test - that a specified key is present in a dictionary. - * LevelOfParallelizationAttribute has been renamed to LevelOfParallelismAttribute. - * The Silverlight runner now displays output in color and includes any - text output created by the tests. - * The class and method names of each test are included in the output xml - where applicable. - * String arguments used in test case names are now truncated to 40 rather - than 20 characters. - -Engine - - * The engine API has now been finalized. It permits specifying a minimum - version of the engine that a runner is able to use. The best installed - version of the engine will be loaded. Third-party runners may override - the selection process by including a copy of the engine in their - installation directory and specifying that it must be used. - * The V2 framework driver now uses the event listener and test listener - passed to it by the runner. This corrects several outstanding issues - caused by events not being received and allows selecting V2 tests to - be run from the command-line, in the same way that V3 tests are selected. - -Console - - * The console now defaults to not using shadowcopy. There is a new option - --shadowcopy to turn it on if needed. - -Issues Resolved - - * 224 Silverlight Support - * 318 TestActionAttribute: Retrieving the TestFixture - * 428 Add ExpectedExceptionAttribute to C# samples - * 440 Automatic selection of Test Engine to use - * 450 Create master install that includes the framework, engine and console installs - * 477 Assert does not work with ArraySegment - * 482 nunit-console has multiple errors related to -framework option - * 483 Adds constraint for asserting that a dictionary contains a particular key - * 484 Missing file in NUnit.Console nuget package - * 485 Can't run v2 tests with nunit-console 3.0 - * 487 NUnitLite can't load assemblies by their file name - * 488 Async setup and teardown still don't work - * 497 Framework installer shold register the portable framework - * 504 Option --workers:0 is ignored - * 508 Travis builds with failure in engine tests show as successful - * 509 Under linux, not all mono profiles are listed as available - * 512 Drop the .NET 3.5 build - * 517 V2 FrameworkDriver does not make use of passed in TestEventListener - * 523 Provide an option to disable shadowcopy in NUnit v3 - * 528 V2 FrameworkDriver does not make use of passed in TestFilter - * 530 Color display for Silverlight runner - * 531 Display text output from tests in Silverlight runner - * 534 Add classname and methodname to test result xml - * 541 Console help doesn't indicate defaults - -NUnit 3.0.0 Alpha 5 - January 30, 2015 - -General - - * A Windows installer is now included in the release packages. - -Framework - - * TestCaseAttribute now allows arguments with default values to be omitted. Additionaly, it accepts a Platform property to specify the platforms on which the test case should be run. - * TestFixture and TestCase attributes now enforce the requirement that a reason needs to be provided when ignoring a test. - * SetUp, TearDown, OneTimeSetUp and OneTimeTearDown methods may now be async. - * String arguments over 20 characters in length are truncated when used as part of a test name. - -Engine - - * The engine is now extensible using Mono.Addins. In this release, extension points are provided for FrameworkDrivers, ProjectLoaders and OutputWriters. The following addins are bundled as a part of NUnit: - * A FrameworkDriver that allows running NUnit V2 tests under NUnit 3.0. - * ProjectLoaders for NUnit and Visual Studio projects. - * An OutputWriter that creates XML output in NUnit V2 format. - * DomainUsage now defaults to Multiple if not specified by the runner - -Console - - * New options supported: - * --testlist provides a list of tests to run in a file - * --stoponerror indicates that the run should terminate when any test fails. - -Issues Resolved - - * 20 TestCaseAttribute needs Platform property. - * 60 NUnit should support async setup, teardown, fixture setup and fixture teardown. - * 257 TestCaseAttribute should not require parameters with default values to be specified. - * 266 Pluggable framework drivers. - * 368 Create addin model. - * 369 Project loader addins - * 370 OutputWriter addins - * 403 Move ConsoleOptions.cs and Options.cs to Common and share... - * 419 Create Windows Installer for NUnit. - * 427 [TestFixture(Ignore=true)] should not be allowed. - * 437 Errors in tests under Linux due to hard-coded paths. - * 441 NUnit-Console should support --testlist option - * 442 Add --stoponerror option back to nunit-console. - * 456 Fix memory leak in RuntimeFramework. - * 459 Remove the Mixed Platforms build configuration. - * 468 Change default domain usage to multiple. - * 469 Truncate string arguments in test names in order to limit the length. - -NUnit 3.0.0 Alpha 4 - December 30, 2014 - -Framework - - * ApartmentAttribute has been added, replacing STAAttribute and MTAAttribute. - * Unnecessary overloads of Assert.That and Assume.That have been removed. - * Multiple SetUpFixtures may be specified in a single namespace. - * Improvements to the Pairwise strategy test case generation algorithm. - * The new NUnitLite runner --testlist option, allows a list of tests to be kept in a file. - -Engine - - * A driver is now included, which allows running NUnit 2.x tests under NUnit 3.0. - * The engine can now load and run tests specified in a number of project formats: - * NUnit (.nunit) - * Visual Studio C# projects (.csproj) - * Visual Studio F# projects (.vjsproj) - * Visual Studio Visual Basic projects (.vbproj) - * Visual Studio solutions (.sln) - * Legacy C++ and Visual JScript projects (.csproj and .vjsproj) are also supported - * Support for the current C++ format (.csxproj) is not yet available - * Creation of output files like TestResult.xml in various formats is now a - service of the engine, available to any runner. - -Console - - * The command-line may now include any number of assemblies and/or supported projects. - -Issues Resolved - - * 37 Multiple SetUpFixtures should be permitted on same namespace - * 210 TestContext.WriteLine in an AppDomain causes an error - * 227 Add support for VS projects and solutions - * 231 Update C# samples to use NUnit 3.0 - * 233 Update F# samples to use NUnit 3.0 - * 234 Update C++ samples to use NUnit 3.0 - * 265 Reorganize console reports for nunit-console and nunitlite - * 299 No full path to assembly in XML file under Compact Framework - * 301 Command-line length - * 363 Make Xml result output an engine service - * 377 CombiningStrategyAttributes don't work correctly on generic methods - * 388 Improvements to NUnitLite runner output - * 390 Specify exactly what happens when a test times out - * 396 ApartmentAttribute - * 397 CF nunitlite runner assembly has the wrong name - * 407 Assert.Pass() with ]]> in message crashes console runner - * 414 Simplify Assert overloads - * 416 NUnit 2.x Framework Driver - * 417 Complete work on NUnit projects - * 420 Create Settings file in proper location - -NUnit 3.0.0 Alpha 3 - November 29, 2014 - -Breaking Changes - - * NUnitLite tests must reference both the nunit.framework and nunitlite assemblies. - -Framework - - * The NUnit and NUnitLite frameworks have now been merged. There is no longer any distinction - between them in terms of features, although some features are not available on all platforms. - * The release includes two new framework builds: compact framework 3.5 and portable. The portable - library is compatible with .NET 4.5, Silverlight 5.0, Windows 8, Windows Phone 8.1, - Windows Phone Silverlight 8, Mono for Android and MonoTouch. - * A number of previously unsupported features are available for the Compact Framework: - - Generic methods as tests - - RegexConstraint - - TimeoutAttribute - - FileAssert, DirectoryAssert and file-related constraints - -Engine - - * The logic of runtime selection has now changed so that each assembly runs by default - in a separate process using the runtime for which it was built. - * On 64-bit systems, each test process is automatically created as 32-bit or 64-bit, - depending on the platform specified for the test assembly. - -Console - - * The console runner now runs tests in a separate process per assembly by default. They may - still be run in process or in a single separate process by use of command-line options. - * The console runner now starts in the highest version of the .NET runtime available, making - it simpler to debug tests by specifying that they should run in-process on the command-line. - * The -x86 command-line option is provided to force execution in a 32-bit process on a 64-bit system. - * A writeability check is performed for each output result file before trying to run the tests. - * The -teamcity option is now supported. - -Issues Resolved - - * 12 Compact framework should support generic methods - * 145 NUnit-console fails if test result message contains invalid xml characters - * 155 Create utility classes for platform-specific code - * 223 Common code for NUnitLite console runner and NUnit-Console - * 225 Compact Framework Support - * 238 Improvements to running 32 bit tests on a 64 bit system - * 261 Add portable nunitlite build - * 284 NUnitLite Unification - * 293 CF does not have a CurrentDirectory - * 306 Assure NUnit can write resultfile - * 308 Early disposal of runners - * 309 NUnit-Console should support incremental output under TeamCity - * 325 Add RegexConstraint to compact framework build - * 326 Add TimeoutAttribute to compact framework build - * 327 Allow generic test methods in the compact framework - * 328 Use .NET Stopwatch class for compact framework builds - * 331 Alpha 2 CF does not build - * 333 Add parallel execution to desktop builds of NUnitLite - * 334 Include File-related constraints and syntax in NUnitLite builds - * 335 Re-introduce 'Classic' NUnit syntax in NUnitLite - * 336 Document use of separate obj directories per build in our projects - * 337 Update Standard Defines page for .NET 3.0 - * 341 Move the NUnitLite runners to separate assemblies - * 367 Refactor XML Escaping Tests - * 372 CF Build TestAssemblyRunnerTests - * 373 Minor CF Test Fixes - * 378 Correct documentation for PairwiseAttribute - * 386 Console Output Improvements - -NUnit 3.0.0 Alpha 2 - November 2, 2014 - -Breaking Changes - - * The console runner no longer displays test results in the debugger. - * The NUnitLite compact framework 2.0 build has been removed. - * All addin support has been removed from the framework. Documentation of NUnit 3.0 extensibility features will be published in time for the beta release. In the interim, please ask for support on the nunit-discuss list. - -General - - * A separate solution has been created for Linux - * We now have continuous integration builds under both Travis and Appveyor - * The compact framework 3.5 build is now working and will be supported in future releases. - -New Features - - * The console runner now automatically detects 32- versus 64-bit test assemblies. - * The NUnitLite report output has been standardized to match that of nunit-console. - * The NUnitLite command-line has been standardized to match that of nunit-console where they share the same options. - * Both nunit-console and NUnitLite now display output in color. - * ActionAttributes now allow specification of multiple targets on the attribute as designed. This didn't work in the first alpha. - * OneTimeSetUp and OneTimeTearDown failures are now shown on the test report. Individual test failures after OneTimeSetUp failure are no longer shown. - * The console runner refuses to run tests build with older versions of NUnit. A plugin will be available to run older tests in the future. - -Issues Resolved - - * 222 Color console for NUnitLite - * 229 Timing failures in tests - * 241 Remove reference to Microslft BCL packages - * 243 Create solution for Linux - * 245 Multiple targets on action attributes not implemented - * 246 C++ tests do not compile in VS2013 - * 247 Eliminate trace display when running tests in debug - * 255 Add new result states for more precision in where failures occur - * 256 ContainsConstraint break when used with AndConstraint - * 264 Stacktrace displays too many entries - * 269 Add manifest to nunit-console and nunit-agent - * 270 OneTimeSetUp failure results in too much output - * 271 Invalid tests should be treated as errors - * 274 Command line options should be case insensitive - * 276 NUnit-console should not reference nunit.framework - * 278 New result states (ChildFailure and SetupFailure) break NUnit2XmlOutputWriter - * 282 Get tests for NUnit2XmlOutputWriter working - * 288 Set up Appveyor CI build - * 290 Stack trace still displays too many items - * 315 NUnit 3.0 alpha: Cannot run in console on my assembly - * 319 CI builds are not treating test failures as failures of the build - * 322 Remove Stopwatch tests where they test the real .NET Stopwatch - -NUnit 3.0.0 Alpha 1 - September 22, 2014 - -Breaking Changes - - * Legacy suites are no longer supported - * Assert.NullOrEmpty is no longer supported (Use Is.Null.Or.Empty) - -General - - * MsBuild is now used for the build rather than NAnt - * The framework test harness has been removed now that nunit-console is at a point where it can run the tests. - -New Features - - * Action Attributes have been added with the same features as in NUnit 2.6.3. - * TestContext now has a method that allows writing to the XML output. - * TestContext.CurrentContext.Result now provides the error message and stack trace during teardown. - * Does prefix operator supplies several added constraints. - -Issues Resolved - - * 6 Log4net not working with NUnit - * 13 Standardize commandline options for nunitlite runner - * 17 No allowance is currently made for nullable arguents in TestCase parameter conversions - * 33 TestCaseSource cannot refer to a parameterized test fixture - * 54 Store message and stack trace in TestContext for use in TearDown - * 111 Implement Changes to File, Directory and Path Assertions - * 112 Implement Action Attributes - * 156 Accessing multiple AppDomains within unit tests result in SerializationException - * 163 Add --trace option to NUnitLite - * 167 Create interim documentation for the alpha release - * 169 Design and implement distribution of NUnit packages - * 171 Assert.That should work with any lambda returning bool - * 175 Test Harness should return an error if any tests fail - * 180 Errors in Linux CI build - * 181 Replace NAnt with MsBuild / XBuild - * 183 Standardize commandline options for test harness - * 188 No output from NUnitLite when selected test is not found - * 189 Add string operators to Does prefix - * 193 TestWorkerTests.BusyExecutedIdleEventsCalledInSequence fails occasionally - * 197 Deprecate or remove Assert.NullOrEmpty - * 202 Eliminate legacy suites - * 203 Combine framework, engine and console runner in a single solution and repository - * 209 Make Ignore attribute's reason mandatory - * 215 Running 32-bit tests on a 64-bit OS - * 219 Teardown failures are not reported - -Console Issues Resolved (Old nunit-console project, now combined with nunit) - - * 2 Failure in TestFixtureSetUp is not reported correctly - * 5 CI Server for nunit-console - * 6 System.NullReferenceException on start nunit-console-x86 - * 21 NUnitFrameworkDriverTests fail if not run from same directory - * 24 'Debug' value for /trace option is deprecated in 2.6.3 - * 38 Confusing Excluded categories output - -NUnit 2.9.7 - August 8, 2014 - -Breaking Changes - - * NUnit no longer supports void async test methods. You should use a Task return Type instead. - * The ExpectedExceptionAttribute is no longer supported. Use Assert.Throws() or Assert.That(..., Throws) instead for a more precise specification of where the exception is expected to be thrown. - -New Features - - * Parallel test execution is supported down to the Fixture level. Use ParallelizableAttribute to indicate types that may be run in parallel. - * Async tests are supported for .NET 4.0 if the user has installed support for them. - * A new FileExistsConstraint has been added along with FileAssert.Exists and FileAssert.DoesNotExist - * ExpectedResult is now supported on simple (non-TestCase) tests. - * The Ignore attribute now takes a named parameter Until, which allows specifying a date after which the test is no longer ignored. - * The following new values are now recognized by PlatformAttribute: Win7, Win8, Win8.1, Win2012Server, Win2012ServerR2, NT6.1, NT6.2, 32-bit, 64-bit - * TimeoutAttribute is now supported under Silverlight - * ValuesAttribute may be used without any values on an enum or boolean argument. All possible values are used. - * You may now specify a tolerance using Within when testing equality of DateTimeOffset values. - * The XML output now includes a start and end time for each test. - -Issues Resolved - - * 8 [SetUpFixture] is not working as expected - * 14 CI Server for NUnit Framework - * 21 Is.InRange Constraint Ambiguity - * 27 Values attribute support for enum types - * 29 Specifying a tolerance with "Within" doesn't work for DateTimeOffset data types - * 31 Report start and end time of test execution - * 36 Make RequiresThread, RequiresSTA, RequiresMTA inheritable - * 45 Need of Enddate together with Ignore - * 55 Incorrect XML comments for CollectionAssert.IsSubsetOf - * 62 Matches(Constraint) does not work as expected - * 63 Async support should handle Task return type without state machine - * 64 AsyncStateMachineAttribute should only be checked by name - * 65 Update NUnit Wiki to show the new location of samples - * 66 Parallel Test Execution within test assemblies - * 67 Allow Expected Result on simple tests - * 70 EquivalentTo isn't compatible with IgnoreCase for dictioneries - * 75 Async tests should be supported for projects that target .NET 4.0 - * 82 nunit-framework tests are timing out on Linux - * 83 Path-related tests fail on Linux - * 85 Culture-dependent NUnit tests fail on non-English machine - * 88 TestCaseSourceAttribute documentation - * 90 EquivalentTo isn't compatible with IgnoreCase for char - * 100 Changes to Tolerance definitions - * 110 Add new platforms to PlatformAttribute - * 113 Remove ExpectedException - * 118 Workarounds for missing InternalPreserveStackTrace in mono - * 121 Test harness does not honor the --worker option when set to zero - * 129 Standardize Timeout in the Silverlight build - * 130 Add FileAssert.Exists and FileAssert.DoesNotExist - * 132 Drop support for void async methods - * 153 Surprising behavior of DelayedConstraint pollingInterval - * 161 Update API to support stopping an ongoing test run - -NOTE: Bug Fixes below this point refer to the number of the bug in Launchpad. - -NUnit 2.9.6 - October 4, 2013 - -Main Features - - * Separate projects for nunit-console and nunit.engine - * New builds for .NET 4.5 and Silverlight - * TestContext is now supported - * External API is now stable; internal interfaces are separate from API - * Tests may be run in parallel on separate threads - * Solutions and projects now use VS2012 (except for Compact framework) - -Bug Fixes - - * 463470 We should encapsulate references to pre-2.0 collections - * 498690 Assert.That() doesn't like properties with scoped setters - * 501784 Theory tests do not work correctly when using null parameters - * 531873 Feature: Extraction of unit tests from NUnit test assembly and calling appropriate one - * 611325 Allow Teardown to detect if last test failed - * 611938 Generic Test Instances disappear - * 655882 Make CategoryAttribute inherited - * 664081 Add Server2008 R2 and Windows 7 to PlatformAttribute - * 671432 Upgrade NAnt to Latest Release - * 676560 Assert.AreEqual does not support IEquatable - * 691129 Add Category parameter to TestFixture - * 697069 Feature request: dynamic location for TestResult.xml - * 708173 NUnit's logic for comparing arrays - use Comparer if it is provided - * 709062 "System.ArgumentException : Cannot compare" when the element is a list - * 712156 Tests cannot use AppDomain.SetPrincipalPolicy - * 719184 Platformdependency in src/ClientUtilities/util/Services/DomainManager.cs:40 - * 719187 Using Path.GetTempPath() causes conflicts in shared temporary folders - * 735851 Add detection of 3.0, 3.5 and 4.0 frameworks to PlatformAttribute - * 736062 Deadlock when EventListener performs a Trace call + EventPump synchronisation - * 756843 Failing assertion does not show non-linear tolerance mode - * 766749 net-2.0\nunit-console-x86.exe.config should have a element and also enable loadFromRemoteSources - * 770471 Assert.IsEmpty does not support IEnumerable - * 785460 Add Category parameter to TestCaseSourceAttribute - * 787106 EqualConstraint provides inadequate failure information for IEnumerables - * 792466 TestContext MethodName - * 794115 HashSet incorrectly reported - * 800089 Assert.Throws() hides details of inner AssertionException - * 848713 Feature request: Add switch for console to break on any test case error - * 878376 Add 'Exactly(n)' to the NUnit constraint syntax - * 882137 When no tests are run, higher level suites display as Inconclusive - * 882517 NUnit 2.5.10 doesn't recognize TestFixture if there are only TestCaseSource inside - * 885173 Tests are still executed after cancellation by user - * 885277 Exception when project calls for a runtime using only 2 digits - * 885604 Feature request: Explicit named parameter to TestCaseAttribute - * 890129 DelayedConstraint doesn't appear to poll properties of objects - * 892844 Not using Mono 4.0 profile under Windows - * 893919 DelayedConstraint fails polling properties on references which are initially null - * 896973 Console output lines are run together under Linux - * 897289 Is.Empty constraint has unclear failure message - * 898192 Feature Request: Is.Negative, Is.Positive - * 898256 IEnumerable for Datapoints doesn't work - * 899178 Wrong failure message for parameterized tests that expect exceptions - * 904841 After exiting for timeout the teardown method is not executed - * 908829 TestCase attribute does not play well with variadic test functions - * 910218 NUnit should add a trailing separator to the ApplicationBase - * 920472 CollectionAssert.IsNotEmpty must dispose Enumerator - * 922455 Add Support for Windows 8 and Windows 2012 Server to PlatformAttribute - * 928246 Use assembly.Location instead of assembly.CodeBase - * 958766 For development work under TeamCity, we need to support nunit2 formatted output under direct-runner - * 1000181 Parameterized TestFixture with System.Type as constructor arguments fails - * 1000213 Inconclusive message Not in report output - * 1023084 Add Enum support to RandomAttribute - * 1028188 Add Support for Silverlight - * 1029785 Test loaded from remote folder failed to run with exception System.IODirectory - * 1037144 Add MonoTouch support to PlatformAttribute - * 1041365 Add MaxOsX and Xbox support to platform attribute - * 1057981 C#5 async tests are not supported - * 1060631 Add .NET 4.5 build - * 1064014 Simple async tests should not return Task - * 1071164 Support async methods in usage scenarios of Throws constraints - * 1071343 Runner.Load fails on CF if the test assembly contains a generic method - * 1071861 Error in Path Constraints - * 1072379 Report test execution time at a higher resolution - * 1074568 Assert/Assume should support an async method for the ActualValueDelegate - * 1082330 Better Exception if SetCulture attribute is applied multiple times - * 1111834 Expose Random Object as part of the test context - * 1111838 Include Random Seed in Test Report - * 1172979 Add Category Support to nunitlite Runner - * 1203361 Randomizer uniqueness tests sometimes fail - * 1221712 When non-existing test method is specified in -test, result is still "Tests run: 1, Passed: 1" - * 1223294 System.NullReferenceException thrown when ExpectedExceptionAttribute is used in a static class - * 1225542 Standardize commandline options for test harness - -Bug Fixes in 2.9.6 But Not Listed Here in the Release - - * 541699 Silverlight Support - * 1222148 /framework switch does not recognize net-4.5 - * 1228979 Theories with all test cases inconclusive are not reported as failures - - -NUnit 2.9.5 - July 30, 2010 - -Bug Fixes - - * 483836 Allow non-public test fixtures consistently - * 487878 Tests in generic class without proper TestFixture attribute should be invalid - * 498656 TestCase should show array values in GUI - * 513989 Is.Empty should work for directories - * 519912 Thread.CurrentPrincipal Set In TestFixtureSetUp Not Maintained Between Tests - * 532488 constraints from ConstraintExpression/ConstraintBuilder are not reusable - * 590717 categorie contains dash or trail spaces is not selectable - * 590970 static TestFixtureSetUp/TestFixtureTearDown methods in base classes are not run - * 595683 NUnit console runner fails to load assemblies - * 600627 Assertion message formatted poorly by PropertyConstraint - * 601108 Duplicate test using abstract test fixtures - * 601645 Parametered test should try to convert data type from source to parameter - * 605432 ToString not working properly for some properties - * 606548 Deprecate Directory Assert in 2.5 and remove it in 3.0 - * 608875 NUnit Equality Comparer incorrectly defines equality for Dictionary objects - -NUnit 2.9.4 - May 4, 2010 - -Bug Fixes - - * 419411 Fixture With No Tests Shows as Non-Runnable - * 459219 Changes to thread princpal cause failures under .NET 4.0 - * 459224 Culture test failure under .NET 4.0 - * 462019 Line endings needs to be better controlled in source - * 462418 Assume.That() fails if I specify a message - * 483845 TestCase expected return value cannot be null - * 488002 Should not report tests in abstract class as invalid - * 490679 Category in TestCaseData clashes with Category on ParameterizedMethodSuite - * 501352 VS2010 projects have not been updated for new directory structure - * 504018 Automatic Values For Theory Test Parameters Not Provided For bool And enum - * 505899 'Description' parameter in both TestAttribute and TestCaseAttribute is not allowed - * 523335 TestFixtureTearDown in static class not executed - * 556971 Datapoint(s)Attribute should work on IEnumerable as well as on Arrays - * 561436 SetCulture broken with 2.5.4 - * 563532 DatapointsAttribute should be allowed on properties and methods - -NUnit 2.9.3 - October 26, 2009 - -Main Features - - * Created new API for controlling framework - * New builds for .Net 3.5 and 4.0, compact framework 3.5 - * Support for old style tests has been removed - * New adhoc runner for testing the framework - -Bug Fixes - - * 432805 Some Framework Tests don't run on Linux - * 440109 Full Framework does not support "Contains" - -NUnit 2.9.2 - September 19, 2009 - -Main Features - - * NUnitLite code is now merged with NUnit - * Added NUnitLite runner to the framework code - * Added Compact framework builds - -Bug Fixes - - * 430100 Assert.Catch should return T - * 432566 NUnitLite shows empty string as argument - * 432573 Mono test should be at runtime - -NUnit 2.9.1 - August 27, 2009 - -General - - * Created a separate project for the framework and framework tests - * Changed license to MIT / X11 - * Created Windows installer for the framework - -Bug Fixes - - * 400502 NUnitEqualityComparer.StreamsE­qual fails for same stream - * 400508 TestCaseSource attirbute is not working when Type is given - * 400510 TestCaseData variable length ctor drops values - * 417557 Add SetUICultureAttribute from NUnit 2.5.2 - * 417559 Add Ignore to TestFixture, TestCase and TestCaseData - * 417560 Merge Assert.Throws and Assert.Catch changes from NUnit 2.5.2 - * 417564 TimeoutAttribute on Assembly diff --git a/packages/NUnit.3.0.0/LICENSE.txt b/packages/NUnit.3.0.0/LICENSE.txt deleted file mode 100644 index 41c02243d..000000000 --- a/packages/NUnit.3.0.0/LICENSE.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2015 Charlie Poole - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - diff --git a/packages/NUnit.3.0.0/NOTICES.txt b/packages/NUnit.3.0.0/NOTICES.txt deleted file mode 100644 index 02f3f84d6..000000000 --- a/packages/NUnit.3.0.0/NOTICES.txt +++ /dev/null @@ -1,5 +0,0 @@ -NUnit 3.0 is based on earlier versions of NUnit, with Portions - -Copyright (c) 2002-2014 Charlie Poole or -Copyright (c) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov or -Copyright (c) 2000-2002 Philip A. Craig diff --git a/packages/NUnit.3.0.0/lib/dotnet/nunit.framework.dll b/packages/NUnit.3.0.0/lib/dotnet/nunit.framework.dll deleted file mode 100644 index 5e721f0be..000000000 Binary files a/packages/NUnit.3.0.0/lib/dotnet/nunit.framework.dll and /dev/null differ diff --git a/packages/NUnit.3.0.0/lib/dotnet/nunit.framework.xml b/packages/NUnit.3.0.0/lib/dotnet/nunit.framework.xml deleted file mode 100644 index cfc59be9b..000000000 --- a/packages/NUnit.3.0.0/lib/dotnet/nunit.framework.xml +++ /dev/null @@ -1,15231 +0,0 @@ - - - - nunit.framework - - - - - AssemblyHelper provides static methods for working - with assemblies. - - - - - Gets the AssemblyName of an assembly. - - The assembly - An AssemblyName - - - - Loads an assembly given a string, which is the AssemblyName - - - - - - - Env is a static class that provides some of the features of - System.Environment that are not available under all runtimes - - - - - The newline sequence in the current environment. - - - - - Path to the 'My Documents' folder - - - - - Directory used for file output if not specified on commandline. - - - - - Class used to guard against unexpected argument values - or operations by throwing an appropriate exception. - - - - - Throws an exception if an argument is null - - The value to be tested - The name of the argument - - - - Throws an exception if a string argument is null or empty - - The value to be tested - The name of the argument - - - - Throws an ArgumentOutOfRangeException if the specified condition is not met. - - The condition that must be met - The exception message to be used - The name of the argument - - - - Throws an ArgumentException if the specified condition is not met. - - The condition that must be met - The exception message to be used - The name of the argument - - - - Throws an InvalidOperationException if the specified condition is not met. - - The condition that must be met - The exception message to be used - - - - Interface for logging within the engine - - - - - Logs the specified message at the error level. - - The message. - - - - Logs the specified message at the error level. - - The message. - The arguments. - - - - Logs the specified message at the warning level. - - The message. - - - - Logs the specified message at the warning level. - - The message. - The arguments. - - - - Logs the specified message at the info level. - - The message. - - - - Logs the specified message at the info level. - - The message. - The arguments. - - - - Logs the specified message at the debug level. - - The message. - - - - Logs the specified message at the debug level. - - The message. - The arguments. - - - - InternalTrace provides facilities for tracing the execution - of the NUnit framework. Tests and classes under test may make use - of Console writes, System.Diagnostics.Trace or various loggers and - NUnit itself traps and processes each of them. For that reason, a - separate internal trace is needed. - - Note: - InternalTrace uses a global lock to allow multiple threads to write - trace messages. This can easily make it a bottleneck so it must be - used sparingly. Keep the trace Level as low as possible and only - insert InternalTrace writes where they are needed. - TODO: add some buffering and a separate writer thread as an option. - TODO: figure out a way to turn on trace in specific classes only. - - - - - Initialize the internal trace using a provided TextWriter and level - - A TextWriter - The InternalTraceLevel - - - - Get a named Logger - - - - - - Get a logger named for a particular Type. - - - - - Gets a flag indicating whether the InternalTrace is initialized - - - - - InternalTraceLevel is an enumeration controlling the - level of detailed presented in the internal log. - - - - - Use the default settings as specified by the user. - - - - - Do not display any trace messages - - - - - Display Error messages only - - - - - Display Warning level and higher messages - - - - - Display informational and higher messages - - - - - Display debug messages and higher - i.e. all messages - - - - - Display debug messages and higher - i.e. all messages - - - - - A trace listener that writes to a separate file per domain - and process using it. - - - - - Construct an InternalTraceWriter that writes to a - TextWriter provided by the caller. - - - - - - Writes a character to the text string or stream. - - The character to write to the text stream. - - - - Writes a string to the text string or stream. - - The string to write. - - - - Writes a string followed by a line terminator to the text string or stream. - - The string to write. If is null, only the line terminator is written. - - - - Releases the unmanaged resources used by the and optionally releases the managed resources. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - Clears all buffers for the current writer and causes any buffered data to be written to the underlying device. - - - - - Returns the character encoding in which the output is written. - - The character encoding in which the output is written. - - - - Provides internal logging to the NUnit framework - - - - - Initializes a new instance of the class. - - The name. - The log level. - The writer where logs are sent. - - - - Logs the message at error level. - - The message. - - - - Logs the message at error level. - - The message. - The message arguments. - - - - Logs the message at warm level. - - The message. - - - - Logs the message at warning level. - - The message. - The message arguments. - - - - Logs the message at info level. - - The message. - - - - Logs the message at info level. - - The message. - The message arguments. - - - - Logs the message at debug level. - - The message. - - - - Logs the message at debug level. - - The message. - The message arguments. - - - - PackageSettings is a static class containing constant values that - are used as keys in setting up a TestPackage. These values are used in - the engine and framework. Setting values may be a string, int or bool. - - - - - Flag (bool) indicating whether tests are being debugged. - - - - - The InternalTraceLevel for this run. Values are: "Default", - "Off", "Error", "Warning", "Info", "Debug", "Verbose". - Default is "Off". "Debug" and "Verbose" are synonyms. - - - - - Full path of the directory to be used for work and result files. - This path is provided to tests by the frameowrk TestContext. - - - - - The name of the config to use in loading a project. - If not specified, the first config found is used. - - - - - Bool indicating whether the engine should determine the private - bin path by examining the paths to all the tests. Defaults to - true unless PrivateBinPath is specified. - - - - - The ApplicationBase to use in loading the tests. If not - specified, and each assembly has its own process, then the - location of the assembly is used. For multiple assemblies - in a single process, the closest common root directory is used. - - - - - Path to the config file to use in running the tests. - - - - - Bool flag indicating whether a debugger should be launched at agent - startup. Used only for debugging NUnit itself. - - - - - Indicates how to load tests across AppDomains. Values are: - "Default", "None", "Single", "Multiple". Default is "Multiple" - if more than one assembly is loaded in a process. Otherwise, - it is "Single". - - - - - The private binpath used to locate assemblies. Directory paths - is separated by a semicolon. It's an error to specify this and - also set AutoBinPath to true. - - - - - The maximum number of test agents permitted to run simultneously. - Ignored if the ProcessModel is not set or defaulted to Multiple. - - - - - Indicates how to allocate assemblies to processes. Values are: - "Default", "Single", "Separate", "Multiple". Default is "Multiple" - for more than one assembly, "Separate" for a single assembly. - - - - - Indicates the desired runtime to use for the tests. Values - are strings like "net-4.5", "mono-4.0", etc. Default is to - use the target framework for which an assembly was built. - - - - - Bool flag indicating that the test should be run in a 32-bit process - on a 64-bit system. By default, NUNit runs in a 64-bit process on - a 64-bit system. Ignored if set on a 32-bit system. - - - - - Indicates that test runners should be disposed after the tests are executed - - - - - Bool flag indicating that the test assemblies should be shadow copied. - Defaults to false. - - - - - Integer value in milliseconds for the default timeout value - for test cases. If not specified, there is no timeout except - as specified by attributes on the tests themselves. - - - - - A TextWriter to which the internal trace will be sent. - - - - - A list of tests to be loaded. - - - - - The number of test threads to run for the assembly. If set to - 1, a single queue is used. If set to 0, tests are executed - directly, without queuing. - - - - - The random seed to be used for this assembly. If specified - as the value reported from a prior run, the framework should - generate identical random values for tests as were used for - that run, provided that no change has been made to the test - assembly. Default is a random value itself. - - - - - If true, execution stops after the first error or failure. - - - - - If true, use of the event queue is suppressed and test events are synchronous. - - - - - The different targets a test action attribute can be applied to - - - - - Default target, which is determined by where the action attribute is attached - - - - - Target a individual test case - - - - - Target a suite of test cases - - - - - DefaultTestAssemblyBuilder loads a single assembly and builds a TestSuite - containing test fixtures present in the assembly. - - - - - The ITestAssemblyBuilder interface is implemented by a class - that is able to build a suite of tests given an assembly or - an assembly filename. - - - - - Build a suite of tests from a provided assembly - - The assembly from which tests are to be built - A dictionary of options to use in building the suite - A TestSuite containing the tests found in the assembly - - - - Build a suite of tests given the filename of an assembly - - The filename of the assembly from which tests are to be built - A dictionary of options to use in building the suite - A TestSuite containing the tests found in the assembly - - - - The default suite builder used by the test assembly builder. - - - - - Initializes a new instance of the class. - - - - - Build a suite of tests from a provided assembly - - The assembly from which tests are to be built - A dictionary of options to use in building the suite - - A TestSuite containing the tests found in the assembly - - - - - Build a suite of tests given the filename of an assembly - - The filename of the assembly from which tests are to be built - A dictionary of options to use in building the suite - - A TestSuite containing the tests found in the assembly - - - - - FrameworkController provides a facade for use in loading, browsing - and running tests without requiring a reference to the NUnit - framework. All calls are encapsulated in constructors for - this class and its nested classes, which only require the - types of the Common Type System as arguments. - - The controller supports four actions: Load, Explore, Count and Run. - They are intended to be called by a driver, which should allow for - proper sequencing of calls. Load must be called before any of the - other actions. The driver may support other actions, such as - reload on run, by combining these calls. - - - - - A MarshalByRefObject that lives forever - - - - - Construct a FrameworkController using the default builder and runner. - - The AssemblyName or path to the test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - - - - Construct a FrameworkController using the default builder and runner. - - The test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - - - - Construct a FrameworkController, specifying the types to be used - for the runner and builder. This constructor is provided for - purposes of development. - - The full AssemblyName or the path to the test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - The Type of the test runner - The Type of the test builder - - - - Construct a FrameworkController, specifying the types to be used - for the runner and builder. This constructor is provided for - purposes of development. - - The test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - The Type of the test runner - The Type of the test builder - - - - Inserts settings element - - Target node - Settings dictionary - The new node - - - - Gets the ITestAssemblyBuilder used by this controller instance. - - The builder. - - - - Gets the ITestAssemblyRunner used by this controller instance. - - The runner. - - - - Gets the AssemblyName or the path for which this FrameworkController was created - - - - - Gets the Assembly for which this - - - - - Gets a dictionary of settings for the FrameworkController - - - - - FrameworkControllerAction is the base class for all actions - performed against a FrameworkController. - - - - - LoadTestsAction loads a test into the FrameworkController - - - - - LoadTestsAction loads the tests in an assembly. - - The controller. - The callback handler. - - - - ExploreTestsAction returns info about the tests in an assembly - - - - - Initializes a new instance of the class. - - The controller for which this action is being performed. - Filter used to control which tests are included (NYI) - The callback handler. - - - - CountTestsAction counts the number of test cases in the loaded TestSuite - held by the FrameworkController. - - - - - Construct a CountsTestAction and perform the count of test cases. - - A FrameworkController holding the TestSuite whose cases are to be counted - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - RunTestsAction runs the loaded TestSuite held by the FrameworkController. - - - - - Construct a RunTestsAction and run all tests in the loaded TestSuite. - - A FrameworkController holding the TestSuite to run - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - RunAsyncAction initiates an asynchronous test run, returning immediately - - - - - Construct a RunAsyncAction and run all tests in the loaded TestSuite. - - A FrameworkController holding the TestSuite to run - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - StopRunAction stops an ongoing run. - - - - - Construct a StopRunAction and stop any ongoing run. If no - run is in process, no error is raised. - - The FrameworkController for which a run is to be stopped. - True the stop should be forced, false for a cooperative stop. - >A callback handler used to report results - A forced stop will cause threads and processes to be killed as needed. - - - - The ITestAssemblyRunner interface is implemented by classes - that are able to execute a suite of tests loaded - from an assembly. - - - - - Loads the tests found in an Assembly, returning an - indication of whether or not the load succeeded. - - File name of the assembly to load - Dictionary of options to use in loading the test - An ITest representing the loaded tests - - - - Loads the tests found in an Assembly, returning an - indication of whether or not the load succeeded. - - The assembly to load - Dictionary of options to use in loading the test - An ITest representing the loaded tests - - - - Count Test Cases using a filter - - The filter to apply - The number of test cases found - - - - Run selected tests and return a test result. The test is run synchronously, - and the listener interface is notified as it progresses. - - Interface to receive ITestListener notifications. - A test filter used to select tests to be run - - - - Run selected tests asynchronously, notifying the listener interface as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - - - Wait for the ongoing run to complete. - - Time to wait in milliseconds - True if the run completed, otherwise false - - - - Signal any test run that is in process to stop. Return without error if no test is running. - - If true, kill any test-running threads - - - - Gets the tree of loaded tests, or null if - no tests have been loaded. - - - - - Gets the tree of test results, if the test - run is completed, otherwise null. - - - - - Indicates whether a test has been loaded - - - - - Indicates whether a test is currently running - - - - - Indicates whether a test run is complete - - - - - Implementation of ITestAssemblyRunner - - - - - Initializes a new instance of the class. - - The builder. - - - - Loads the tests found in an Assembly - - File name of the assembly to load - Dictionary of option settings for loading the assembly - True if the load was successful - - - - Loads the tests found in an Assembly - - The assembly to load - Dictionary of option settings for loading the assembly - True if the load was successful - - - - Count Test Cases using a filter - - The filter to apply - The number of test cases found - - - - Run selected tests and return a test result. The test is run synchronously, - and the listener interface is notified as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - - - - Run selected tests asynchronously, notifying the listener interface as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - RunAsync is a template method, calling various abstract and - virtual methods to be overridden by derived classes. - - - - - Wait for the ongoing run to complete. - - Time to wait in milliseconds - True if the run completed, otherwise false - - - - Initiate the test run. - - - - - Signal any test run that is in process to stop. Return without error if no test is running. - - If true, kill any test-running threads - - - - Create the initial TestExecutionContext used to run tests - - The ITestListener specified in the RunAsync call - - - - Handle the the Completed event for the top level work item - - - - - The tree of tests that was loaded by the builder - - - - - The test result, if a run has completed - - - - - Indicates whether a test is loaded - - - - - Indicates whether a test is running - - - - - Indicates whether a test run is complete - - - - - Our settings, specified when loading the assembly - - - - - The top level WorkItem created for the assembly as a whole - - - - - The TestExecutionContext for the top level WorkItem - - - - - The Assert class contains a collection of static methods that - implement the most common assertions used in NUnit. - - - - - Verifies that the first int is greater than the second - int. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first int is greater than the second - int. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - - - - We don't actually want any instances of this object, but some people - like to inherit from it to add other static methods. Hence, the - protected constructor disallows any instances of this object. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - The message to initialize the with. - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - - - - Throws an with the message and arguments - that are passed in. This is used by the other Assert functions. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This is used by the other Assert functions. - - The message to initialize the with. - - - - Throws an . - This is used by the other Assert functions. - - - - - Throws an with the message and arguments - that are passed in. This causes the test to be reported as ignored. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This causes the test to be reported as ignored. - - The message to initialize the with. - - - - Throws an . - This causes the test to be reported as ignored. - - - - - Throws an with the message and arguments - that are passed in. This causes the test to be reported as inconclusive. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This causes the test to be reported as inconclusive. - - The message to initialize the with. - - - - Throws an . - This causes the test to be reported as Inconclusive. - - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - - - - Helper for Assert.AreEqual(double expected, double actual, ...) - allowing code generation to work consistently. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestDelegate - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestDelegate - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - A TestDelegate - - - - Verifies that a delegate does not throw an exception - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate does not throw an exception. - - A TestDelegate - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - - This method is provided for use by VB developers needing to test - the value of properties with private setters. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Delegate used by tests that execute code and - capture any thrown exception. - - - - - AssertionHelper is an optional base class for user tests, - allowing the use of shorter ids for constraints and - asserts and avoiding conflict with the definition of - , from which it inherits much of its - behavior, in certain mock object frameworks. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that fails if the actual - value matches the pattern supplied as an argument. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically to - . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically to . - - The evaluated condition - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Returns a ListMapper based on a collection. - - The original collection - - - - - Provides static methods to express the assumptions - that must be met for a test to give a meaningful - result. If an assumption is not met, the test - should produce an inconclusive result. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the - method throws an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Provides the Author of a test or test fixture. - - - - - PropertyAttribute is used to attach information to a test as a name/value pair.. - - - - - The abstract base class for all custom attributes defined by NUnit. - - - - - Default constructor - - - - - The IApplyToTest interface is implemented by self-applying - attributes that modify the state of a test in some way. - - - - - Modifies a test as defined for the specific attribute. - - The test to modify - - - - Construct a PropertyAttribute with a name and string value - - The name of the property - The property value - - - - Construct a PropertyAttribute with a name and int value - - The name of the property - The property value - - - - Construct a PropertyAttribute with a name and double value - - The name of the property - The property value - - - - Constructor for derived classes that set the - property dictionary directly. - - - - - Constructor for use by derived classes that use the - name of the type as the property name. Derived classes - must ensure that the Type of the property value is - a standard type supported by the BCL. Any custom - types will cause a serialization Exception when - in the client. - - - - - Modifies a test by adding properties to it. - - The test to modify - - - - Gets the property dictionary for this attribute - - - - - Initializes a new instance of the class. - - The name of the author. - - - - Initializes a new instance of the class. - - The name of the author. - The email address of the author. - - - - Attribute used to apply a category to a test - - - - - The name of the category - - - - - Construct attribute for a given category based on - a name. The name may not contain the characters ',', - '+', '-' or '!'. However, this is not checked in the - constructor since it would cause an error to arise at - as the test was loaded without giving a clear indication - of where the problem is located. The error is handled - in NUnitFramework.cs by marking the test as not - runnable. - - The name of the category - - - - Protected constructor uses the Type name as the name - of the category. - - - - - Modifies a test by adding a category to it. - - The test to modify - - - - The name of the category - - - - - Marks a test to use a combinatorial join of any argument - data provided. Since this is the default, the attribute is - optional. - - - - - Marks a test to use a particular CombiningStrategy to join - any parameter data provided. Since this is the default, the - attribute is optional. - - - - - The ITestBuilder interface is exposed by a class that knows how to - build one or more TestMethods from a MethodInfo. In general, it is exposed - by an attribute, which has additional information available to provide - the necessary test parameters to distinguish the test cases built. - - - - - Build one or more TestMethods from the provided MethodInfo. - - The method to be used as a test - The TestSuite to which the method will be added - A TestMethod object - - - - Construct a CombiningStrategyAttribute incorporating an object - that implements ICombiningStrategy. - - Combining strategy to be used - - - - Construct a CombiningStrategyAttribute incorporating an object - that implements ICombiningStrategy. This constructor is provided - for CLS compliance. - - Combining strategy to be used - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Modify the test by adding the name of the combining strategy - to the properties. - - The test to modify - - - - Default constructor - - - - - CultureAttribute is used to mark a test fixture or an - individual method as applying to a particular Culture only. - - - - - Abstract base for Attributes that are used to include tests - in the test run based on environmental settings. - - - - - Constructor with no included items specified, for use - with named property syntax. - - - - - Constructor taking one or more included items - - Comma-delimited list of included items - - - - Name of the item that is needed in order for - a test to run. Multiple items may be given, - separated by a comma. - - - - - Name of the item to be excluded. Multiple items - may be given, separated by a comma. - - - - - The reason for including or excluding the test - - - - - Constructor with no cultures specified, for use - with named property syntax. - - - - - Constructor taking one or more cultures - - Comma-deliminted list of cultures - - - - Causes a test to be skipped if this CultureAttribute is not satisfied. - - The test to modify - - - - Tests to determine if the current culture is supported - based on the properties of this attribute. - - True, if the current culture is supported - - - - Test to determine if the a particular culture or comma- - delimited set of cultures is in use. - - Name of the culture or comma-separated list of culture ids - True if the culture is in use on the system - - - - Test to determine if one of a collection of cultures - is being used currently. - - - - - - - The abstract base class for all data-providing attributes - defined by NUnit. Used to select all data sources for a - method, class or parameter. - - - - - Default constructor - - - - - Used to mark a field for use as a datapoint when executing a theory - within the same fixture that requires an argument of the field's Type. - - - - - Used to mark a field, property or method providing a set of datapoints to - be used in executing any theories within the same fixture that require an - argument of the Type provided. The data source may provide an array of - the required Type or an . - Synonymous with DatapointSourceAttribute. - - - - - Used to mark a field, property or method providing a set of datapoints to - be used in executing any theories within the same fixture that require an - argument of the Type provided. The data source may provide an array of - the required Type or an . - Synonymous with DatapointsAttribute. - - - - - Attribute used to provide descriptive text about a - test case or fixture. - - - - - Construct a description Attribute - - The text of the description - - - - ExplicitAttribute marks a test or test fixture so that it will - only be run if explicitly executed from the gui or command line - or if it is included by use of a filter. The test will not be - run simply because an enclosing suite is run. - - - - - Default constructor - - - - - Constructor with a reason - - The reason test is marked explicit - - - - Modifies a test by marking it as explicit. - - The test to modify - - - - Attribute used to mark a test that is to be ignored. - Ignored tests result in a warning message when the - tests are run. - - - - - Constructs the attribute giving a reason for ignoring the test - - The reason for ignoring the test - - - - Modifies a test by marking it as Ignored. - - The test to modify - - - - The date in the future to stop ignoring the test as a string in UTC time. - For example for a date and time, "2014-12-25 08:10:00Z" or for just a date, - "2014-12-25". If just a date is given, the Ignore will expire at midnight UTC. - - - Once the ignore until date has passed, the test will be marked - as runnable. Tests with an ignore until date will have an IgnoreUntilDate - property set which will appear in the test results. - - The string does not contain a valid string representation of a date and time. - - - - LevelOfParallelismAttribute is used to set the number of worker threads - that may be allocated by the framework for running tests. - - - - - Construct a LevelOfParallelismAttribute. - - The number of worker threads to be created by the framework. - - - - Summary description for MaxTimeAttribute. - - - - - Objects implementing this interface are used to wrap - the entire test, including SetUp and TearDown. - - - - - ICommandWrapper is implemented by attributes and other - objects able to wrap a TestCommand with another command. - - - Attributes or other objects should implement one of the - derived interfaces, rather than this one, since they - indicate in which part of the command chain the wrapper - should be applied. - - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - Construct a MaxTimeAttribute, given a time in milliseconds. - - The maximum elapsed time in milliseconds - - - - Attribute used to identify a method that is called once - to perform setup before any child tests are run. - - - - - Attribute used to identify a method that is called once - after all the child tests have run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Marks a test to use a pairwise join of any argument - data provided. Arguments will be combined in such a - way that all possible pairs of arguments are used. - - - - - Default constructor - - - - - ParallelizableAttribute is used to mark tests that may be run in parallel. - - - - - The IApplyToContext interface is implemented by attributes - that want to make changes to the execution context before - a test is run. - - - - - Apply changes to the execution context - - The execution context - - - - Construct a ParallelizableAttribute using default ParallelScope.Self. - - - - - Construct a ParallelizableAttribute with a specified scope. - - The ParallelScope associated with this attribute. - - - - Modify the context to be used for child tests - - The current TestExecutionContext - - - - The ParallelScope enumeration permits specifying the degree to - which a test and its descendants may be run in parallel. - - - - - No Parallelism is permitted - - - - - The test itself may be run in parallel with others at the same level - - - - - Descendants of the test may be run in parallel with one another - - - - - Descendants of the test down to the level of TestFixtures may be run in parallel - - - - - RandomAttribute is used to supply a set of random _values - to a single parameter of a parameterized test. - - - - - The IParameterDataSource interface is implemented by types - that can provide data for a test method parameter. - - - - - Gets an enumeration of data items for use as arguments - for a test method parameter. - - The parameter for which data is needed - An enumeration containing individual data items - - - - Construct a random set of values appropriate for the Type of the - parameter on which the attribute appears, specifying only the count. - - - - - - Construct a set of ints within a specified range - - - - - Construct a set of unsigned ints within a specified range - - - - - Construct a set of longs within a specified range - - - - - Construct a set of unsigned longs within a specified range - - - - - Construct a set of shorts within a specified range - - - - - Construct a set of unsigned shorts within a specified range - - - - - Construct a set of doubles within a specified range - - - - - Construct a set of floats within a specified range - - - - - Construct a set of bytes within a specified range - - - - - Construct a set of sbytes within a specified range - - - - - Get the collection of _values to be used as arguments. - - - - - RangeAttribute is used to supply a range of _values to an - individual parameter of a parameterized test. - - - - - ValuesAttribute is used to provide literal arguments for - an individual parameter of a test. - - - - - The collection of data to be returned. Must - be set by any derived attribute classes. - We use an object[] so that the individual - elements may have their type changed in GetData - if necessary - - - - - Constructs for use with an Enum parameter. Will pass every enum - value in to the test. - - - - - Construct with one argument - - - - - - Construct with two arguments - - - - - - - Construct with three arguments - - - - - - - - Construct with an array of arguments - - - - - - Get the collection of _values to be used as arguments - - - - - Construct a range of ints using default step of 1 - - - - - - - Construct a range of ints specifying the step size - - - - - - - - Construct a range of unsigned ints using default step of 1 - - - - - - - Construct a range of unsigned ints specifying the step size - - - - - - - - Construct a range of longs using a default step of 1 - - - - - - - Construct a range of longs - - - - - - - - Construct a range of unsigned longs using default step of 1 - - - - - - - Construct a range of unsigned longs specifying the step size - - - - - - - - Construct a range of doubles - - - - - - - - Construct a range of floats - - - - - - - - RepeatAttribute may be applied to test case in order - to run it multiple times. - - - - - Construct a RepeatAttribute - - The number of times to run the test - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - The test command for the RepeatAttribute - - - - - TODO: Documentation needed for class - - - - - TestCommand is the abstract base class for all test commands - in the framework. A TestCommand represents a single stage in - the execution of a test, e.g.: SetUp/TearDown, checking for - Timeout, verifying the returned result from a method, etc. - - TestCommands may decorate other test commands so that the - execution of a lower-level command is nested within that - of a higher level command. All nested commands are executed - synchronously, as a single unit. Scheduling test execution - on separate threads is handled at a higher level, using the - task dispatcher. - - - - - Construct a TestCommand for a test. - - The test to be executed - - - - Runs the test in a specified context, returning a TestResult. - - The TestExecutionContext to be used for running the test. - A TestResult - - - - Gets the test associated with this command. - - - - TODO: Documentation needed for field - - - - TODO: Documentation needed for constructor - - - - - - Initializes a new instance of the class. - - The inner command. - The number of repetitions - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - RepeatAttribute may be applied to test case in order - to run it multiple times. - - - - - Construct a RepeatAttribute - - The number of times to run the test - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - The test command for the RetryAttribute - - - - - Initializes a new instance of the class. - - The inner command. - The number of repetitions - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - Marks a test to use a Sequential join of any argument - data provided. Arguments will be combined into test cases, - taking the next value of each argument until all are used. - - - - - Default constructor - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - SetUpFixtureAttribute is used to identify a SetUpFixture - - - - - The IFixtureBuilder interface is exposed by a class that knows how to - build a TestFixture from one or more Types. In general, it is exposed - by an attribute, but may be implemented in a helper class used by the - attribute in some cases. - - - - - Build one or more TestFixtures from type provided. At least one - non-null TestSuite must always be returned, since the method is - generally called because the user has marked the target class as - a fixture. If something prevents the fixture from being used, it - will be returned nonetheless, labelled as non-runnable. - - The type info of the fixture to be used. - A TestSuite object or one derived from TestSuite. - - - - Build a SetUpFixture from type provided. Normally called for a Type - on which the attribute has been placed. - - The type info of the fixture to be used. - A SetUpFixture object as a TestSuite. - - - - Attribute used to identify a method that is called - immediately after each test is run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Provide actions to execute before and after tests. - - - - - When implemented by an attribute, this interface implemented to provide actions to execute before and after tests. - - - - - Executed before each test is run - - The test that is going to be run. - - - - Executed after each test is run - - The test that has just been run. - - - - Provides the target for the action attribute - - The target for the action attribute - - - - Executed before each test is run - - The test that is going to be run. - - - - Executed after each test is run - - The test that has just been run. - - - - Provides the target for the action attribute - - - - - Adding this attribute to a method within a - class makes the method callable from the NUnit test runner. There is a property - called Description which is optional which you can provide a more detailed test - description. This class cannot be inherited. - - - - [TestFixture] - public class Fixture - { - [Test] - public void MethodToTest() - {} - - [Test(Description = "more detailed description")] - public void TestDescriptionMethod() - {} - } - - - - - - The ISimpleTestBuilder interface is exposed by a class that knows how to - build a single TestMethod from a suitable MethodInfo Types. In general, - it is exposed by an attribute, but may be implemented in a helper class - used by the attribute in some cases. - - - - - Build a TestMethod from the provided MethodInfo. - - The method to be used as a test - The TestSuite to which the method will be added - A TestMethod object - - - - IImplyFixture is an empty marker interface used by attributes like - TestAttribute that cause the class where they are used to be treated - as a TestFixture even without a TestFixtureAttribute. - - Marker interfaces are not usually considered a good practice, but - we use it here to avoid cluttering the attribute hierarchy with - classes that don't contain any extra implementation. - - - - - Modifies a test by adding a description, if not already set. - - The test to modify - - - - Construct a TestMethod from a given method. - - The method for which a test is to be constructed. - The suite to which the test will be added. - A TestMethod - - - - Descriptive text for this test - - - - - The author of this test - - - - - The type that this test is testing - - - - - Gets or sets the expected result. - - The result. - - - - Returns true if an expected result has been set - - - - - TestCaseAttribute is used to mark parameterized test cases - and provide them with their arguments. - - - - - The ITestCaseData interface is implemented by a class - that is able to return complete testcases for use by - a parameterized test method. - - - - - The ITestData interface is implemented by a class that - represents a single instance of a parameterized test. - - - - - Gets the name to be used for the test - - - - - Gets the RunState for this test case. - - - - - Gets the argument list to be provided to the test - - - - - Gets the property dictionary for the test case - - - - - Gets the expected result of the test case - - - - - Returns true if an expected result has been set - - - - - Construct a TestCaseAttribute with a list of arguments. - This constructor is not CLS-Compliant - - - - - - Construct a TestCaseAttribute with a single argument - - - - - - Construct a TestCaseAttribute with a two arguments - - - - - - - Construct a TestCaseAttribute with a three arguments - - - - - - - - Performs several special conversions allowed by NUnit in order to - permit arguments with types that cannot be used in the constructor - of an Attribute such as TestCaseAttribute or to simplify their use. - - The arguments to be converted - The ParameterInfo array for the method - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Gets or sets the name of the test. - - The name of the test. - - - - Gets or sets the RunState of this test case. - - - - - Gets the list of arguments to a test case - - - - - Gets the properties of the test case - - - - - Gets or sets the expected result. - - The result. - - - - Returns true if the expected result has been set - - - - - Gets or sets the description. - - The description. - - - - The author of this test - - - - - The type that this test is testing - - - - - Gets or sets the reason for ignoring the test - - - - - Gets or sets a value indicating whether this is explicit. - - - true if explicit; otherwise, false. - - - - - Gets or sets the reason for not running the test. - - The reason. - - - - Gets or sets the ignore reason. When set to a non-null - non-empty value, the test is marked as ignored. - - The ignore reason. - - - - Gets and sets the category for this test case. - May be a comma-separated list of categories. - - - - - TestCaseSourceAttribute indicates the source to be used to - provide test cases for a test method. - - - - - Construct with the name of the method, property or field that will provide data - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Construct with a Type - - The type that will provide data - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The IMethod for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Returns a set of ITestCaseDataItems for use as arguments - to a parameterized test method. - - The method for which data is needed. - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - Gets or sets the category associated with every fixture created from - this attribute. May be a single category or a comma-separated list. - - - - - TestFixtureAttribute is used to mark a class that represents a TestFixture. - - - - - The ITestCaseData interface is implemented by a class - that is able to return the data required to create an - instance of a parameterized test fixture. - - - - - Get the TypeArgs if separately set - - - - - Default constructor - - - - - Construct with a object[] representing a set of arguments. - In .NET 2.0, the arguments may later be separated into - type arguments and constructor arguments. - - - - - - Build a fixture from type provided. Normally called for a Type - on which the attribute has been placed. - - The type info of the fixture to be used. - A an IEnumerable holding one TestFixture object. - - - - Gets or sets the name of the test. - - The name of the test. - - - - Gets or sets the RunState of this test fixture. - - - - - The arguments originally provided to the attribute - - - - - Properties pertaining to this fixture - - - - - Get or set the type arguments. If not set - explicitly, any leading arguments that are - Types are taken as type arguments. - - - - - Descriptive text for this fixture - - - - - The author of this fixture - - - - - The type that this fixture is testing - - - - - Gets or sets the ignore reason. May set RunState as a side effect. - - The ignore reason. - - - - Gets or sets the reason for not running the fixture. - - The reason. - - - - Gets or sets the ignore reason. When set to a non-null - non-empty value, the test is marked as ignored. - - The ignore reason. - - - - Gets or sets a value indicating whether this is explicit. - - - true if explicit; otherwise, false. - - - - - Gets and sets the category for this fixture. - May be a comma-separated list of categories. - - - - - Attribute used to identify a method that is - called before any tests in a fixture are run. - - - - - TestCaseSourceAttribute indicates the source to be used to - provide test fixture instances for a test class. - - - - - Error message string is public so the tests can use it - - - - - Construct with the name of the method, property or field that will provide data - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Construct with a Type - - The type that will provide data - - - - Construct one or more TestFixtures from a given Type, - using available parameter data. - - The TypeInfo for which fixures are to be constructed. - One or more TestFixtures as TestSuite - - - - Returns a set of ITestFixtureData items for use as arguments - to a parameterized test fixture. - - The type for which data is needed. - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - Gets or sets the category associated with every fixture created from - this attribute. May be a single category or a comma-separated list. - - - - - Attribute used to identify a method that is called after - all the tests in a fixture have run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Indicates which class the test or test fixture is testing - - - - - Initializes a new instance of the class. - - The type that is being tested. - - - - Initializes a new instance of the class. - - The type that is being tested. - - - - Adding this attribute to a method within a - class makes the method callable from the NUnit test runner. There is a property - called Description which is optional which you can provide a more detailed test - description. This class cannot be inherited. - - - - [TestFixture] - public class Fixture - { - [Test] - public void MethodToTest() - {} - - [Test(Description = "more detailed description")] - public void TestDescriptionMethod() - {} - } - - - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - ValueSourceAttribute indicates the source to be used to - provide data for one parameter of a test method. - - - - - Construct with the name of the factory - for use with languages - that don't support params arrays. - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - for use with languages - that don't support params arrays. - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Gets an enumeration of data items for use as arguments - for a test method parameter. - - The parameter for which data is needed - - An enumeration containing individual data items - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - A set of Assert methods operating on one or more collections - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable containing objects to be considered - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the superset does not contain the subset - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - - - - Asserts that the superset does not contain the subset - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the superset contains the subset. - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - - - - Asserts that the superset contains the subset. - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the subset does not contain the superset - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - - - - Asserts that the subset does not contain the superset - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the subset contains the superset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - - - - Asserts that the subset contains the superset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array,list or other collection is empty - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array,list or other collection is empty - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - - - - Provides a platform-independent methods for getting attributes - for use by AttributeConstraint and AttributeExistsConstraint. - - - - - Gets the custom attributes from the given object. - - Portable libraries do not have an ICustomAttributeProvider, so we need to cast to each of - it's direct subtypes and try to get attributes off those instead. - The actual. - Type of the attribute. - if set to true [inherit]. - A list of the given attribute on the given object. - - - - Specifies flags that control binding and the way in which the search for members - and types is conducted by reflection. - - - - - Specifies no binding flag. - - - - - Specifies that only members declared at the level of the supplied type's hierarchy - should be considered. Inherited members are not considered. - - - - - Specifies that instance members are to be included in the search. - - - - - Specifies that static members are to be included in the search. - - - - - Specifies that public members are to be included in the search. - - - - - Specifies that non-public members are to be included in the search. - - - - - Specifies that public and protected static members up the hierarchy should be - returned. Private static members in inherited classes are not returned. Static - members include fields, methods, events, and properties. Nested types are not - returned. - - - - - A shim of the .NET interface for platforms that do not support it. - Used to indicate that a control can be the target of a callback event on the server. - - - - - Processes a callback event that targets a control. - - - - - - Returns the results of a callback event that targets a control. - - - - - - Some path based methods that we need even in the Portable framework which - does not have the System.IO.Path class - - - - - Windows directory separator - - - - - Alternate directory separator - - - - - A volume separator character. - - - - - Get the file name and extension of the specified path string. - - The path string from which to obtain the file name and extension. - The filename as a . If the last character of is a directory or volume separator character, this method returns . If is null, this method returns null. - - - - Provides NUnit specific extensions to aid in Reflection - across multiple frameworks - - - This version of the class allows direct calls on Type on - those platforms that would normally require use of - GetTypeInfo(). - - - - - Returns an array of generic arguments for the give type - - - - - - - Gets the constructor with the given parameter types - - - - - - - - Gets the constructors for a type - - - - - - - - - - - - - - - - - - - - - - - Gets declared or inherited interfaces on this type - - - - - - - Gets the member on a given type by name. BindingFlags ARE IGNORED. - - - - - - - - - Gets all members on a given type. BindingFlags ARE IGNORED. - - - - - - - - Gets field of the given name on the type - - - - - - - - Gets property of the given name on the type - - - - - - - - Gets property of the given name on the type - - - - - - - - - Gets the method with the given name and parameter list - - - - - - - - Gets the method with the given name and parameter list - - - - - - - - - Gets the method with the given name and parameter list - - - - - - - - - Gets public methods on the given type - - - - - - - Gets methods on a type - - - - - - - - Determines if one type can be implicitly converted from another - - - - - - - - Extensions to the various MemberInfo derived classes - - - - - Returns the get method for the given property - - - - - - - - Returns an array of custom attributes of the specified type applied to this member - - Portable throws an argument exception if T does not - derive from Attribute. NUnit uses interfaces to find attributes, thus - this method - - - - Returns an array of custom attributes of the specified type applied to this parameter - - - - - Returns an array of custom attributes of the specified type applied to this assembly - - - - - Extensions for Assembly that are not available in portable - - - - - DNX does not have a version of GetCustomAttributes on Assembly that takes an inherit - parameter since it doesn't make sense on Assemblies. This version just ignores the - inherit parameter. - - The assembly - The type of attribute you are looking for - Ignored - - - - - Gets the types in a given assembly - - - - - - - A shim of the .NET attribute for platforms that do not support it. - - - - - This class is a System.Diagnostics.Stopwatch on operating systems that support it. On those that don't, - it replicates the functionality at the resolution supported. - - - - - Gets the current number of ticks in the timer mechanism. - - - If the Stopwatch class uses a high-resolution performance counter, GetTimestamp returns the current - value of that counter. If the Stopwatch class uses the system timer, GetTimestamp returns the current - DateTime.Ticks property of the DateTime.Now instance. - - A long integer representing the tick counter value of the underlying timer mechanism. - - - - Stops time interval measurement and resets the elapsed time to zero. - - - - - Starts, or resumes, measuring elapsed time for an interval. - - - - - Initializes a new Stopwatch instance, sets the elapsed time property to zero, and starts measuring elapsed time. - - A Stopwatch that has just begun measuring elapsed time. - - - - Stops measuring elapsed time for an interval. - - - - - Returns a string that represents the current object. - - - A string that represents the current object. - - - - - Gets the total elapsed time measured by the current instance, in milliseconds. - - - - - Gets a value indicating whether the Stopwatch timer is running. - - - - - Gets the frequency of the timer as the number of ticks per second. - - - - - Indicates whether the timer is based on a high-resolution performance counter. - - - - - AllItemsConstraint applies another constraint to each - item in a collection, succeeding if they all succeed. - - - - - Abstract base class used for prefixes - - - - - The Constraint class is the base of all built-in constraints - within NUnit. It provides the operator overloads used to combine - constraints. - - - - - Interface for all constraints - - - - - The IResolveConstraint interface is implemented by all - complete and resolvable constraints and expressions. - - - - - Return the top-level constraint for this expression - - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - The default implementation simply dereferences the value but - derived classes may override it to provide for delayed processing. - - A reference to the value to be tested - A ConstraintResult - - - - The display name of this Constraint for use by ToString(). - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Arguments provided to this Constraint, for use in - formatting the description. - - - - - The ConstraintBuilder holding this constraint - - - - - Construct a constraint with optional arguments - - Arguments to be saved - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - The default implementation simply dereferences the value but - derived classes may override it to provide for delayed processing. - - A reference to the value to be tested - A ConstraintResult - - - - Default override of ToString returns the constraint DisplayName - followed by any arguments within angle brackets. - - - - - - Returns the string representation of this constraint - - - - - This operator creates a constraint that is satisfied only if both - argument constraints are satisfied. - - - - - This operator creates a constraint that is satisfied if either - of the argument constraints is satisfied. - - - - - This operator creates a constraint that is satisfied if the - argument constraint is not satisfied. - - - - - Resolves any pending operators and returns the resolved constraint. - - - - - The display name of this Constraint for use by ToString(). - The default value is the name of the constraint with - trailing "Constraint" removed. Derived classes may set - this to another name in their constructors. - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Arguments provided to this Constraint, for use in - formatting the description. - - - - - The ConstraintBuilder holding this constraint - - - - - Returns a ConstraintExpression by appending And - to the current constraint. - - - - - Returns a ConstraintExpression by appending And - to the current constraint. - - - - - Returns a ConstraintExpression by appending Or - to the current constraint. - - - - - The base constraint - - - - - Prefix used in forming the constraint description - - - - - Construct given a base constraint - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Construct an AllItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - failing if any item fails. - - - - - - - AndConstraint succeeds only if both members succeed. - - - - - BinaryConstraint is the abstract base of all constraints - that combine two other constraints in some fashion. - - - - - The first constraint being combined - - - - - The second constraint being combined - - - - - Construct a BinaryConstraint from two other constraints - - The first constraint - The second constraint - - - - Create an AndConstraint from two other constraints - - The first constraint - The second constraint - - - - Apply both member constraints to an actual value, succeeding - succeeding only if both of them succeed. - - The actual value - True if the constraints both succeeded - - - - Gets text describing a constraint - - - - - Contain the result of matching a against an actual value. - - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - The status of the new ConstraintResult. - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - If true, applies a status of Success to the result, otherwise Failure. - - - - Write the failure message to the MessageWriter provided - as an argument. The default implementation simply passes - the result and the actual value to the writer, which - then displays the constraint description and the value. - - Constraints that need to provide additional details, - such as where the error occured can override this. - - The MessageWriter on which to display the message - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - The actual value that was passed to the method. - - - - - Gets and sets the ResultStatus for this result. - - - - - True if actual value meets the Constraint criteria otherwise false. - - - - - Display friendly name of the constraint. - - - - - Description of the constraint may be affected by the state the constraint had - when was performed against the actual value. - - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - AssignableFromConstraint is used to test that an object - can be assigned from a given Type. - - - - - TypeConstraint is the abstract base for constraints - that take a Type as their expected value. - - - - - The expected Type used by the constraint - - - - - The type of the actual argument to which the constraint was applied - - - - - Construct a TypeConstraint for a given Type - - The expected type for the constraint - Prefix used in forming the constraint description - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - Construct an AssignableFromConstraint for the type provided - - - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - AssignableToConstraint is used to test that an object - can be assigned to a given Type. - - - - - Construct an AssignableToConstraint for the type provided - - - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - AttributeConstraint tests that a specified attribute is present - on a Type or other provider and that the value of the attribute - satisfies some other constraint. - - - - - Constructs an AttributeConstraint for a specified attribute - Type and base constraint. - - - - - - - Determines whether the Type or other provider has the - expected attribute and if its value matches the - additional constraint specified. - - - - - Returns a string representation of the constraint. - - - - - AttributeExistsConstraint tests for the presence of a - specified attribute on a Type. - - - - - Constructs an AttributeExistsConstraint for a specific attribute Type - - - - - - Tests whether the object provides the expected attribute. - - A Type, MethodInfo, or other ICustomAttributeProvider - True if the expected attribute is present, otherwise false - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionConstraint is the abstract base class for - constraints that operate on collections. - - - - - Construct an empty CollectionConstraint - - - - - Construct a CollectionConstraint - - - - - - Determines whether the specified enumerable is empty. - - The enumerable. - - true if the specified enumerable is empty; otherwise, false. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Protected method to be implemented by derived classes - - - - - - - CollectionContainsConstraint is used to test whether a collection - contains an expected object as a member. - - - - - CollectionItemsEqualConstraint is the abstract base class for all - collection constraints that apply some notion of item equality - as a part of their operation. - - - - - Construct an empty CollectionConstraint - - - - - Construct a CollectionConstraint - - - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Compares two collection members for equality - - - - - Return a new CollectionTally for use in making tests - - The collection to be included in the tally - - - - Flag the constraint to ignore case and return self. - - - - - Construct a CollectionContainsConstraint - - - - - - Test whether the expected item is contained in the collection - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Gets the expected object - - - - - CollectionEquivalentConstraint is used to determine whether two - collections are equivalent. - - - - - Construct a CollectionEquivalentConstraint - - - - - - Test whether two collections are equivalent - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionOrderedConstraint is used to test whether a collection is ordered. - - - - - Construct a CollectionOrderedConstraint - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use a and returns self. - - - - - Modifies the constraint to test ordering by the value of - a specified property and returns self. - - - - - Test whether the collection is ordered - - - - - - - Returns the string representation of the constraint. - - - - - - If used performs a reverse comparison - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionSubsetConstraint is used to determine whether - one collection is a subset of another - - - - - Construct a CollectionSubsetConstraint - - The collection that the actual value is expected to be a subset of - - - - Test whether the actual collection is a subset of - the expected collection provided. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionSupersetConstraint is used to determine whether - one collection is a superset of another - - - - - Construct a CollectionSupersetConstraint - - The collection that the actual value is expected to be a superset of - - - - Test whether the actual collection is a superset of - the expected collection provided. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionTally counts (tallies) the number of - occurrences of each object in one or more enumerations. - - - - - Construct a CollectionTally object from a comparer and a collection - - - - - Try to remove an object from the tally - - The object to remove - True if successful, false if the object was not found - - - - Try to remove a set of objects from the tally - - The objects to remove - True if successful, false if any object was not found - - - - The number of objects remaining in the tally - - - - - ComparisonAdapter class centralizes all comparisons of - _values in NUnit, adapting to the use of any provided - , - or . - - - - - Returns a ComparisonAdapter that wraps an - - - - - Returns a ComparisonAdapter that wraps an - - - - - Returns a ComparisonAdapter that wraps a - - - - - Compares two objects - - - - - Gets the default ComparisonAdapter, which wraps an - NUnitComparer object. - - - - - Construct a ComparisonAdapter for an - - - - - Compares two objects - - - - - - - - Construct a default ComparisonAdapter - - - - - ComparerAdapter extends and - allows use of an or - to actually perform the comparison. - - - - - Construct a ComparisonAdapter for an - - - - - Compare a Type T to an object - - - - - Construct a ComparisonAdapter for a - - - - - Compare a Type T to an object - - - - - Abstract base class for constraints that compare _values to - determine if one is greater than, equal to or less than - the other. - - - - - The value against which a comparison is to be made - - - - - If true, less than returns success - - - - - if true, equal returns success - - - - - if true, greater than returns success - - - - - ComparisonAdapter to be used in making the comparison - - - - - Initializes a new instance of the class. - - The value against which to make a comparison. - if set to true less succeeds. - if set to true equal succeeds. - if set to true greater succeeds. - String used in describing the constraint. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Modifies the constraint to use an and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Modifies the constraint to use an and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Modifies the constraint to use a and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Delegate used to delay evaluation of the actual value - to be used in evaluating a constraint - - - - - ConstraintBuilder maintains the stacks that are used in - processing a ConstraintExpression. An OperatorStack - is used to hold operators that are waiting for their - operands to be reorganized. a ConstraintStack holds - input constraints as well as the results of each - operator applied. - - - - - Initializes a new instance of the class. - - - - - Appends the specified operator to the expression by first - reducing the operator stack and then pushing the new - operator on the stack. - - The operator to push. - - - - Appends the specified constraint to the expression by pushing - it on the constraint stack. - - The constraint to push. - - - - Sets the top operator right context. - - The right context. - - - - Reduces the operator stack until the topmost item - precedence is greater than or equal to the target precedence. - - The target precedence. - - - - Resolves this instance, returning a Constraint. If the Builder - is not currently in a resolvable state, an exception is thrown. - - The resolved constraint - - - - Gets a value indicating whether this instance is resolvable. - - - true if this instance is resolvable; otherwise, false. - - - - - OperatorStack is a type-safe stack for holding ConstraintOperators - - - - - Initializes a new instance of the class. - - The ConstraintBuilder using this stack. - - - - Pushes the specified operator onto the stack. - - The operator to put onto the stack. - - - - Pops the topmost operator from the stack. - - The topmost operator on the stack - - - - Gets a value indicating whether this is empty. - - true if empty; otherwise, false. - - - - Gets the topmost operator without modifying the stack. - - - - - ConstraintStack is a type-safe stack for holding Constraints - - - - - Initializes a new instance of the class. - - The ConstraintBuilder using this stack. - - - - Pushes the specified constraint. As a side effect, - the constraint's Builder field is set to the - ConstraintBuilder owning this stack. - - The constraint to put onto the stack - - - - Pops this topmost constraint from the stack. - As a side effect, the constraint's Builder - field is set to null. - - The topmost contraint on the stack - - - - Gets a value indicating whether this is empty. - - true if empty; otherwise, false. - - - - ConstraintExpression represents a compound constraint in the - process of being constructed from a series of syntactic elements. - - Individual elements are appended to the expression as they are - reorganized. When a constraint is appended, it is returned as the - value of the operation so that modifiers may be applied. However, - any partially built expression is attached to the constraint for - later resolution. When an operator is appended, the partial - expression is returned. If it's a self-resolving operator, then - a ResolvableConstraintExpression is returned. - - - - - The ConstraintBuilder holding the elements recognized so far - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class passing in a ConstraintBuilder, which may be pre-populated. - - The builder. - - - - Returns a string representation of the expression as it - currently stands. This should only be used for testing, - since it has the side-effect of resolving the expression. - - - - - - Appends an operator to the expression and returns the - resulting expression itself. - - - - - Appends a self-resolving operator to the expression and - returns a new ResolvableConstraintExpression. - - - - - Appends a constraint to the expression and returns that - constraint, which is associated with the current state - of the expression being built. Note that the constraint - is not reduced at this time. For example, if there - is a NotOperator on the stack we don't reduce and - return a NotConstraint. The original constraint must - be returned because it may support modifiers that - are yet to be applied. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns the constraint provided as an argument - used to allow custom - custom constraints to easily participate in the syntax. - - - - - Returns the constraint provided as an argument - used to allow custom - custom constraints to easily participate in the syntax. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - With is currently a NOP - reserved for future use. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - ConstraintStatus represents the status of a ConstraintResult - returned by a Constraint being applied to an actual value. - - - - - The status has not yet been set - - - - - The constraint succeeded - - - - - The constraint failed - - - - - An error occured in applying the constraint (reserved for future use) - - - - - ContainsConstraint tests a whether a string contains a substring - or a collection contains an object. It postpones the decision of - which test to use until the type of the actual argument is known. - This allows testing whether a string is contained in a collection - or as a substring of another string using the same syntax. - - - - - Initializes a new instance of the class. - - The _expected. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Flag the constraint to ignore case and return self. - - - - - DictionaryContainsKeyConstraint is used to test whether a dictionary - contains an expected object as a key. - - - - - Construct a DictionaryContainsKeyConstraint - - - - - - Test whether the expected key is contained in the dictionary - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - DictionaryContainsValueConstraint is used to test whether a dictionary - contains an expected object as a value. - - - - - Construct a DictionaryContainsValueConstraint - - - - - - Test whether the expected value is contained in the dictionary - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyCollectionConstraint tests whether a collection is empty. - - - - - Check that the collection is empty - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyConstraint tests a whether a string or collection is empty, - postponing the decision about which test is applied until the - type of the actual argument is known. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyStringConstraint tests whether a string is empty. - - - - - StringConstraint is the abstract base for constraints - that operate on strings. It supports the IgnoreCase - modifier for string operations. - - - - - The expected value - - - - - Indicates whether tests should be case-insensitive - - - - - Description of this constraint - - - - - Constructs a StringConstraint without an expected value - - - - - Constructs a StringConstraint given an expected value - - The expected value - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Test whether the constraint is satisfied by a given string - - The string to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Modify the constraint to ignore case in matching. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EndsWithConstraint can test whether a string ends - with an expected substring. - - - - - Initializes a new instance of the class. - - The expected string - - - - Test whether the constraint is matched by the actual value. - This is a template method, which calls the IsMatch method - of the derived class. - - - - - - - EqualConstraint is able to compare an actual value with the - expected value provided in its constructor. Two objects are - considered equal if both are null, or if both have the same - value. NUnit has special semantics for some object types. - - - - - NUnitEqualityComparer used to test equality. - - - - - Initializes a new instance of the class. - - The expected value. - - - - Flag the constraint to use a tolerance when determining equality. - - Tolerance value to be used - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Gets the tolerance for this comparison. - - - The tolerance. - - - - - Gets a value indicating whether to compare case insensitive. - - - true if comparing case insensitive; otherwise, false. - - - - - Gets a value indicating whether or not to clip strings. - - - true if set to clip strings otherwise, false. - - - - - Gets the failure points. - - - The failure points. - - - - - Flag the constraint to ignore case and return self. - - - - - Flag the constraint to suppress string clipping - and return self. - - - - - Flag the constraint to compare arrays as collections - and return self. - - - - - Flags the constraint to include - property in comparison of two values. - - - Using this modifier does not allow to use the - constraint modifier. - - - - - Switches the .Within() modifier to interpret its tolerance as - a distance in representable _values (see remarks). - - Self. - - Ulp stands for "unit in the last place" and describes the minimum - amount a given value can change. For any integers, an ulp is 1 whole - digit. For floating point _values, the accuracy of which is better - for smaller numbers and worse for larger numbers, an ulp depends - on the size of the number. Using ulps for comparison of floating - point results instead of fixed tolerances is safer because it will - automatically compensate for the added inaccuracy of larger numbers. - - - - - Switches the .Within() modifier to interpret its tolerance as - a percentage that the actual _values is allowed to deviate from - the expected value. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in days. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in hours. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in minutes. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in seconds. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in milliseconds. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in clock ticks. - - Self - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - The EqualConstraintResult class is tailored for formatting - and displaying the result of an EqualConstraint. - - - - - Construct an EqualConstraintResult - - - - - Write a failure message. Overridden to provide custom - failure messages for EqualConstraint. - - The MessageWriter to write to - - - - Display the failure information for two collections that did not match. - - The MessageWriter on which to display - The expected collection. - The actual collection - The depth of this failure in a set of nested collections - - - - Displays a single line showing the types and sizes of the expected - and actual collections or arrays. If both are identical, the value is - only shown once. - - The MessageWriter on which to display - The expected collection or array - The actual collection or array - The indentation level for the message line - - - - Displays a single line showing the point in the expected and actual - arrays at which the comparison failed. If the arrays have different - structures or dimensions, both _values are shown. - - The MessageWriter on which to display - The expected array - The actual array - Index of the failure point in the underlying collections - The indentation level for the message line - - - - Display the failure information for two IEnumerables that did not match. - - The MessageWriter on which to display - The expected enumeration. - The actual enumeration - The depth of this failure in a set of nested collections - - - - EqualityAdapter class handles all equality comparisons - that use an , - or a . - - - - - Compares two objects, returning true if they are equal - - - - - Returns true if the two objects can be compared by this adapter. - The base adapter cannot handle IEnumerables except for strings. - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps a . - - - - - that wraps an . - - - - - Returns true if the two objects can be compared by this adapter. - Generic adapter requires objects of the specified type. - - - - - that wraps an . - - - - - ExactCountConstraint applies another constraint to each - item in a collection, succeeding only if a specified - number of items succeed. - - - - - Construct an ExactCountConstraint on top of an existing constraint - - - - - - - Apply the item constraint to each item in the collection, - succeeding only if the expected number of items pass. - - - - - - - ExactTypeConstraint is used to test that an object - is of the exact type provided in the constructor - - - - - Construct an ExactTypeConstraint for a given Type - - The expected Type. - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - ExceptionTypeConstraint is a special version of ExactTypeConstraint - used to provided detailed info about the exception thrown in - an error message. - - - - - Constructs an ExceptionTypeConstraint - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - FalseConstraint tests that the actual value is false - - - - - Initializes a new instance of the class. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - Helper routines for working with floating point numbers - - - The floating point comparison code is based on this excellent article: - http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm - - - "ULP" means Unit in the Last Place and in the context of this library refers to - the distance between two adjacent floating point numbers. IEEE floating point - numbers can only represent a finite subset of natural numbers, with greater - accuracy for smaller numbers and lower accuracy for very large numbers. - - - If a comparison is allowed "2 ulps" of deviation, that means the _values are - allowed to deviate by up to 2 adjacent floating point _values, which might be - as low as 0.0000001 for small numbers or as high as 10.0 for large numbers. - - - - - Compares two floating point _values for equality - First floating point value to be compared - Second floating point value t be compared - - Maximum number of representable floating point _values that are allowed to - be between the left and the right floating point _values - - True if both numbers are equal or close to being equal - - - Floating point _values can only represent a finite subset of natural numbers. - For example, the _values 2.00000000 and 2.00000024 can be stored in a float, - but nothing inbetween them. - - - This comparison will count how many possible floating point _values are between - the left and the right number. If the number of possible _values between both - numbers is less than or equal to maxUlps, then the numbers are considered as - being equal. - - - Implementation partially follows the code outlined here: - http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ - - - - - Compares two double precision floating point _values for equality - First double precision floating point value to be compared - Second double precision floating point value t be compared - - Maximum number of representable double precision floating point _values that are - allowed to be between the left and the right double precision floating point _values - - True if both numbers are equal or close to being equal - - - Double precision floating point _values can only represent a limited series of - natural numbers. For example, the _values 2.0000000000000000 and 2.0000000000000004 - can be stored in a double, but nothing inbetween them. - - - This comparison will count how many possible double precision floating point - _values are between the left and the right number. If the number of possible - _values between both numbers is less than or equal to maxUlps, then the numbers - are considered as being equal. - - - Implementation partially follows the code outlined here: - http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ - - - - - - Reinterprets the memory contents of a floating point value as an integer value - - - Floating point value whose memory contents to reinterpret - - - The memory contents of the floating point value interpreted as an integer - - - - - Reinterprets the memory contents of a double precision floating point - value as an integer value - - - Double precision floating point value whose memory contents to reinterpret - - - The memory contents of the double precision floating point value - interpreted as an integer - - - - - Reinterprets the memory contents of an integer as a floating point value - - Integer value whose memory contents to reinterpret - - The memory contents of the integer value interpreted as a floating point value - - - - - Reinterprets the memory contents of an integer value as a double precision - floating point value - - Integer whose memory contents to reinterpret - - The memory contents of the integer interpreted as a double precision - floating point value - - - - Union of a floating point variable and an integer - - - The union's value as a floating point variable - - - The union's value as an integer - - - The union's value as an unsigned integer - - - Union of a double precision floating point variable and a long - - - The union's value as a double precision floating point variable - - - The union's value as a long - - - The union's value as an unsigned long - - - - Tests whether a value is greater than the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - Tests whether a value is greater than or equal to the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - InstanceOfTypeConstraint is used to test that an object - is of the same type provided or derived from it. - - - - - Construct an InstanceOfTypeConstraint for the type provided - - The expected Type - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - Tests whether a value is less than the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - Tests whether a value is less than or equal to the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - MessageWriter is the abstract base for classes that write - constraint descriptions and messages in some form. The - class has separate methods for writing various components - of a message, allowing implementations to tailor the - presentation as needed. - - - - - Construct a MessageWriter given a culture - - - - - Method to write single line message with optional args, usually - written to precede the general failure message. - - The message to be written - Any arguments used in formatting the message - - - - Method to write single line message with optional args, usually - written to precede the general failure message, at a givel - indentation level. - - The indentation level of the message - The message to be written - Any arguments used in formatting the message - - - - Display Expected and Actual lines for a constraint. This - is called by MessageWriter's default implementation of - WriteMessageTo and provides the generic two-line display. - - The failing constraint result - - - - Display Expected and Actual lines for given _values. This - method may be called by constraints that need more control over - the display of actual and expected _values than is provided - by the default implementation. - - The expected value - The actual value causing the failure - - - - Display Expected and Actual lines for given _values, including - a tolerance value on the Expected line. - - The expected value - The actual value causing the failure - The tolerance within which the test was made - - - - Display the expected and actual string _values on separate lines. - If the mismatch parameter is >=0, an additional line is displayed - line containing a caret that points to the mismatch point. - - The expected string value - The actual string value - The point at which the strings don't match or -1 - If true, case is ignored in locating the point where the strings differ - If true, the strings should be clipped to fit the line - - - - Writes the text for an actual value. - - The actual value. - - - - Writes the text for a generalized value. - - The value. - - - - Writes the text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Abstract method to get the max line length - - - - - Static methods used in creating messages - - - - - Static string used when strings are clipped - - - - - Formatting strings used for expected and actual _values - - - - - Formats text to represent a generalized value. - - The value - The formatted text - - - - Formats text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Returns the representation of a type as used in NUnitLite. - This is the same as Type.ToString() except for arrays, - which are displayed with their declared sizes. - - - - - - - Converts any control characters in a string - to their escaped representation. - - The string to be converted - The converted string - - - - Return the a string representation for a set of indices into an array - - Array of indices for which a string is needed - - - - Get an array of indices representing the point in a collection or - array corresponding to a single int index into the collection. - - The collection to which the indices apply - Index in the collection - Array of indices - - - - Clip a string to a given length, starting at a particular offset, returning the clipped - string with ellipses representing the removed parts - - The string to be clipped - The maximum permitted length of the result string - The point at which to start clipping - The clipped string - - - - Clip the expected and actual strings in a coordinated fashion, - so that they may be displayed together. - - - - - - - - - Shows the position two strings start to differ. Comparison - starts at the start index. - - The expected string - The actual string - The index in the strings at which comparison should start - Boolean indicating whether case should be ignored - -1 if no mismatch found, or the index where mismatch found - - - - NaNConstraint tests that the actual value is a double or float NaN - - - - - Test that the actual value is an NaN - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - NoItemConstraint applies another constraint to each - item in a collection, failing if any of them succeeds. - - - - - Construct a SomeItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - failing if any item fails. - - - - - - - NotConstraint negates the effect of some other constraint - - - - - Initializes a new instance of the class. - - The base constraint to be negated. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for if the base constraint fails, false if it succeeds - - - - NullConstraint tests that the actual value is null - - - - - Initializes a new instance of the class. - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - The Numerics class contains common operations on numeric _values. - - - - - Checks the type of the object, returning true if - the object is a numeric type. - - The object to check - true if the object is a numeric type - - - - Checks the type of the object, returning true if - the object is a floating point numeric type. - - The object to check - true if the object is a floating point numeric type - - - - Checks the type of the object, returning true if - the object is a fixed point numeric type. - - The object to check - true if the object is a fixed point numeric type - - - - Test two numeric _values for equality, performing the usual numeric - conversions and using a provided or default tolerance. If the tolerance - provided is Empty, this method may set it to a default tolerance. - - The expected value - The actual value - A reference to the tolerance in effect - True if the _values are equal - - - - Compare two numeric _values, performing the usual numeric conversions. - - The expected value - The actual value - The relationship of the _values to each other - - - - NUnitComparer encapsulates NUnit's default behavior - in comparing two objects. - - - - - Compares two objects - - - - - - - - Returns the default NUnitComparer. - - - - - NUnitEqualityComparer encapsulates NUnit's handling of - equality tests between objects. - - - - - If true, all string comparisons will ignore case - - - - - If true, arrays will be treated as collections, allowing - those of different dimensions to be compared - - - - - Comparison objects used in comparisons for some constraints. - - - - - List of points at which a failure occurred. - - - - - Compares two objects for equality within a tolerance. - - - - - Helper method to compare two arrays - - - - - Returns the default NUnitEqualityComparer - - - - - Gets and sets a flag indicating whether case should - be ignored in determining equality. - - - - - Gets and sets a flag indicating that arrays should be - compared as collections, without regard to their shape. - - - - - Gets the list of external comparers to be used to - test for equality. They are applied to members of - collections, in place of NUnit's own logic. - - - - - Gets the list of failure points for the last Match performed. - The list consists of objects to be interpreted by the caller. - This generally means that the caller may only make use of - objects it has placed on the list at a particular depthy. - - - - - Flags the comparer to include - property in comparison of two values. - - - Using this modifier does not allow to use the - modifier. - - - - - FailurePoint class represents one point of failure - in an equality test. - - - - - The location of the failure - - - - - The expected value - - - - - The actual value - - - - - Indicates whether the expected value is valid - - - - - Indicates whether the actual value is valid - - - - - Represents a constraint that succeeds if all the - members of a collection match a base constraint. - - - - - Abstract base for operators that indicate how to - apply a constraint to items in a collection. - - - - - PrefixOperator takes a single constraint and modifies - it's action in some way. - - - - - The ConstraintOperator class is used internally by a - ConstraintBuilder to represent an operator that - modifies or combines constraints. - - Constraint operators use left and right precedence - _values to determine whether the top operator on the - stack should be reduced before pushing a new operator. - - - - - The precedence value used when the operator - is about to be pushed to the stack. - - - - - The precedence value used when the operator - is on the top of the stack. - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - The syntax element preceding this operator - - - - - The syntax element following this operator - - - - - The precedence value used when the operator - is about to be pushed to the stack. - - - - - The precedence value used when the operator - is on the top of the stack. - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Returns the constraint created by applying this - prefix to another constraint. - - - - - - - Constructs a CollectionOperator - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - they all succeed. - - - - - Operator that requires both it's arguments to succeed - - - - - Abstract base class for all binary operators - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Abstract method that produces a constraint by applying - the operator to its left and right constraint arguments. - - - - - Gets the left precedence of the operator - - - - - Gets the right precedence of the operator - - - - - Construct an AndOperator - - - - - Apply the operator to produce an AndConstraint - - - - - Operator that tests for the presence of a particular attribute - on a type and optionally applies further tests to the attribute. - - - - - Abstract base class for operators that are able to reduce to a - constraint whether or not another syntactic element follows. - - - - - Construct an AttributeOperator for a particular Type - - The Type of attribute tested - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - Represents a constraint that succeeds if the specified - count of members of a collection match a base constraint. - - - - - Construct an ExactCountOperator for a specified count - - The expected count - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - none of them succeed. - - - - - Represents a constraint that succeeds if none of the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - none of them succeed. - - - - - Negates the test of the constraint it wraps. - - - - - Constructs a new NotOperator - - - - - Returns a NotConstraint applied to its argument. - - - - - Operator that requires at least one of it's arguments to succeed - - - - - Construct an OrOperator - - - - - Apply the operator to produce an OrConstraint - - - - - Operator used to test for the presence of a named Property - on an object and optionally apply further tests to the - value of that property. - - - - - Constructs a PropOperator for a particular named property - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Gets the name of the property to which the operator applies - - - - - Represents a constraint that succeeds if any of the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - any of them succeed. - - - - - Operator that tests that an exception is thrown and - optionally applies further tests to the exception. - - - - - Construct a ThrowsOperator - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - Represents a constraint that simply wraps the - constraint provided as an argument, without any - further functionality, but which modifies the - order of evaluation because of its precedence. - - - - - Constructor for the WithOperator - - - - - Returns a constraint that wraps its argument - - - - - OrConstraint succeeds if either member succeeds - - - - - Create an OrConstraint from two other constraints - - The first constraint - The second constraint - - - - Apply the member constraints to an actual value, succeeding - succeeding as soon as one of them succeeds. - - The actual value - True if either constraint succeeded - - - - Gets text describing a constraint - - - - - Predicate constraint wraps a Predicate in a constraint, - returning success if the predicate is true. - - - - - Construct a PredicateConstraint from a predicate - - - - - Determines whether the predicate succeeds when applied - to the actual value. - - - - - Gets text describing a constraint - - - - - PropertyConstraint extracts a named property and uses - its value as the actual value for a chained constraint. - - - - - Initializes a new instance of the class. - - The name. - The constraint to apply to the property. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - - PropertyExistsConstraint tests that a named property - exists on the object provided through Match. - - Originally, PropertyConstraint provided this feature - in addition to making optional tests on the value - of the property. The two constraints are now separate. - - - - - Initializes a new instance of the class. - - The name of the property. - - - - Test whether the property exists for a given object - - The object to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - RangeConstraint tests whether two _values are within a - specified range. - - - - - Initializes a new instance of the class. - - from must be less than or equal to true - Inclusive beginning of the range. Must be less than or equal to to. - Inclusive end of the range. Must be greater than or equal to from. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use a and returns self. - - - - - Gets text describing a constraint - - - - - RegexConstraint can test whether a string matches - the pattern provided. - - - - - Initializes a new instance of the class. - - The pattern. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - ResolvableConstraintExpression is used to represent a compound - constraint being constructed at a point where the last operator - may either terminate the expression or may have additional - qualifying constraints added to it. - - It is used, for example, for a Property element or for - an Exception element, either of which may be optionally - followed by constraints that apply to the property or - exception. - - - - - Create a new instance of ResolvableConstraintExpression - - - - - Create a new instance of ResolvableConstraintExpression, - passing in a pre-populated ConstraintBuilder. - - - - - Resolve the current expression to a Constraint - - - - - Appends an And Operator to the expression - - - - - Appends an Or operator to the expression. - - - - - ReusableConstraint wraps a constraint expression after - resolving it so that it can be reused consistently. - - - - - Construct a ReusableConstraint from a constraint expression - - The expression to be resolved and reused - - - - Converts a constraint to a ReusableConstraint - - The constraint to be converted - A ReusableConstraint - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Return the top-level constraint for this expression - - - - - - SameAsConstraint tests whether an object is identical to - the object passed to its constructor - - - - - Initializes a new instance of the class. - - The expected object. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - SomeItemsConstraint applies another constraint to each - item in a collection, succeeding if any of them succeeds. - - - - - Construct a SomeItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - succeeding if any item succeeds. - - - - - - - StartsWithConstraint can test whether a string starts - with an expected substring. - - - - - Initializes a new instance of the class. - - The expected string - - - - Test whether the constraint is matched by the actual value. - This is a template method, which calls the IsMatch method - of the derived class. - - - - - - - SubstringConstraint can test whether a string contains - the expected substring. - - - - - Initializes a new instance of the class. - - The expected. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - ThrowsConstraint is used to test the exception thrown by - a delegate by applying a constraint to it. - - - - - Initializes a new instance of the class, - using a constraint to be applied to the exception. - - A constraint to apply to the caught exception. - - - - Executes the code of the delegate and captures any exception. - If a non-null base constraint was provided, it applies that - constraint to the exception. - - A delegate representing the code to be tested - True if an exception is thrown and the constraint succeeds, otherwise false - - - - Converts an ActualValueDelegate to a TestDelegate - before calling the primary overload. - - - - - - - Get the actual exception thrown - used by Assert.Throws. - - - - - Gets text describing a constraint - - - - - Write the actual value for a failing constraint test to a - MessageWriter. This override only handles the special message - used when an exception is expected but none is thrown. - - The writer on which the actual value is displayed - - - - ThrowsExceptionConstraint tests that an exception has - been thrown, without any further tests. - - - - - Executes the code and returns success if an exception is thrown. - - A delegate representing the code to be tested - True if an exception is thrown, otherwise false - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - ThrowsNothingConstraint tests that a delegate does not - throw an exception. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True if no exception is thrown, otherwise false - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Gets text describing a constraint - - - - - The Tolerance class generalizes the notion of a tolerance - within which an equality test succeeds. Normally, it is - used with numeric types, but it can be used with any - type that supports taking a difference between two - objects and comparing that difference to a value. - - - - - Constructs a linear tolerance of a specified amount - - - - - Constructs a tolerance given an amount and - - - - - Tests that the current Tolerance is linear with a - numeric value, throwing an exception if it is not. - - - - - Returns a default Tolerance object, equivalent to - specifying an exact match unless - is set, in which case, the - will be used. - - - - - Returns an empty Tolerance object, equivalent to - specifying an exact match even if - is set. - - - - - Gets the for the current Tolerance - - - - - Gets the value of the current Tolerance instance. - - - - - Returns a new tolerance, using the current amount as a percentage. - - - - - Returns a new tolerance, using the current amount in Ulps - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of days. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of hours. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of minutes. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of seconds. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of milliseconds. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of clock ticks. - - - - - Returns true if the current tolerance has not been set or is using the . - - - - - Modes in which the tolerance value for a comparison can be interpreted. - - - - - The tolerance was created with a value, without specifying - how the value would be used. This is used to prevent setting - the mode more than once and is generally changed to Linear - upon execution of the test. - - - - - The tolerance is used as a numeric range within which - two compared _values are considered to be equal. - - - - - Interprets the tolerance as the percentage by which - the two compared _values my deviate from each other. - - - - - Compares two _values based in their distance in - representable numbers. - - - - - TrueConstraint tests that the actual value is true - - - - - Initializes a new instance of the class. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - UniqueItemsConstraint tests whether all the items in a - collection are unique. - - - - - Check that all items are unique. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new DictionaryContainsKeyConstraint checking for the - presence of a particular key in the dictionary. - - - - - Returns a new DictionaryContainsValueConstraint checking for the - presence of a particular value in the dictionary. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Thrown when an assertion failed. - - - - - Abstract base for Exceptions that terminate a test and provide a ResultState. - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Gets the ResultState provided by this exception - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Gets the ResultState provided by this exception - - - - - Thrown when an assertion failed. - - - - - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Gets the ResultState provided by this exception - - - - - Thrown when a test executes inconclusively. - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Gets the ResultState provided by this exception - - - - - Thrown when an assertion failed. - - - - - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Gets the ResultState provided by this exception - - - - - GlobalSettings is a place for setting default _values used - by the framework in performing asserts. - - - - - Default tolerance for floating point equality - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - CombiningStrategy is the abstract base for classes that - know how to combine values provided for individual test - parameters to create a set of test cases. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - Objects implementing this interface are used to wrap - the TestMethodCommand itself. They apply after SetUp - has been run and before TearDown. - - - - - Any ITest that implements this interface is at a level that the implementing - class should be disposed at the end of the test run - - - - - The IMethodInfo class is used to encapsulate information - about a method in a platform-independent manner. - - - - - The IReflectionInfo interface is implemented by NUnit wrapper objects that perform reflection. - - - - - Returns an array of custom attributes of the specified type applied to this object - - - - - Returns a value indicating whether an attribute of the specified type is defined on this object. - - - - - Gets the parameters of the method. - - - - - - Returns the Type arguments of a generic method or the Type parameters of a generic method definition. - - - - - Replaces the type parameters of the method with the array of types provided and returns a new IMethodInfo. - - The type arguments to be used - A new IMethodInfo with the type arguments replaced - - - - Invokes the method, converting any TargetInvocationException to an NUnitException. - - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - Gets the Type from which this method was reflected. - - - - - Gets the MethodInfo for this method. - - - - - Gets the name of the method. - - - - - Gets a value indicating whether the method is abstract. - - - - - Gets a value indicating whether the method is public. - - - - - Gets a value indicating whether the method contains unassigned generic type parameters. - - - - - Gets a value indicating whether the method is a generic method. - - - - - Gets a value indicating whether the MethodInfo represents the definition of a generic method. - - - - - Gets the return Type of the method. - - - - - The IDataPointProvider interface is used by extensions - that provide data for a single test parameter. - - - - - Determine whether any data is available for a parameter. - - An IParameterInfo representing one - argument to a parameterized test - True if any data is available, otherwise false. - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - An IParameterInfo representing one - argument to a parameterized test - An IEnumerable providing the required data - - - - The IParameterInfo interface is an abstraction of a .NET parameter. - - - - - Gets a value indicating whether the parameter is optional - - - - - Gets an IMethodInfo representing the method for which this is a parameter - - - - - Gets the underlying .NET ParameterInfo - - - - - Gets the Type of the parameter - - - - - A PropertyBag represents a collection of name/value pairs - that allows duplicate entries with the same key. Methods - are provided for adding a new pair as well as for setting - a key to a single value. All keys are strings but _values - may be of any type. Null _values are not permitted, since - a null entry represents the absence of the key. - - The entries in a PropertyBag are of two kinds: those that - take a single value and those that take multiple _values. - However, the PropertyBag has no knowledge of which entries - fall into each category and the distinction is entirely - up to the code using the PropertyBag. - - When working with multi-valued properties, client code - should use the Add method to add name/value pairs and - indexing to retrieve a list of all _values for a given - key. For example: - - bag.Add("Tag", "one"); - bag.Add("Tag", "two"); - Assert.That(bag["Tag"], - Is.EqualTo(new string[] { "one", "two" })); - - When working with single-valued propeties, client code - should use the Set method to set the value and Get to - retrieve the value. The GetSetting methods may also be - used to retrieve the value in a type-safe manner while - also providing default. For example: - - bag.Set("Priority", "low"); - bag.Set("Priority", "high"); // replaces value - Assert.That(bag.Get("Priority"), - Is.EqualTo("high")); - Assert.That(bag.GetSetting("Priority", "low"), - Is.EqualTo("high")); - - - - - An object implementing IXmlNodeBuilder is able to build - an XML representation of itself and any children. - - - - - Returns a TNode representing the current object. - - If true, children are included where applicable - A TNode representing the result - - - - Returns a TNode representing the current object after - adding it as a child of the supplied parent node. - - The parent node. - If true, children are included, where applicable - - - - - Adds a key/value pair to the property bag - - The key - The value - - - - Sets the value for a key, removing any other - _values that are already in the property set. - - - - - - - Gets a single value for a key, using the first - one if multiple _values are present and returning - null if the value is not found. - - - - - Gets a flag indicating whether the specified key has - any entries in the property set. - - The key to be checked - True if their are _values present, otherwise false - - - - Gets or sets the list of _values for a particular key - - The key for which the _values are to be retrieved or set - - - - Gets a collection containing all the keys in the property set - - - - - The ISuiteBuilder interface is exposed by a class that knows how to - build a suite from one or more Types. - - - - - Examine the type and determine if it is suitable for - this builder to use in building a TestSuite. - - Note that returning false will cause the type to be ignored - in loading the tests. If it is desired to load the suite - but label it as non-runnable, ignored, etc., then this - method must return true. - - The type of the fixture to be used - True if the type can be used to build a TestSuite - - - - Build a TestSuite from type provided. - - The type of the fixture to be used - A TestSuite - - - - Common interface supported by all representations - of a test. Only includes informational fields. - The Run method is specifically excluded to allow - for data-only representations of a test. - - - - - Gets the id of the test - - - - - Gets the name of the test - - - - - Gets the fully qualified name of the test - - - - - Gets the name of the class containing this test. Returns - null if the test is not associated with a class. - - - - - Gets the name of the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the Type of the test fixture, if applicable, or - null if no fixture type is associated with this test. - - - - - Gets an IMethod for the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the RunState of the test, indicating whether it can be run. - - - - - Count of the test cases ( 1 if this is a test case ) - - - - - Gets the properties of the test - - - - - Gets the parent test, if any. - - The parent test or null if none exists. - - - - Returns true if this is a test suite - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets a fixture object for running this test. - - - - - The ITestCaseBuilder interface is exposed by a class that knows how to - build a test case from certain methods. - - - This interface is not the same as the ITestCaseBuilder interface in NUnit 2.x. - We have reused the name because the two products don't interoperate at all. - - - - - Examine the method and determine if it is suitable for - this builder to use in building a TestCase to be - included in the suite being populated. - - Note that returning false will cause the method to be ignored - in loading the tests. If it is desired to load the method - but label it as non-runnable, ignored, etc., then this - method must return true. - - The test method to examine - The suite being populated - True is the builder can use this method - - - - Build a TestCase from the provided MethodInfo for - inclusion in the suite being constructed. - - The method to be used as a test case - The test suite being populated, or null - A TestCase or null - - - - Interface to be implemented by filters applied to tests. - The filter applies when running the test, after it has been - loaded, since this is the only time an ITest exists. - - - - - Determine if a particular test passes the filter criteria. Pass - may examine the parents and/or descendants of a test, depending - on the semantics of the particular filter - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - The ITestListener interface is used internally to receive - notifications of significant events while a test is being - run. The events are propagated to clients by means of an - AsyncCallback. NUnit extensions may also monitor these events. - - - - - Called when a test has just started - - The test that is starting - - - - Called when a test has finished - - The result of the test - - - - The ITestResult interface represents the result of a test. - - - - - Gets the ResultState of the test result, which - indicates the success or failure of the test. - - - - - Gets the name of the test result - - - - - Gets the full name of the test result - - - - - Gets the elapsed time for running the test in seconds - - - - - Gets or sets the time the test started running. - - - - - Gets or sets the time the test finished running. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. Not available in - the Compact Framework 1.0. - - - - - Gets the number of asserts executed - when running the test and all its children. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Indicates whether this result has any child results. - Accessing HasChildren should not force creation of the - Children collection in classes implementing this interface. - - - - - Gets the the collection of child results. - - - - - Gets the Test to which this result applies. - - - - - Gets any text output written to this result. - - - - - The ITypeInfo interface is an abstraction of a .NET Type - - - - - Returns true if the Type wrapped is equal to the argument - - - - - Get the display name for this typeInfo. - - - - - Get the display name for an oject of this type, constructed with specific arguments - - - - - Returns a Type representing a generic type definition from which this Type can be constructed. - - - - - Returns a new ITypeInfo representing an instance of this generic Type using the supplied Type arguments - - - - - Returns a value indicating whether this type has a method with a specified public attribute - - - - - Returns an array of IMethodInfos for methods of this Type - that match the specified flags. - - - - - Returns a value indicating whether this Type has a public constructor taking the specified argument Types. - - - - - Construct an object of this Type, using the specified arguments. - - - - - Gets the underlying Type on which this ITypeInfo is based - - - - - Gets the base type of this type as an ITypeInfo - - - - - Gets the Name of the Type - - - - - Gets the FullName of the Type - - - - - Gets the assembly in which the type is declared - - - - - Gets the Namespace of the Type - - - - - Gets a value indicating whether the type is abstract. - - - - - Gets a value indicating whether the Type is a generic Type - - - - - Gets a value indicating whether the Type has generic parameters that have not been replaced by specific Types. - - - - - Gets a value indicating whether the Type is a generic Type definition - - - - - Gets a value indicating whether the type is sealed. - - - - - Gets a value indicating whether this type is a static class. - - - - - The ResultState class represents the outcome of running a test. - It contains two pieces of information. The Status of the test - is an enum indicating whether the test passed, failed, was - skipped or was inconclusive. The Label provides a more - detailed breakdown for use by client runners. - - - - - Initializes a new instance of the class. - - The TestStatus. - - - - Initializes a new instance of the class. - - The TestStatus. - The label. - - - - Initializes a new instance of the class. - - The TestStatus. - The stage at which the result was produced - - - - Initializes a new instance of the class. - - The TestStatus. - The label. - The stage at which the result was produced - - - - The result is inconclusive - - - - - The test has been skipped. - - - - - The test has been ignored. - - - - - The test was skipped because it is explicit - - - - - The test succeeded - - - - - The test failed - - - - - The test encountered an unexpected exception - - - - - The test was cancelled by the user - - - - - The test was not runnable. - - - - - A suite failed because one or more child tests failed or had errors - - - - - A suite failed in its OneTimeSetUp - - - - - A suite had an unexpected exception in its OneTimeSetUp - - - - - A suite had an unexpected exception in its OneTimeDown - - - - - Get a new ResultState, which is the same as the current - one but with the FailureSite set to the specified value. - - The FailureSite to use - A new ResultState - - - - Determines whether the specified , is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Returns a hash code for this instance. - - - A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Gets the TestStatus for the test. - - The status. - - - - Gets the label under which this test result is - categorized, if any. - - - - - Gets the stage of test execution in which - the failure or other result took place. - - - - - The FailureSite enum indicates the stage of a test - in which an error or failure occurred. - - - - - Failure in the test itself - - - - - Failure in the SetUp method - - - - - Failure in the TearDown method - - - - - Failure of a parent test - - - - - Failure of a child test - - - - - The RunState enum indicates whether a test can be executed. - - - - - The test is not runnable. - - - - - The test is runnable. - - - - - The test can only be run explicitly - - - - - The test has been skipped. This value may - appear on a Test when certain attributes - are used to skip the test. - - - - - The test has been ignored. May appear on - a Test, when the IgnoreAttribute is used. - - - - - The TestStatus enum indicates the result of running a test - - - - - The test was inconclusive - - - - - The test has skipped - - - - - The test succeeded - - - - - The test failed - - - - - TNode represents a single node in the XML representation - of a Test or TestResult. It replaces System.Xml.XmlNode and - System.Xml.Linq.XElement, providing a minimal set of methods - for operating on the XML in a platform-independent manner. - - - - - Constructs a new instance of TNode - - The name of the node - - - - Constructs a new instance of TNode with a value - - The name of the node - The text content of the node - - - - Constructs a new instance of TNode with a value - - The name of the node - The text content of the node - Flag indicating whether to use CDATA when writing the text - - - - Create a TNode from it's XML text representation - - The XML text to be parsed - A TNode - - - - Adds a new element as a child of the current node and returns it. - - The element name. - The newly created child element - - - - Adds a new element with a value as a child of the current node and returns it. - - The element name - The text content of the new element - The newly created child element - - - - Adds a new element with a value as a child of the current node and returns it. - The value will be output using a CDATA section. - - The element name - The text content of the new element - The newly created child element - - - - Adds an attribute with a specified name and value to the XmlNode. - - The name of the attribute. - The value of the attribute. - - - - Finds a single descendant of this node matching an xpath - specification. The format of the specification is - limited to what is needed by NUnit and its tests. - - - - - - - Finds all descendants of this node matching an xpath - specification. The format of the specification is - limited to what is needed by NUnit and its tests. - - - - - Writes the XML representation of the node to an XmlWriter - - - - - - Gets the name of the node - - - - - Gets the value of the node - - - - - Gets a flag indicating whether the value should be output using CDATA. - - - - - Gets the dictionary of attributes - - - - - Gets a list of child nodes - - - - - Gets the first ChildNode - - - - - Gets the XML representation of this node. - - - - - Class used to represent a list of XmlResults - - - - - Class used to represent the attributes of a node - - - - - Gets or sets the value associated with the specified key. - Overridden to return null if attribute is not found. - - The key. - Value of the attribute or null - - - - Waits for pending asynchronous operations to complete, if appropriate, - and returns a proper result of the invocation by unwrapping task results - - The raw result of the method invocation - The unwrapped result, if necessary - - - - CombinatorialStrategy creates test cases by using all possible - combinations of the parameter data. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - Provides data from fields marked with the DatapointAttribute or the - DatapointsAttribute. - - - - - Determine whether any data is available for a parameter. - - A ParameterInfo representing one - argument to a parameterized test - - True if any data is available, otherwise false. - - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - A ParameterInfo representing one - argument to a parameterized test - - An IEnumerable providing the required data - - - - - Built-in SuiteBuilder for all types of test classes. - - - - - Checks to see if the provided Type is a fixture. - To be considered a fixture, it must be a non-abstract - class with one or more attributes implementing the - IFixtureBuilder interface or one or more methods - marked as tests. - - The fixture type to check - True if the fixture can be built, false if not - - - - Build a TestSuite from TypeInfo provided. - - The fixture type to build - A TestSuite built from that type - - - - We look for attributes implementing IFixtureBuilder at one level - of inheritance at a time. Attributes on base classes are not used - unless there are no fixture builder attributes at all on the derived - class. This is by design. - - The type being examined for attributes - A list of the attributes found. - - - - Class to build ether a parameterized or a normal NUnitTestMethod. - There are four cases that the builder must deal with: - 1. The method needs no params and none are provided - 2. The method needs params and they are provided - 3. The method needs no params but they are provided in error - 4. The method needs params but they are not provided - This could have been done using two different builders, but it - turned out to be simpler to have just one. The BuildFrom method - takes a different branch depending on whether any parameters are - provided, but all four cases are dealt with in lower-level methods - - - - - Determines if the method can be used to build an NUnit test - test method of some kind. The method must normally be marked - with an identifying attribute for this to be true. - - Note that this method does not check that the signature - of the method for validity. If we did that here, any - test methods with invalid signatures would be passed - over in silence in the test run. Since we want such - methods to be reported, the check for validity is made - in BuildFrom rather than here. - - An IMethodInfo for the method being used as a test method - True if the builder can create a test case from this method - - - - Build a Test from the provided MethodInfo. Depending on - whether the method takes arguments and on the availability - of test case data, this method may return a single test - or a group of tests contained in a ParameterizedMethodSuite. - - The method for which a test is to be built - A Test representing one or more method invocations - - - - Determines if the method can be used to build an NUnit test - test method of some kind. The method must normally be marked - with an identifying attribute for this to be true. - - Note that this method does not check that the signature - of the method for validity. If we did that here, any - test methods with invalid signatures would be passed - over in silence in the test run. Since we want such - methods to be reported, the check for validity is made - in BuildFrom rather than here. - - An IMethodInfo for the method being used as a test method - The test suite being built, to which the new test would be added - True if the builder can create a test case from this method - - - - Build a Test from the provided MethodInfo. Depending on - whether the method takes arguments and on the availability - of test case data, this method may return a single test - or a group of tests contained in a ParameterizedMethodSuite. - - The method for which a test is to be built - The test fixture being populated, or null - A Test representing one or more method invocations - - - - Builds a ParameterizedMethodSuite containing individual test cases. - - The method for which a test is to be built. - The list of test cases to include. - A ParameterizedMethodSuite populated with test cases - - - - Build a simple, non-parameterized TestMethod for this method. - - The MethodInfo for which a test is to be built - The test suite for which the method is being built - A TestMethod. - - - - Class that can build a tree of automatic namespace - suites from a group of fixtures. - - - - - NamespaceDictionary of all test suites we have created to represent - namespaces. Used to locate namespace parent suites for fixtures. - - - - - The root of the test suite being created by this builder. - - - - - Initializes a new instance of the class. - - The root suite. - - - - Adds the specified fixtures to the tree. - - The fixtures to be added. - - - - Adds the specified fixture to the tree. - - The fixture to be added. - - - - Gets the root entry in the tree created by the NamespaceTreeBuilder. - - The root suite. - - - - NUnitTestCaseBuilder is a utility class used by attributes - that build test cases. - - - - - Constructs an - - - - - Builds a single NUnitTestMethod, either as a child of the fixture - or as one of a set of test cases under a ParameterizedTestMethodSuite. - - The MethodInfo from which to construct the TestMethod - The suite or fixture to which the new test will be added - The ParameterSet to be used, or null - - - - - Helper method that checks the signature of a TestMethod and - any supplied parameters to determine if the test is valid. - - Currently, NUnitTestMethods are required to be public, - non-abstract methods, either static or instance, - returning void. They may take arguments but the _values must - be provided or the TestMethod is not considered runnable. - - Methods not meeting these criteria will be marked as - non-runnable and the method will return false in that case. - - The TestMethod to be checked. If it - is found to be non-runnable, it will be modified. - Parameters to be used for this test, or null - True if the method signature is valid, false if not - - The return value is no longer used internally, but is retained - for testing purposes. - - - - - NUnitTestFixtureBuilder is able to build a fixture given - a class marked with a TestFixtureAttribute or an unmarked - class containing test methods. In the first case, it is - called by the attribute and in the second directly by - NUnitSuiteBuilder. - - - - - Build a TestFixture from type provided. A non-null TestSuite - must always be returned, since the method is generally called - because the user has marked the target class as a fixture. - If something prevents the fixture from being used, it should - be returned nonetheless, labelled as non-runnable. - - An ITypeInfo for the fixture to be used. - A TestSuite object or one derived from TestSuite. - - - - Overload of BuildFrom called by tests that have arguments. - Builds a fixture using the provided type and information - in the ITestFixtureData object. - - The TypeInfo for which to construct a fixture. - An object implementing ITestFixtureData or null. - - - - - Method to add test cases to the newly constructed fixture. - - The fixture to which cases should be added - - - - Method to create a test case from a MethodInfo and add - it to the fixture being built. It first checks to see if - any global TestCaseBuilder addin wants to build the - test case. If not, it uses the internal builder - collection maintained by this fixture builder. - - The default implementation has no test case builders. - Derived classes should add builders to the collection - in their constructor. - - The method for which a test is to be created - The test suite being built. - A newly constructed Test - - - - PairwiseStrategy creates test cases by combining the parameter - data so that all possible pairs of data items are used. - - - - The number of test cases that cover all possible pairs of test function - parameters values is significantly less than the number of test cases - that cover all possible combination of test function parameters values. - And because different studies show that most of software failures are - caused by combination of no more than two parameters, pairwise testing - can be an effective ways to test the system when it's impossible to test - all combinations of parameters. - - - The PairwiseStrategy code is based on "jenny" tool by Bob Jenkins: - http://burtleburtle.net/bob/math/jenny.html - - - - - - Gets the test cases generated by this strategy instance. - - A set of test cases. - - - - FleaRand is a pseudo-random number generator developed by Bob Jenkins: - http://burtleburtle.net/bob/rand/talksmall.html#flea - - - - - Initializes a new instance of the FleaRand class. - - The seed. - - - - FeatureInfo represents coverage of a single value of test function - parameter, represented as a pair of indices, Dimension and Feature. In - terms of unit testing, Dimension is the index of the test parameter and - Feature is the index of the supplied value in that parameter's list of - sources. - - - - - Initializes a new instance of FeatureInfo class. - - Index of a dimension. - Index of a feature. - - - - A FeatureTuple represents a combination of features, one per test - parameter, which should be covered by a test case. In the - PairwiseStrategy, we are only trying to cover pairs of features, so the - tuples actually may contain only single feature or pair of features, but - the algorithm itself works with triplets, quadruples and so on. - - - - - Initializes a new instance of FeatureTuple class for a single feature. - - Single feature. - - - - Initializes a new instance of FeatureTuple class for a pair of features. - - First feature. - Second feature. - - - - TestCase represents a single test case covering a list of features. - - - - - Initializes a new instance of TestCaseInfo class. - - A number of features in the test case. - - - - PairwiseTestCaseGenerator class implements an algorithm which generates - a set of test cases which covers all pairs of possible values of test - function. - - - - The algorithm starts with creating a set of all feature tuples which we - will try to cover (see method). This set - includes every single feature and all possible pairs of features. We - store feature tuples in the 3-D collection (where axes are "dimension", - "feature", and "all combinations which includes this feature"), and for - every two feature (e.g. "A" and "B") we generate both ("A", "B") and - ("B", "A") pairs. This data structure extremely reduces the amount of - time needed to calculate coverage for a single test case (this - calculation is the most time-consuming part of the algorithm). - - - Then the algorithm picks one tuple from the uncovered tuple, creates a - test case that covers this tuple, and then removes this tuple and all - other tuples covered by this test case from the collection of uncovered - tuples. - - - Picking a tuple to cover - - - There are no any special rules defined for picking tuples to cover. We - just pick them one by one, in the order they were generated. - - - Test generation - - - Test generation starts from creating a completely random test case which - covers, nevertheless, previously selected tuple. Then the algorithm - tries to maximize number of tuples which this test covers. - - - Test generation and maximization process repeats seven times for every - selected tuple and then the algorithm picks the best test case ("seven" - is a magic number which provides good results in acceptable time). - - Maximizing test coverage - - To maximize tests coverage, the algorithm walks thru the list of mutable - dimensions (mutable dimension is a dimension that are not included in - the previously selected tuple). Then for every dimension, the algorithm - walks thru the list of features and checks if this feature provides - better coverage than randomly selected feature, and if yes keeps this - feature. - - - This process repeats while it shows progress. If the last iteration - doesn't improve coverage, the process ends. - - - In addition, for better results, before start every iteration, the - algorithm "scrambles" dimensions - so for every iteration dimension - probes in a different order. - - - - - - Creates a set of test cases for specified dimensions. - - - An array which contains information about dimensions. Each element of - this array represents a number of features in the specific dimension. - - - A set of test cases. - - - - - ParameterDataProvider supplies individual argument _values for - single parameters using attributes derived from DataAttribute. - - - - - Determine whether any data is available for a parameter. - - A ParameterInfo representing one - argument to a parameterized test - - True if any data is available, otherwise false. - - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - An IParameterInfo representing one - argument to a parameterized test - - An IEnumerable providing the required data - - - - - SequentialStrategy creates test cases by using all of the - parameter data sources in parallel, substituting null - when any of them run out of data. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - ContextSettingsCommand applies specified changes to the - TestExecutionContext prior to running a test. No special - action is needed after the test runs, since the prior - context will be restored automatically. - - - - - The CommandStage enumeration represents the defined stages - of execution for a series of TestCommands. The int _values - of the enum are used to apply decorators in the proper - order. Lower _values are applied first and are therefore - "closer" to the actual test execution. - - - No CommandStage is defined for actual invocation of the test or - for creation of the context. Execution may be imagined as - proceeding from the bottom of the list upwards, with cleanup - after the test running in the opposite order. - - - - - Use an application-defined default value. - - - - - Make adjustments needed before and after running - the raw test - that is, after any SetUp has run - and before TearDown. - - - - - Run SetUp and TearDown for the test. This stage is used - internally by NUnit and should not normally appear - in user-defined decorators. - - - - - Make adjustments needed before and after running - the entire test - including SetUp and TearDown. - - - - - TODO: Documentation needed for class - - - - - Initializes a new instance of the class. - - The inner command. - The max time allowed in milliseconds - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext - - The context in which the test should run. - A TestResult - - - - OneTimeSetUpCommand runs any one-time setup methods for a suite, - constructing the user test object if necessary. - - - - - Constructs a OneTimeSetUpCommand for a suite - - The suite to which the command applies - A SetUpTearDownList for use by the command - A List of TestActionItems to be run after Setup - - - - Overridden to run the one-time setup for a suite. - - The TestExecutionContext to be used. - A TestResult - - - - OneTimeTearDownCommand performs any teardown actions - specified for a suite and calls Dispose on the user - test object, if any. - - - - - Construct a OneTimeTearDownCommand - - The test suite to which the command applies - A SetUpTearDownList for use by the command - A List of TestActionItems to be run before teardown. - - - - Overridden to run the teardown methods specified on the test. - - The TestExecutionContext to be used. - A TestResult - - - - SetUpTearDownCommand runs any SetUp methods for a suite, - runs the test and then runs any TearDown methods. - - - - - Initializes a new instance of the class. - - The inner command. - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - SetUpTearDownItem holds the setup and teardown methods - for a single level of the inheritance hierarchy. - - - - - Construct a SetUpTearDownNode - - A list of setup methods for this level - A list teardown methods for this level - - - - Run SetUp on this level. - - The execution context to use for running. - - - - Run TearDown for this level. - - - - - - Returns true if this level has any methods at all. - This flag is used to discard levels that do nothing. - - - - - TODO: Documentation needed for class - - - - - Initializes a new instance of the class. - - The test being skipped. - - - - Overridden to simply set the CurrentResult to the - appropriate Skipped state. - - The execution context for the test - A TestResult - - - - TestActionCommand runs the BeforeTest actions for a test, - then runs the test and finally runs the AfterTestActions. - - - - - Initializes a new instance of the class. - - The inner command. - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - TestActionItem represents a single execution of an - ITestAction. It is used to track whether the BeforeTest - method has been called and suppress calling the - AfterTest method if it has not. - - - - - Construct a TestActionItem - - The ITestAction to be included - - - - Run the BeforeTest method of the action and remember that it has been run. - - The test to which the action applies - - - - Run the AfterTest action, but only if the BeforeTest - action was actually run. - - The test to which the action applies - - - - TestMethodCommand is the lowest level concrete command - used to run actual test cases. - - - - - Initializes a new instance of the class. - - The test. - - - - Runs the test, saving a TestResult in the execution context, as - well as returning it. If the test has an expected result, it - is asserts on that value. Since failed tests and errors throw - an exception, this command must be wrapped in an outer command, - will handle that exception and records the failure. This role - is usually played by the SetUpTearDown command. - - The execution context - - - - TheoryResultCommand adjusts the result of a Theory so that - it fails if all the results were inconclusive. - - - - - Constructs a TheoryResultCommand - - The command to be wrapped by this one - - - - Overridden to call the inner command and adjust the result - in case all chlid results were inconclusive. - - - - - - - CultureDetector is a helper class used by NUnit to determine - whether a test should be run based on the current culture. - - - - - Default constructor uses the current culture. - - - - - Construct a CultureDetector for a particular culture for testing. - - The culture to be used - - - - Test to determine if one of a collection of cultures - is being used currently. - - - - - - - Tests to determine if the current culture is supported - based on a culture attribute. - - The attribute to examine - - - - - Test to determine if the a particular culture or comma- - delimited set of cultures is in use. - - Name of the culture or comma-separated list of culture ids - True if the culture is in use on the system - - - - Return the last failure reason. Results are not - defined if called before IsSupported( Attribute ) - is called. - - - - - ExceptionHelper provides static methods for working with exceptions - - - - - Builds up a message, using the Message field of the specified exception - as well as any InnerExceptions. - - The exception. - A combined message string. - - - - Builds up a message, using the Message field of the specified exception - as well as any InnerExceptions. - - The exception. - A combined stack trace. - - - - Gets the stack trace of the exception. - - The exception. - A string representation of the stack trace. - - - - A utility class to create TestCommands - - - - - Gets the command to be executed before any of - the child tests are run. - - A TestCommand - - - - Gets the command to be executed after all of the - child tests are run. - - A TestCommand - - - - Creates a test command for use in running this test. - - - - - - Creates a command for skipping a test. The result returned will - depend on the test RunState. - - - - - Builds the set up tear down list. - - Type of the fixture. - Type of the set up attribute. - Type of the tear down attribute. - A list of SetUpTearDownItems - - - - A CompositeWorkItem represents a test suite and - encapsulates the execution of the suite as well - as all its child tests. - - - - - A WorkItem may be an individual test case, a fixture or - a higher level grouping of tests. All WorkItems inherit - from the abstract WorkItem class, which uses the template - pattern to allow derived classes to perform work in - whatever way is needed. - - A WorkItem is created with a particular TestExecutionContext - and is responsible for re-establishing that context in the - current thread before it begins or resumes execution. - - - - - Creates a work item. - - The test for which this WorkItem is being created. - The filter to be used in selecting any child Tests. - - - - - Construct a WorkItem for a particular test. - - The test that the WorkItem will run - - - - Initialize the TestExecutionContext. This must be done - before executing the WorkItem. - - - Originally, the context was provided in the constructor - but delaying initialization of the context until the item - is about to be dispatched allows changes in the parent - context during OneTimeSetUp to be reflected in the child. - - The TestExecutionContext to use - - - - Execute the current work item, including any - child work items. - - - - - Method that performs actually performs the work. It should - set the State to WorkItemState.Complete when done. - - - - - Method called by the derived class when all work is complete - - - - - Event triggered when the item is complete - - - - - Gets the current state of the WorkItem - - - - - The test being executed by the work item - - - - - The execution context - - - - - The test actions to be performed before and after this test - - - - - The test result - - - - - Construct a CompositeWorkItem for executing a test suite - using a filter to select child tests. - - The TestSuite to be executed - A filter used to select child tests - - - - Method that actually performs the work. Overridden - in CompositeWorkItem to do setup, run all child - items and then do teardown. - - - - - A simplified implementation of .NET 4 CountdownEvent - for use in earlier versions of .NET. Only the methods - used by NUnit are implemented. - - - - - Construct a CountdownEvent - - The initial count - - - - Decrement the count by one - - - - - Block the thread until the count reaches zero - - - - - Gets the initial count established for the CountdownEvent - - - - - Gets the current count remaining for the CountdownEvent - - - - - An IWorkItemDispatcher handles execution of work items. - - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and used when stopping the run. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - A SimpleWorkItem represents a single test case and is - marked as completed immediately upon execution. This - class is also used for skipped or ignored test suites. - - - - - Construct a simple work item for a test. - - The test to be executed - The filter used to select this test - - - - Method that performs actually performs the work. - - - - - SimpleWorkItemDispatcher handles execution of WorkItems by - directly executing them. It is provided so that a dispatcher - is always available in the context, thereby simplifying the - code needed to run child tests. - - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and a thread is created on which to - run it. Subsequent calls come from the top level - item or its descendants on the proper thread. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - TextMessageWriter writes constraint descriptions and messages - in displayable form as a text stream. It tailors the display - of individual message components to form the standard message - format of NUnit assertion failure messages. - - - - - Prefix used for the expected value line of a message - - - - - Prefix used for the actual value line of a message - - - - - Length of a message prefix - - - - - Construct a TextMessageWriter - - - - - Construct a TextMessageWriter, specifying a user message - and optional formatting arguments. - - - - - - - Method to write single line message with optional args, usually - written to precede the general failure message, at a given - indentation level. - - The indentation level of the message - The message to be written - Any arguments used in formatting the message - - - - Display Expected and Actual lines for a constraint. This - is called by MessageWriter's default implementation of - WriteMessageTo and provides the generic two-line display. - - The result of the constraint that failed - - - - Display Expected and Actual lines for given _values. This - method may be called by constraints that need more control over - the display of actual and expected _values than is provided - by the default implementation. - - The expected value - The actual value causing the failure - - - - Display Expected and Actual lines for given _values, including - a tolerance value on the expected line. - - The expected value - The actual value causing the failure - The tolerance within which the test was made - - - - Display the expected and actual string _values on separate lines. - If the mismatch parameter is >=0, an additional line is displayed - line containing a caret that points to the mismatch point. - - The expected string value - The actual string value - The point at which the strings don't match or -1 - If true, case is ignored in string comparisons - If true, clip the strings to fit the max line length - - - - Writes the text for an actual value. - - The actual value. - - - - Writes the text for a generalized value. - - The value. - - - - Writes the text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Write the generic 'Expected' line for a constraint - - The constraint that failed - - - - Write the generic 'Expected' line for a given value - - The expected value - - - - Write the generic 'Expected' line for a given value - and tolerance. - - The expected value - The tolerance within which the test was made - - - - Write the generic 'Actual' line for a constraint - - The ConstraintResult for which the actual value is to be written - - - - Write the generic 'Actual' line for a given value - - The actual value causing a failure - - - - Gets or sets the maximum line length for this writer - - - - - The current state of a work item - - - - - Ready to run or continue - - - - - Work Item is executing - - - - - Complete - - - - - Combines multiple filters so that a test must pass all - of them in order to pass this filter. - - - - - A base class for multi-part filters - - - - - Interface to be implemented by filters applied to tests. - The filter applies when running the test, after it has been - loaded, since this is the only time an ITest exists. - - - - - Unique Empty filter. - - - - - Determine if a particular test passes the filter criteria. The default - implementation checks the test itself, its parents and any descendants. - - Derived classes may override this method or any of the Match methods - to change the behavior of the filter. - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - Determine whether the test itself matches the filter criteria, without - examining either parents or descendants. This is overridden by each - different type of filter to perform the necessary tests. - - The test to which the filter is applied - True if the filter matches the any parent of the test - - - - Determine whether any ancestor of the test matches the filter criteria - - The test to which the filter is applied - True if the filter matches the an ancestor of the test - - - - Determine whether any descendant of the test matches the filter criteria. - - The test to be matched - True if at least one descendant matches the filter criteria - - - - Create a TestFilter instance from an xml representation. - - - - - Create a TestFilter from it's TNode representation - - - - - Adds an XML node - - True if recursive - The added XML node - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Indicates whether this is the EmptyFilter - - - - - Indicates whether this is a top-level filter, - not contained in any other filter. - - - - - Nested class provides an empty filter - one that always - returns true when called. It never matches explicitly. - - - - - Constructs an empty CompositeFilter - - - - - Constructs a CompositeFilter from an array of filters - - - - - - Adds a filter to the list of filters - - The filter to be added - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Return a list of the composing filters. - - - - - Gets the element name - - Element name - - - - Constructs an empty AndFilter - - - - - Constructs an AndFilter from an array of filters - - - - - - Checks whether the AndFilter is matched by a test - - The test to be matched - True if all the component filters pass, otherwise false - - - - Checks whether the AndFilter is matched by a test - - The test to be matched - True if all the component filters match, otherwise false - - - - Gets the element name - - Element name - - - - CategoryFilter is able to select or exclude tests - based on their categories. - - - - - - ValueMatchFilter selects tests based on some value, which - is expected to be contained in the test. - - - - - Construct a ValueMatchFilter for a single value. - - The value to be included. - - - - Match the input provided by the derived class - - The value to be matchedT - True for a match, false otherwise. - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Returns the value matched by the filter - used for testing - - - - - Indicates whether the value is a regular expression - - - - - Gets the element name - - Element name - - - - Construct a CategoryFilter using a single category name - - A category name - - - - Check whether the filter matches a test - - The test to be matched - - - - - Gets the element name - - Element name - - - - ClassName filter selects tests based on the class FullName - - - - - Construct a FullNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - FullName filter selects tests based on their FullName - - - - - Construct a FullNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - IdFilter selects tests based on their id - - - - - Construct an IdFilter for a single value - - The id the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - FullName filter selects tests based on their FullName - - - - - Construct a MethodNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - NotFilter negates the operation of another filter - - - - - Construct a not filter on another filter - - The filter to be negated - - - - Determine if a particular test passes the filter criteria. The default - implementation checks the test itself, its parents and any descendants. - - Derived classes may override this method or any of the Match methods - to change the behavior of the filter. - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Check whether the filter matches a test - - The test to be matched - True if it matches, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Gets the base filter - - - - - Combines multiple filters so that a test must pass one - of them in order to pass this filter. - - - - - Constructs an empty OrFilter - - - - - Constructs an AndFilter from an array of filters - - - - - - Checks whether the OrFilter is matched by a test - - The test to be matched - True if any of the component filters pass, otherwise false - - - - Checks whether the OrFilter is matched by a test - - The test to be matched - True if any of the component filters match, otherwise false - - - - Gets the element name - - Element name - - - - PropertyFilter is able to select or exclude tests - based on their properties. - - - - - - Construct a PropertyFilter using a property name and expected value - - A property name - The expected value of the property - - - - Check whether the filter matches a test - - The test to be matched - - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Gets the element name - - Element name - - - - TestName filter selects tests based on their Name - - - - - Construct a TestNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - GenericMethodHelper is able to deduce the Type arguments for - a generic method from the actual arguments provided. - - - - - Construct a GenericMethodHelper for a method - - MethodInfo for the method to examine - - - - Return the type argments for the method, deducing them - from the arguments actually provided. - - The arguments to the method - An array of type arguments. - - - - InvalidTestFixtureException is thrown when an appropriate test - fixture constructor using the provided arguments cannot be found. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The message. - - - - Initializes a new instance of the class. - - The message. - The inner. - - - - InvalidTestFixtureException is thrown when an appropriate test - fixture constructor using the provided arguments cannot be found. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The message. - - - - Initializes a new instance of the class. - - The message. - The inner. - - - - The MethodWrapper class wraps a MethodInfo so that it may - be used in a platform-independent manner. - - - - - Construct a MethodWrapper for a Type and a MethodInfo. - - - - - Construct a MethodInfo for a given Type and method name. - - - - - Gets the parameters of the method. - - - - - - Returns the Type arguments of a generic method or the Type parameters of a generic method definition. - - - - - Replaces the type parameters of the method with the array of types provided and returns a new IMethodInfo. - - The type arguments to be used - A new IMethodInfo with the type arguments replaced - - - - Returns an array of custom attributes of the specified type applied to this method - - - - - Gets a value indicating whether one or more attributes of the spcified type are defined on the method. - - - - - Invokes the method, converting any TargetInvocationException to an NUnitException. - - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - Override ToString() so that error messages in NUnit's own tests make sense - - - - - Gets the Type from which this method was reflected. - - - - - Gets the MethodInfo for this method. - - - - - Gets the name of the method. - - - - - Gets a value indicating whether the method is abstract. - - - - - Gets a value indicating whether the method is public. - - - - - Gets a value indicating whether the method contains unassigned generic type parameters. - - - - - Gets a value indicating whether the method is a generic method. - - - - - Gets a value indicating whether the MethodInfo represents the definition of a generic method. - - - - - Gets the return Type of the method. - - - - - Thrown when an assertion failed. Here to preserve the inner - exception and hence its stack trace. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The error message that explains - the reason for the exception - - - - Initializes a new instance of the class. - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - The ParameterWrapper class wraps a ParameterInfo so that it may - be used in a platform-independent manner. - - - - - Construct a ParameterWrapper for a given method and parameter - - - - - - - Returns an array of custom attributes of the specified type applied to this method - - - - - Gets a value indicating whether one or more attributes of the specified type are defined on the parameter. - - - - - Gets a value indicating whether the parameter is optional - - - - - Gets an IMethodInfo representing the method for which this is a parameter. - - - - - Gets the underlying ParameterInfo - - - - - Gets the Type of the parameter - - - - - A PropertyBag represents a collection of name value pairs - that allows duplicate entries with the same key. Methods - are provided for adding a new pair as well as for setting - a key to a single value. All keys are strings but _values - may be of any type. Null _values are not permitted, since - a null entry represents the absence of the key. - - - - - Adds a key/value pair to the property set - - The key - The value - - - - Sets the value for a key, removing any other - _values that are already in the property set. - - - - - - - Gets a single value for a key, using the first - one if multiple _values are present and returning - null if the value is not found. - - - - - - - Gets a flag indicating whether the specified key has - any entries in the property set. - - The key to be checked - - True if their are _values present, otherwise false - - - - - Returns an XmlNode representating the current PropertyBag. - - Not used - An XmlNode representing the PropertyBag - - - - Returns an XmlNode representing the PropertyBag after - adding it as a child of the supplied parent node. - - The parent node. - Not used - - - - - Gets a collection containing all the keys in the property set - - - - - - Gets or sets the list of _values for a particular key - - - - - The PropertyNames class provides static constants for the - standard property ids that NUnit uses on tests. - - - - - The FriendlyName of the AppDomain in which the assembly is running - - - - - The selected strategy for joining parameter data into test cases - - - - - The process ID of the executing assembly - - - - - The stack trace from any data provider that threw - an exception. - - - - - The reason a test was not run - - - - - The author of the tests - - - - - The ApartmentState required for running the test - - - - - The categories applying to a test - - - - - The Description of a test - - - - - The number of threads to be used in running tests - - - - - The maximum time in ms, above which the test is considered to have failed - - - - - The ParallelScope associated with a test - - - - - The number of times the test should be repeated - - - - - Indicates that the test should be run on a separate thread - - - - - The culture to be set for a test - - - - - The UI culture to be set for a test - - - - - The type that is under test - - - - - The timeout value for the test - - - - - The test will be ignored until the given date - - - - - Randomizer returns a set of random _values in a repeatable - way, to allow re-running of tests if necessary. It extends - the .NET Random class, providing random values for a much - wider range of types. - - The class is used internally by the framework to generate - test case data and is also exposed for use by users through - the TestContext.Random property. - - - For consistency with the underlying Random Type, methods - returning a single value use the prefix "Next..." Those - without an argument return a non-negative value up to - the full positive range of the Type. Overloads are provided - for specifying a maximum or a range. Methods that return - arrays or strings use the prefix "Get..." to avoid - confusion with the single-value methods. - - - - - Default characters for random functions. - - Default characters are the English alphabet (uppercase & lowercase), arabic numerals, and underscore - - - - Get a Randomizer for a particular member, returning - one that has already been created if it exists. - This ensures that the same _values are generated - each time the tests are reloaded. - - - - - Get a randomizer for a particular parameter, returning - one that has already been created if it exists. - This ensures that the same values are generated - each time the tests are reloaded. - - - - - Create a new Randomizer using the next seed - available to ensure that each randomizer gives - a unique sequence of values. - - - - - - Default constructor - - - - - Construct based on seed value - - - - - - Returns a random unsigned int. - - - - - Returns a random unsigned int less than the specified maximum. - - - - - Returns a random unsigned int within a specified range. - - - - - Returns a non-negative random short. - - - - - Returns a non-negative random short less than the specified maximum. - - - - - Returns a non-negative random short within a specified range. - - - - - Returns a random unsigned short. - - - - - Returns a random unsigned short less than the specified maximum. - - - - - Returns a random unsigned short within a specified range. - - - - - Returns a random long. - - - - - Returns a random long less than the specified maximum. - - - - - Returns a non-negative random long within a specified range. - - - - - Returns a random ulong. - - - - - Returns a random ulong less than the specified maximum. - - - - - Returns a non-negative random long within a specified range. - - - - - Returns a random Byte - - - - - Returns a random Byte less than the specified maximum. - - - - - Returns a random Byte within a specified range - - - - - Returns a random SByte - - - - - Returns a random sbyte less than the specified maximum. - - - - - Returns a random sbyte within a specified range - - - - - Returns a random bool - - - - - Returns a random bool based on the probablility a true result - - - - - Returns a random double between 0.0 and the specified maximum. - - - - - Returns a random double within a specified range. - - - - - Returns a random float. - - - - - Returns a random float between 0.0 and the specified maximum. - - - - - Returns a random float within a specified range. - - - - - Returns a random enum value of the specified Type as an object. - - - - - Returns a random enum value of the specified Type. - - - - - Generate a random string based on the characters from the input string. - - desired length of output string. - string representing the set of characters from which to construct the resulting string - A random string of arbitrary length - - - - Generate a random string based on the characters from the input string. - - desired length of output string. - A random string of arbitrary length - Uses DefaultStringChars as the input character set - - - - Generate a random string based on the characters from the input string. - - A random string of the default length - Uses DefaultStringChars as the input character set - - - - Returns a random decimal. - - - - - Returns a random decimal between positive zero and the specified maximum. - - - - - Returns a random decimal within a specified range, which is not - permitted to exceed decimal.MaxVal in the current implementation. - - - A limitation of this implementation is that the range from min - to max must not exceed decimal.MaxVal. - - - - - Initial seed used to create randomizers for this run - - - - - Helper methods for inspecting a type by reflection. - - Many of these methods take ICustomAttributeProvider as an - argument to avoid duplication, even though certain attributes can - only appear on specific types of members, like MethodInfo or Type. - - In the case where a type is being examined for the presence of - an attribute, interface or named member, the Reflect methods - operate with the full name of the member being sought. This - removes the necessity of the caller having a reference to the - assembly that defines the item being sought and allows the - NUnit core to inspect assemblies that reference an older - version of the NUnit framework. - - - - - Examine a fixture type and return an array of methods having a - particular attribute. The array is order with base methods first. - - The type to examine - The attribute Type to look for - Specifies whether to search the fixture type inheritance chain - The array of methods found - - - - Examine a fixture type and return true if it has a method with - a particular attribute. - - The type to examine - The attribute Type to look for - True if found, otherwise false - - - - Invoke the default constructor on a Type - - The Type to be constructed - An instance of the Type - - - - Invoke a constructor on a Type with arguments - - The Type to be constructed - Arguments to the constructor - An instance of the Type - - - - Returns an array of types from an array of objects. - Used because the compact framework doesn't support - Type.GetTypeArray() - - An array of objects - An array of Types - - - - Invoke a parameterless method returning void on an object. - - A MethodInfo for the method to be invoked - The object on which to invoke the method - - - - Invoke a method, converting any TargetInvocationException to an NUnitException. - - A MethodInfo for the method to be invoked - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - Represents the result of running a single test case. - - - - - The TestResult class represents the result of a test. - - - - - The minimum duration for tests - - - - - Error message for when child tests have errors - - - - - Error message for when child tests are ignored - - - - - List of child results - - - - - Construct a test result given a Test - - The test to be used - - - - Returns the Xml representation of the result. - - If true, descendant results are included - An XmlNode representing the result - - - - Adds the XML representation of the result as a child of the - supplied parent node.. - - The parent node. - If true, descendant results are included - - - - - Adds a child result to this result, setting this result's - ResultState to Failure if the child result failed. - - The result to be added - - - - Set the result of the test - - The ResultState to use in the result - - - - Set the result of the test - - The ResultState to use in the result - A message associated with the result state - - - - Set the result of the test - - The ResultState to use in the result - A message associated with the result state - Stack trace giving the location of the command - - - - Set the test result based on the type of exception thrown - - The exception that was thrown - - - - Set the test result based on the type of exception thrown - - The exception that was thrown - THe FailureSite to use in the result - - - - RecordTearDownException appends the message and stacktrace - from an exception arising during teardown of the test - to any previously recorded information, so that any - earlier failure information is not lost. Note that - calling Assert.Ignore, Assert.Inconclusive, etc. during - teardown is treated as an error. If the current result - represents a suite, it may show a teardown error even - though all contained tests passed. - - The Exception to be recorded - - - - Adds a reason element to a node and returns it. - - The target node. - The new reason element. - - - - Adds a failure element to a node and returns it. - - The target node. - The new failure element. - - - - Gets the test with which this result is associated. - - - - - Gets the ResultState of the test result, which - indicates the success or failure of the test. - - - - - Gets the name of the test result - - - - - Gets the full name of the test result - - - - - Gets or sets the elapsed time for running the test in seconds - - - - - Gets or sets the time the test started running. - - - - - Gets or sets the time the test finished running. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. - - - - - Gets or sets the count of asserts executed - when running the test. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Indicates whether this result has any child results. - Test HasChildren before accessing Children to avoid - the creation of an empty collection. - - - - - Gets the collection of child results. - - - - - Gets a TextWriter, which will write output to be included in the result. - - - - - Gets any text output written to this result. - - - - - Construct a TestCaseResult based on a TestMethod - - A TestMethod to which the result applies. - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Represents the result of running a test suite - - - - - Construct a TestSuiteResult base on a TestSuite - - The TestSuite to which the result applies - - - - Add a child result - - The child result to be added - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - StackFilter class is used to remove internal NUnit - entries from a stack trace so that the resulting - trace provides better information about the test. - - - - - Filters a raw stack trace and returns the result. - - The original stack trace - A filtered stack trace - - - - Provides methods to support legacy string comparison methods. - - - - - Compares two strings for equality, ignoring case if requested. - - The first string. - The second string.. - if set to true, the case of the letters in the strings is ignored. - Zero if the strings are equivalent, a negative number if strA is sorted first, a positive number if - strB is sorted first - - - - Compares two strings for equality, ignoring case if requested. - - The first string. - The second string.. - if set to true, the case of the letters in the strings is ignored. - True if the strings are equivalent, false if not. - - - - The TestCaseParameters class encapsulates method arguments and - other selected parameters needed for constructing - a parameterized test case. - - - - - TestParameters is the abstract base class for all classes - that know how to provide data for constructing a test. - - - - - Default Constructor creates an empty parameter set - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a ParameterSet from an object implementing ITestData - - - - - - Applies ParameterSet _values to the test itself. - - A test. - - - - The RunState for this set of parameters. - - - - - The arguments to be used in running the test, - which must match the method signature. - - - - - A name to be used for this test case in lieu - of the standard generated name containing - the argument list. - - - - - Gets the property dictionary for this test - - - - - The original arguments provided by the user, - used for display purposes. - - - - - The expected result to be returned - - - - - Default Constructor creates an empty parameter set - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a ParameterSet from an object implementing ITestCaseData - - - - - - The expected result of the test, which - must match the method return type. - - - - - Gets a value indicating whether an expected result was specified. - - - - - Helper class used to save and restore certain static or - singleton settings in the environment that affect tests - or which might be changed by the user tests. - - An internal class is used to hold settings and a stack - of these objects is pushed and popped as Save and Restore - are called. - - - - - Link to a prior saved context - - - - - Indicates that a stop has been requested - - - - - The event listener currently receiving notifications - - - - - The number of assertions for the current test - - - - - The current culture - - - - - The current UI culture - - - - - The current test result - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - An existing instance of TestExecutionContext. - - - - The current context, head of the list of saved contexts. - - - - - Clear the current context. This is provided to - prevent "leakage" of the CallContext containing - the current context back to any runners. - - - - - Record any changes in the environment made by - the test code in the execution context so it - will be passed on to lower level tests. - - - - - Set up the execution environment to match a context. - Note that we may be running on the same thread where the - context was initially created or on a different thread. - - - - - Increments the assert count by one. - - - - - Increments the assert count by a specified amount. - - - - - Gets the current context. - - The current context. - - - - Gets or sets the current test - - - - - The time the current test started execution - - - - - The time the current test started in Ticks - - - - - Gets or sets the current test result - - - - - Gets a TextWriter that will send output to the current test result. - - - - - The current test object - that is the user fixture - object on which tests are being executed. - - - - - Get or set the working directory - - - - - Get or set indicator that run should stop on the first error - - - - - Gets an enum indicating whether a stop has been requested. - - - - - The current test event listener - - - - - The current WorkItemDispatcher - - - - - The ParallelScope to be used by tests running in this context. - For builds with out the parallel feature, it has no effect. - - - - - Gets the RandomGenerator specific to this Test - - - - - Gets the assert count. - - The assert count. - - - - Gets or sets the test case timeout value - - - - - Gets a list of ITestActions set by upstream tests - - - - - Saves or restores the CurrentCulture - - - - - Saves or restores the CurrentUICulture - - - - - Enumeration indicating whether the tests are - running normally or being cancelled. - - - - - Running normally with no stop requested - - - - - A graceful stop has been requested - - - - - A forced stop has been requested - - - - - The TestCaseParameters class encapsulates method arguments and - other selected parameters needed for constructing - a parameterized test case. - - - - - Default Constructor creates an empty parameter set - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a ParameterSet from an object implementing ITestCaseData - - - - - - Type arguments used to create a generic fixture instance - - - - - TestListener provides an implementation of ITestListener that - does nothing. It is used only through its NULL property. - - - - - Called when a test has just started - - The test that is starting - - - - Called when a test case has finished - - The result of the test - - - - Construct a new TestListener - private so it may not be used. - - - - - Get a listener that does nothing - - - - - TestNameGenerator is able to create test names according to - a coded pattern. - - - - - Construct a TestNameGenerator - - The pattern used by this generator. - - - - Get the display name for a TestMethod and it's arguments - - A TestMethod - The display name - - - - Get the display name for a TestMethod and it's arguments - - A TestMethod - Arguments to be used - The display name - - - - Get the display name for a MethodInfo - - A MethodInfo - The display name - - - - Get the display name for a method with args - - A MethodInfo - Argument list for the method - The display name - - - - TestProgressReporter translates ITestListener events into - the async callbacks that are used to inform the client - software about the progress of a test run. - - - - - Initializes a new instance of the class. - - The callback handler to be used for reporting progress. - - - - Called when a test has just started - - The test that is starting - - - - Called when a test has finished. Sends a result summary to the callback. - to - - The result of the test - - - - Returns the parent test item for the targer test item if it exists - - - parent test item - - - - Makes a string safe for use as an attribute, replacing - characters characters that can't be used with their - corresponding xml representations. - - The string to be used - A new string with the _values replaced - - - - ParameterizedFixtureSuite serves as a container for the set of test - fixtures created from a given Type using various parameters. - - - - - TestSuite represents a composite test, which contains other tests. - - - - - The Test abstract class represents a test within the framework. - - - - - Static value to seed ids. It's started at 1000 so any - uninitialized ids will stand out. - - - - - The SetUp methods. - - - - - The teardown methods - - - - - Constructs a test given its name - - The name of the test - - - - Constructs a test given the path through the - test hierarchy to its parent and a name. - - The parent tests full name - The name of the test - - - - TODO: Documentation needed for constructor - - - - - - Construct a test from a MethodInfo - - - - - - Creates a TestResult for this test. - - A TestResult suitable for this type of test. - - - - Modify a newly constructed test by applying any of NUnit's common - attributes, based on a supplied ICustomAttributeProvider, which is - usually the reflection element from which the test was constructed, - but may not be in some instances. The attributes retrieved are - saved for use in subsequent operations. - - An object deriving from MemberInfo - - - - Modify a newly constructed test by applying any of NUnit's common - attributes, based on a supplied ICustomAttributeProvider, which is - usually the reflection element from which the test was constructed, - but may not be in some instances. The attributes retrieved are - saved for use in subsequent operations. - - An object deriving from MemberInfo - - - - Add standard attributes and members to a test node. - - - - - - - Returns the Xml representation of the test - - If true, include child tests recursively - - - - - Returns an XmlNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Compares this test to another test for sorting purposes - - The other test - Value of -1, 0 or +1 depending on whether the current test is less than, equal to or greater than the other test - - - - Gets or sets the id of the test - - - - - - Gets or sets the name of the test - - - - - Gets or sets the fully qualified name of the test - - - - - - Gets the name of the class containing this test. Returns - null if the test is not associated with a class. - - - - - Gets the name of the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the TypeInfo of the fixture used in running this test - or null if no fixture type is associated with it. - - - - - Gets a MethodInfo for the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Whether or not the test should be run - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Gets a string representing the type of test. Used as an attribute - value in the XML representation of a test and has no other - function in the framework. - - - - - Gets a count of test cases represented by - or contained under this test. - - - - - Gets the properties for this test - - - - - Returns true if this is a TestSuite - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets the parent as a Test object. - Used by the core to set the parent. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets or sets a fixture object for running this test. - - - - - Static prefix used for ids in this AppDomain. - Set by FrameworkController. - - - - - Gets or Sets the Int value representing the seed for the RandomGenerator - - - - - - Our collection of child tests - - - - - Initializes a new instance of the class. - - The name of the suite. - - - - Initializes a new instance of the class. - - Name of the parent suite. - The name of the suite. - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - Sorts tests under this suite. - - - - - Adds a test to the suite. - - The test. - - - - Overridden to return a TestSuiteResult. - - A TestResult for this test. - - - - Returns an XmlNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Check that setup and teardown methods marked by certain attributes - meet NUnit's requirements and mark the tests not runnable otherwise. - - The attribute type to check for - - - - Gets this test's child tests - - The list of child tests - - - - Gets a count of test cases represented by - or contained under this test. - - - - - - The arguments to use in creating the fixture - - - - - Set to true to suppress sorting this suite's contents - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Initializes a new instance of the class. - - The ITypeInfo for the type that represents the suite. - - - - Gets a string representing the type of test - - - - - - ParameterizedMethodSuite holds a collection of individual - TestMethods with their arguments applied. - - - - - Construct from a MethodInfo - - - - - - Gets a string representing the type of test - - - - - - SetUpFixture extends TestSuite and supports - Setup and TearDown methods. - - - - - Initializes a new instance of the class. - - The type. - - - - TestAssembly is a TestSuite that represents the execution - of tests in a managed assembly. - - - - - Initializes a new instance of the class - specifying the Assembly and the path from which it was loaded. - - The assembly this test represents. - The path used to load the assembly. - - - - Initializes a new instance of the class - for a path which could not be loaded. - - The path used to load the assembly. - - - - Gets the Assembly represented by this instance. - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - TestFixture is a surrogate for a user test fixture class, - containing one or more tests. - - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - The TestMethod class represents a Test implemented as a method. - - - - - The ParameterSet used to create this test method - - - - - Initializes a new instance of the class. - - The method to be used as a test. - - - - Initializes a new instance of the class. - - The method to be used as a test. - The suite or fixture to which the new test will be added - - - - Overridden to return a TestCaseResult. - - A TestResult for this test. - - - - Returns a TNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Returns the name of the method - - - - - TypeHelper provides static methods that operate on Types. - - - - - A special value, which is used to indicate that BestCommonType() method - was unable to find a common type for the specified arguments. - - - - - Gets the display name for a Type as used by NUnit. - - The Type for which a display name is needed. - The display name for the Type - - - - Gets the display name for a Type as used by NUnit. - - The Type for which a display name is needed. - The arglist provided. - The display name for the Type - - - - Returns the best fit for a common type to be used in - matching actual arguments to a methods Type parameters. - - The first type. - The second type. - Either type1 or type2, depending on which is more general. - - - - Determines whether the specified type is numeric. - - The type to be examined. - - true if the specified type is numeric; otherwise, false. - - - - - Convert an argument list to the required parameter types. - Currently, only widening numeric conversions are performed. - - An array of args to be converted - A ParameterInfo[] whose types will be used as targets - - - - Determines whether this instance can deduce type args for a generic type from the supplied arguments. - - The type to be examined. - The arglist. - The type args to be used. - - true if this the provided args give sufficient information to determine the type args to be used; otherwise, false. - - - - - Gets the _values for an enumeration, using Enum.GetTypes - where available, otherwise through reflection. - - - - - - - Gets the ids of the _values for an enumeration, - using Enum.GetNames where available, otherwise - through reflection. - - - - - - - The TypeWrapper class wraps a Type so it may be used in - a platform-independent manner. - - - - - Construct a TypeWrapper for a specified Type. - - - - - Returns true if the Type wrapped is T - - - - - Get the display name for this type - - - - - Get the display name for an object of this type, constructed with the specified args. - - - - - Returns a new ITypeInfo representing an instance of this generic Type using the supplied Type arguments - - - - - Returns a Type representing a generic type definition from which this Type can be constructed. - - - - - Returns an array of custom attributes of the specified type applied to this type - - - - - Returns a value indicating whether the type has an attribute of the specified type. - - - - - - - - Returns a flag indicating whether this type has a method with an attribute of the specified type. - - - - - - - Returns an array of IMethodInfos for methods of this Type - that match the specified flags. - - - - - Returns a value indicating whether this Type has a public constructor taking the specified argument Types. - - - - - Construct an object of this Type, using the specified arguments. - - - - - Override ToString() so that error messages in NUnit's own tests make sense - - - - - Gets the underlying Type on which this TypeWrapper is based. - - - - - Gets the base type of this type as an ITypeInfo - - - - - Gets the Name of the Type - - - - - Gets the FullName of the Type - - - - - Gets the assembly in which the type is declared - - - - - Gets the namespace of the Type - - - - - Gets a value indicating whether the type is abstract. - - - - - Gets a value indicating whether the Type is a generic Type - - - - - Gets a value indicating whether the Type has generic parameters that have not been replaced by specific Types. - - - - - Gets a value indicating whether the Type is a generic Type definition - - - - - Gets a value indicating whether the type is sealed. - - - - - Gets a value indicating whether this type represents a static class. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable to the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable to the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that tests whether the actual value falls - inclusively within a specified range. - - from must be less than or equal to true - Inclusive beginning of the range. Must be less than or equal to to. - Inclusive end of the range. Must be greater than or equal to from. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - The Iz class is a synonym for Is intended for use in VB, - which regards Is as a keyword. - - - - - The List class is a helper class with properties and methods - that supply a number of constraints used with lists and collections. - - - - - List.Map returns a ListMapper, which can be used to map - the original collection to another collection. - - - - - - - ListMapper is used to transform a collection used as an actual argument - producing another collection to be used in the assertion. - - - - - Construct a ListMapper based on a collection - - The collection to be transformed - - - - Produces a collection containing all the _values of a property - - The collection of property _values - - - - - The SpecialValue enum is used to represent TestCase arguments - that cannot be used as arguments to an Attribute. - - - - - Null represents a null value, which cannot be used as an - argument to an attriute under .NET 1.x - - - - - Basic Asserts on strings. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - - - - Asserts that a string is not found within another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - - - - Asserts that two strings are not equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that two strings are not equal, without regard to case. - - The expected string - The actual string - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - - - - The TestCaseData class represents a set of arguments - and other parameter info to be used for a parameterized - test case. It is derived from TestCaseParameters and adds a - fluent syntax for use in initializing the test case. - - - - - Initializes a new instance of the class. - - The arguments. - - - - Initializes a new instance of the class. - - The argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - The third argument. - - - - Sets the expected result for the test - - The expected result - A modified TestCaseData - - - - Sets the name of the test case - - The modified TestCaseData instance - - - - Sets the description for the test case - being constructed. - - The description. - The modified TestCaseData instance. - - - - Applies a category to the test - - - - - - - Applies a named property to the test - - - - - - - - Applies a named property to the test - - - - - - - - Applies a named property to the test - - - - - - - - Marks the test case as explicit. - - - - - Marks the test case as explicit, specifying the reason. - - - - - Ignores this TestCase, specifying the reason. - - The reason. - - - - - Provide the context information of the current test. - This is an adapter for the internal ExecutionContext - class, hiding the internals from the user test. - - - - - Construct a TestContext for an ExecutionContext - - The ExecutionContext to adapt - - - Write the string representation of a boolean value to the current result - - - Write a char to the current result - - - Write a char array to the current result - - - Write the string representation of a double to the current result - - - Write the string representation of an Int32 value to the current result - - - Write the string representation of an Int64 value to the current result - - - Write the string representation of a decimal value to the current result - - - Write the string representation of an object to the current result - - - Write the string representation of a Single value to the current result - - - Write a string to the current result - - - Write the string representation of a UInt32 value to the current result - - - Write the string representation of a UInt64 value to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a line terminator to the current result - - - Write the string representation of a boolean value to the current result followed by a line terminator - - - Write a char to the current result followed by a line terminator - - - Write a char array to the current result followed by a line terminator - - - Write the string representation of a double to the current result followed by a line terminator - - - Write the string representation of an Int32 value to the current result followed by a line terminator - - - Write the string representation of an Int64 value to the current result followed by a line terminator - - - Write the string representation of a decimal value to the current result followed by a line terminator - - - Write the string representation of an object to the current result followed by a line terminator - - - Write the string representation of a Single value to the current result followed by a line terminator - - - Write a string to the current result followed by a line terminator - - - Write the string representation of a UInt32 value to the current result followed by a line terminator - - - Write the string representation of a UInt64 value to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - - Get the current test context. This is created - as needed. The user may save the context for - use within a test, but it should not be used - outside the test for which it is created. - - - - - Gets a TextWriter that will send output to the current test result. - - - - - Get a representation of the current test. - - - - - Gets a Representation of the TestResult for the current test. - - - - - Gets the directory to be used for outputting files created - by this test run. - - - - - Gets the random generator. - - - The random generator. - - - - - TestAdapter adapts a Test for consumption by - the user test code. - - - - - Construct a TestAdapter for a Test - - The Test to be adapted - - - - Gets the unique Id of a test - - - - - The name of the test, which may or may not be - the same as the method name. - - - - - The name of the method representing the test. - - - - - The FullName of the test - - - - - The ClassName of the test - - - - - The properties of the test. - - - - - ResultAdapter adapts a TestResult for consumption by - the user test code. - - - - - Construct a ResultAdapter for a TestResult - - The TestResult to be adapted - - - - Gets a ResultState representing the outcome of the test. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - The TestFixtureData class represents a set of arguments - and other parameter info to be used for a parameterized - fixture. It is derived from TestFixtureParameters and adds a - fluent syntax for use in initializing the fixture. - - - - - Initializes a new instance of the class. - - The arguments. - - - - Initializes a new instance of the class. - - The argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - The third argument. - - - - Marks the test fixture as explicit. - - - - - Marks the test fixture as explicit, specifying the reason. - - - - - Ignores this TestFixture, specifying the reason. - - The reason. - - - - - Helper class with properties and methods that supply - constraints that operate on exceptions. - - - - - Creates a constraint specifying the exact type of exception expected - - - - - Creates a constraint specifying the exact type of exception expected - - - - - Creates a constraint specifying the type of exception expected - - - - - Creates a constraint specifying the type of exception expected - - - - - Creates a constraint specifying an expected exception - - - - - Creates a constraint specifying an exception with a given InnerException - - - - - Creates a constraint specifying an expected TargetInvocationException - - - - - Creates a constraint specifying an expected ArgumentException - - - - - Creates a constraint specifying an expected ArgumentNUllException - - - - - Creates a constraint specifying an expected InvalidOperationException - - - - - Creates a constraint specifying that no exception is thrown - - - - diff --git a/packages/NUnit.3.0.0/lib/net20/nunit.framework.dll b/packages/NUnit.3.0.0/lib/net20/nunit.framework.dll deleted file mode 100644 index 2c1d9efa6..000000000 Binary files a/packages/NUnit.3.0.0/lib/net20/nunit.framework.dll and /dev/null differ diff --git a/packages/NUnit.3.0.0/lib/net20/nunit.framework.xml b/packages/NUnit.3.0.0/lib/net20/nunit.framework.xml deleted file mode 100644 index 85eaefaf2..000000000 --- a/packages/NUnit.3.0.0/lib/net20/nunit.framework.xml +++ /dev/null @@ -1,16850 +0,0 @@ - - - - nunit.framework - - - - - AssemblyHelper provides static methods for working - with assemblies. - - - - - Gets the path from which the assembly defining a type was loaded. - - The Type. - The path. - - - - Gets the path from which an assembly was loaded. - - The assembly. - The path. - - - - Gets the path to the directory from which an assembly was loaded. - - The assembly. - The path. - - - - Gets the AssemblyName of an assembly. - - The assembly - An AssemblyName - - - - Loads an assembly given a string, which may be the - path to the assembly or the AssemblyName - - - - - - - Gets the assembly path from code base. - - Public for testing purposes - The code base. - - - - - Env is a static class that provides some of the features of - System.Environment that are not available under all runtimes - - - - - The newline sequence in the current environment. - - - - - Path to the 'My Documents' folder - - - - - Directory used for file output if not specified on commandline. - - - - - Class used to guard against unexpected argument values - or operations by throwing an appropriate exception. - - - - - Throws an exception if an argument is null - - The value to be tested - The name of the argument - - - - Throws an exception if a string argument is null or empty - - The value to be tested - The name of the argument - - - - Throws an ArgumentOutOfRangeException if the specified condition is not met. - - The condition that must be met - The exception message to be used - The name of the argument - - - - Throws an ArgumentException if the specified condition is not met. - - The condition that must be met - The exception message to be used - The name of the argument - - - - Throws an InvalidOperationException if the specified condition is not met. - - The condition that must be met - The exception message to be used - - - - Interface for logging within the engine - - - - - Logs the specified message at the error level. - - The message. - - - - Logs the specified message at the error level. - - The message. - The arguments. - - - - Logs the specified message at the warning level. - - The message. - - - - Logs the specified message at the warning level. - - The message. - The arguments. - - - - Logs the specified message at the info level. - - The message. - - - - Logs the specified message at the info level. - - The message. - The arguments. - - - - Logs the specified message at the debug level. - - The message. - - - - Logs the specified message at the debug level. - - The message. - The arguments. - - - - InternalTrace provides facilities for tracing the execution - of the NUnit framework. Tests and classes under test may make use - of Console writes, System.Diagnostics.Trace or various loggers and - NUnit itself traps and processes each of them. For that reason, a - separate internal trace is needed. - - Note: - InternalTrace uses a global lock to allow multiple threads to write - trace messages. This can easily make it a bottleneck so it must be - used sparingly. Keep the trace Level as low as possible and only - insert InternalTrace writes where they are needed. - TODO: add some buffering and a separate writer thread as an option. - TODO: figure out a way to turn on trace in specific classes only. - - - - - Initialize the internal trace facility using the name of the log - to be written to and the trace level. - - The log name - The trace level - - - - Initialize the internal trace using a provided TextWriter and level - - A TextWriter - The InternalTraceLevel - - - - Get a named Logger - - - - - - Get a logger named for a particular Type. - - - - - Gets a flag indicating whether the InternalTrace is initialized - - - - - InternalTraceLevel is an enumeration controlling the - level of detailed presented in the internal log. - - - - - Use the default settings as specified by the user. - - - - - Do not display any trace messages - - - - - Display Error messages only - - - - - Display Warning level and higher messages - - - - - Display informational and higher messages - - - - - Display debug messages and higher - i.e. all messages - - - - - Display debug messages and higher - i.e. all messages - - - - - A trace listener that writes to a separate file per domain - and process using it. - - - - - Construct an InternalTraceWriter that writes to a file. - - Path to the file to use - - - - Construct an InternalTraceWriter that writes to a - TextWriter provided by the caller. - - - - - - Writes a character to the text string or stream. - - The character to write to the text stream. - - - - Writes a string to the text string or stream. - - The string to write. - - - - Writes a string followed by a line terminator to the text string or stream. - - The string to write. If is null, only the line terminator is written. - - - - Releases the unmanaged resources used by the and optionally releases the managed resources. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - Clears all buffers for the current writer and causes any buffered data to be written to the underlying device. - - - - - Returns the character encoding in which the output is written. - - The character encoding in which the output is written. - - - - Provides internal logging to the NUnit framework - - - - - Initializes a new instance of the class. - - The name. - The log level. - The writer where logs are sent. - - - - Logs the message at error level. - - The message. - - - - Logs the message at error level. - - The message. - The message arguments. - - - - Logs the message at warm level. - - The message. - - - - Logs the message at warning level. - - The message. - The message arguments. - - - - Logs the message at info level. - - The message. - - - - Logs the message at info level. - - The message. - The message arguments. - - - - Logs the message at debug level. - - The message. - - - - Logs the message at debug level. - - The message. - The message arguments. - - - - PackageSettings is a static class containing constant values that - are used as keys in setting up a TestPackage. These values are used in - the engine and framework. Setting values may be a string, int or bool. - - - - - Flag (bool) indicating whether tests are being debugged. - - - - - The InternalTraceLevel for this run. Values are: "Default", - "Off", "Error", "Warning", "Info", "Debug", "Verbose". - Default is "Off". "Debug" and "Verbose" are synonyms. - - - - - Full path of the directory to be used for work and result files. - This path is provided to tests by the frameowrk TestContext. - - - - - The name of the config to use in loading a project. - If not specified, the first config found is used. - - - - - Bool indicating whether the engine should determine the private - bin path by examining the paths to all the tests. Defaults to - true unless PrivateBinPath is specified. - - - - - The ApplicationBase to use in loading the tests. If not - specified, and each assembly has its own process, then the - location of the assembly is used. For multiple assemblies - in a single process, the closest common root directory is used. - - - - - Path to the config file to use in running the tests. - - - - - Bool flag indicating whether a debugger should be launched at agent - startup. Used only for debugging NUnit itself. - - - - - Indicates how to load tests across AppDomains. Values are: - "Default", "None", "Single", "Multiple". Default is "Multiple" - if more than one assembly is loaded in a process. Otherwise, - it is "Single". - - - - - The private binpath used to locate assemblies. Directory paths - is separated by a semicolon. It's an error to specify this and - also set AutoBinPath to true. - - - - - The maximum number of test agents permitted to run simultneously. - Ignored if the ProcessModel is not set or defaulted to Multiple. - - - - - Indicates how to allocate assemblies to processes. Values are: - "Default", "Single", "Separate", "Multiple". Default is "Multiple" - for more than one assembly, "Separate" for a single assembly. - - - - - Indicates the desired runtime to use for the tests. Values - are strings like "net-4.5", "mono-4.0", etc. Default is to - use the target framework for which an assembly was built. - - - - - Bool flag indicating that the test should be run in a 32-bit process - on a 64-bit system. By default, NUNit runs in a 64-bit process on - a 64-bit system. Ignored if set on a 32-bit system. - - - - - Indicates that test runners should be disposed after the tests are executed - - - - - Bool flag indicating that the test assemblies should be shadow copied. - Defaults to false. - - - - - Integer value in milliseconds for the default timeout value - for test cases. If not specified, there is no timeout except - as specified by attributes on the tests themselves. - - - - - A TextWriter to which the internal trace will be sent. - - - - - A list of tests to be loaded. - - - - - The number of test threads to run for the assembly. If set to - 1, a single queue is used. If set to 0, tests are executed - directly, without queuing. - - - - - The random seed to be used for this assembly. If specified - as the value reported from a prior run, the framework should - generate identical random values for tests as were used for - that run, provided that no change has been made to the test - assembly. Default is a random value itself. - - - - - If true, execution stops after the first error or failure. - - - - - If true, use of the event queue is suppressed and test events are synchronous. - - - - - The different targets a test action attribute can be applied to - - - - - Default target, which is determined by where the action attribute is attached - - - - - Target a individual test case - - - - - Target a suite of test cases - - - - - FrameworkController provides a facade for use in loading, browsing - and running tests without requiring a reference to the NUnit - framework. All calls are encapsulated in constructors for - this class and its nested classes, which only require the - types of the Common Type System as arguments. - - The controller supports four actions: Load, Explore, Count and Run. - They are intended to be called by a driver, which should allow for - proper sequencing of calls. Load must be called before any of the - other actions. The driver may support other actions, such as - reload on run, by combining these calls. - - - - - A MarshalByRefObject that lives forever - - - - - Obtains a lifetime service object to control the lifetime policy for this instance. - - - - - Construct a FrameworkController using the default builder and runner. - - The AssemblyName or path to the test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - - - - Construct a FrameworkController using the default builder and runner. - - The test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - - - - Construct a FrameworkController, specifying the types to be used - for the runner and builder. This constructor is provided for - purposes of development. - - The full AssemblyName or the path to the test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - The Type of the test runner - The Type of the test builder - - - - Construct a FrameworkController, specifying the types to be used - for the runner and builder. This constructor is provided for - purposes of development. - - The test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - The Type of the test runner - The Type of the test builder - - - - Inserts environment element - - Target node - The new node - - - - Inserts settings element - - Target node - Settings dictionary - The new node - - - - Gets the ITestAssemblyBuilder used by this controller instance. - - The builder. - - - - Gets the ITestAssemblyRunner used by this controller instance. - - The runner. - - - - Gets the AssemblyName or the path for which this FrameworkController was created - - - - - Gets the Assembly for which this - - - - - Gets a dictionary of settings for the FrameworkController - - - - - FrameworkControllerAction is the base class for all actions - performed against a FrameworkController. - - - - - LoadTestsAction loads a test into the FrameworkController - - - - - LoadTestsAction loads the tests in an assembly. - - The controller. - The callback handler. - - - - ExploreTestsAction returns info about the tests in an assembly - - - - - Initializes a new instance of the class. - - The controller for which this action is being performed. - Filter used to control which tests are included (NYI) - The callback handler. - - - - CountTestsAction counts the number of test cases in the loaded TestSuite - held by the FrameworkController. - - - - - Construct a CountsTestAction and perform the count of test cases. - - A FrameworkController holding the TestSuite whose cases are to be counted - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - RunTestsAction runs the loaded TestSuite held by the FrameworkController. - - - - - Construct a RunTestsAction and run all tests in the loaded TestSuite. - - A FrameworkController holding the TestSuite to run - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - RunAsyncAction initiates an asynchronous test run, returning immediately - - - - - Construct a RunAsyncAction and run all tests in the loaded TestSuite. - - A FrameworkController holding the TestSuite to run - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - StopRunAction stops an ongoing run. - - - - - Construct a StopRunAction and stop any ongoing run. If no - run is in process, no error is raised. - - The FrameworkController for which a run is to be stopped. - True the stop should be forced, false for a cooperative stop. - >A callback handler used to report results - A forced stop will cause threads and processes to be killed as needed. - - - - Implementation of ITestAssemblyRunner - - - - - The ITestAssemblyRunner interface is implemented by classes - that are able to execute a suite of tests loaded - from an assembly. - - - - - Loads the tests found in an Assembly, returning an - indication of whether or not the load succeeded. - - File name of the assembly to load - Dictionary of options to use in loading the test - An ITest representing the loaded tests - - - - Loads the tests found in an Assembly, returning an - indication of whether or not the load succeeded. - - The assembly to load - Dictionary of options to use in loading the test - An ITest representing the loaded tests - - - - Count Test Cases using a filter - - The filter to apply - The number of test cases found - - - - Run selected tests and return a test result. The test is run synchronously, - and the listener interface is notified as it progresses. - - Interface to receive ITestListener notifications. - A test filter used to select tests to be run - - - - Run selected tests asynchronously, notifying the listener interface as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - - - Wait for the ongoing run to complete. - - Time to wait in milliseconds - True if the run completed, otherwise false - - - - Signal any test run that is in process to stop. Return without error if no test is running. - - If true, kill any test-running threads - - - - Gets the tree of loaded tests, or null if - no tests have been loaded. - - - - - Gets the tree of test results, if the test - run is completed, otherwise null. - - - - - Indicates whether a test has been loaded - - - - - Indicates whether a test is currently running - - - - - Indicates whether a test run is complete - - - - - Initializes a new instance of the class. - - The builder. - - - - Loads the tests found in an Assembly - - File name of the assembly to load - Dictionary of option settings for loading the assembly - True if the load was successful - - - - Loads the tests found in an Assembly - - The assembly to load - Dictionary of option settings for loading the assembly - True if the load was successful - - - - Count Test Cases using a filter - - The filter to apply - The number of test cases found - - - - Run selected tests and return a test result. The test is run synchronously, - and the listener interface is notified as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - - - - Run selected tests asynchronously, notifying the listener interface as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - RunAsync is a template method, calling various abstract and - virtual methods to be overridden by derived classes. - - - - - Wait for the ongoing run to complete. - - Time to wait in milliseconds - True if the run completed, otherwise false - - - - Initiate the test run. - - - - - Signal any test run that is in process to stop. Return without error if no test is running. - - If true, kill any test-running threads - - - - Create the initial TestExecutionContext used to run tests - - The ITestListener specified in the RunAsync call - - - - Handle the the Completed event for the top level work item - - - - - Gets the default level of parallel execution (worker threads) - - - - - The tree of tests that was loaded by the builder - - - - - The test result, if a run has completed - - - - - Indicates whether a test is loaded - - - - - Indicates whether a test is running - - - - - Indicates whether a test run is complete - - - - - Our settings, specified when loading the assembly - - - - - The top level WorkItem created for the assembly as a whole - - - - - The TestExecutionContext for the top level WorkItem - - - - - Marks a test that must run in a particular threading apartment state, causing it - to run in a separate thread if necessary. - - - - - PropertyAttribute is used to attach information to a test as a name/value pair.. - - - - - The abstract base class for all custom attributes defined by NUnit. - - - - - Default constructor - - - - - The IApplyToTest interface is implemented by self-applying - attributes that modify the state of a test in some way. - - - - - Modifies a test as defined for the specific attribute. - - The test to modify - - - - Construct a PropertyAttribute with a name and string value - - The name of the property - The property value - - - - Construct a PropertyAttribute with a name and int value - - The name of the property - The property value - - - - Construct a PropertyAttribute with a name and double value - - The name of the property - The property value - - - - Constructor for derived classes that set the - property dictionary directly. - - - - - Constructor for use by derived classes that use the - name of the type as the property name. Derived classes - must ensure that the Type of the property value is - a standard type supported by the BCL. Any custom - types will cause a serialization Exception when - in the client. - - - - - Modifies a test by adding properties to it. - - The test to modify - - - - Gets the property dictionary for this attribute - - - - - Construct an ApartmentAttribute - - The apartment state that this test must be run under. You must pass in a valid apartment state. - - - - Provides the Author of a test or test fixture. - - - - - Initializes a new instance of the class. - - The name of the author. - - - - Initializes a new instance of the class. - - The name of the author. - The email address of the author. - - - - Marks a test to use a particular CombiningStrategy to join - any parameter data provided. Since this is the default, the - attribute is optional. - - - - - The ITestBuilder interface is exposed by a class that knows how to - build one or more TestMethods from a MethodInfo. In general, it is exposed - by an attribute, which has additional information available to provide - the necessary test parameters to distinguish the test cases built. - - - - - Build one or more TestMethods from the provided MethodInfo. - - The method to be used as a test - The TestSuite to which the method will be added - A TestMethod object - - - - Construct a CombiningStrategyAttribute incorporating an object - that implements ICombiningStrategy. - - Combining strategy to be used - - - - Construct a CombiningStrategyAttribute incorporating an object - that implements ICombiningStrategy. This constructor is provided - for CLS compliance. - - Combining strategy to be used - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Modify the test by adding the name of the combining strategy - to the properties. - - The test to modify - - - - RepeatAttribute may be applied to test case in order - to run it multiple times. - - - - - Objects implementing this interface are used to wrap - the entire test, including SetUp and TearDown. - - - - - ICommandWrapper is implemented by attributes and other - objects able to wrap a TestCommand with another command. - - - Attributes or other objects should implement one of the - derived interfaces, rather than this one, since they - indicate in which part of the command chain the wrapper - should be applied. - - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - Construct a RepeatAttribute - - The number of times to run the test - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - The test command for the RetryAttribute - - - - - TODO: Documentation needed for class - - - - - TestCommand is the abstract base class for all test commands - in the framework. A TestCommand represents a single stage in - the execution of a test, e.g.: SetUp/TearDown, checking for - Timeout, verifying the returned result from a method, etc. - - TestCommands may decorate other test commands so that the - execution of a lower-level command is nested within that - of a higher level command. All nested commands are executed - synchronously, as a single unit. Scheduling test execution - on separate threads is handled at a higher level, using the - task dispatcher. - - - - - Construct a TestCommand for a test. - - The test to be executed - - - - Runs the test in a specified context, returning a TestResult. - - The TestExecutionContext to be used for running the test. - A TestResult - - - - Gets the test associated with this command. - - - - TODO: Documentation needed for field - - - - TODO: Documentation needed for constructor - - - - - - Initializes a new instance of the class. - - The inner command. - The number of repetitions - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - Attribute used to identify a method that is called once - after all the child tests have run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Attribute used to identify a method that is called once - to perform setup before any child tests are run. - - - - - LevelOfParallelismAttribute is used to set the number of worker threads - that may be allocated by the framework for running tests. - - - - - Construct a LevelOfParallelismAttribute. - - The number of worker threads to be created by the framework. - - - - ParallelizableAttribute is used to mark tests that may be run in parallel. - - - - - The IApplyToContext interface is implemented by attributes - that want to make changes to the execution context before - a test is run. - - - - - Apply changes to the execution context - - The execution context - - - - Construct a ParallelizableAttribute using default ParallelScope.Self. - - - - - Construct a ParallelizableAttribute with a specified scope. - - The ParallelScope associated with this attribute. - - - - Modify the context to be used for child tests - - The current TestExecutionContext - - - - The ParallelScope enumeration permits specifying the degree to - which a test and its descendants may be run in parallel. - - - - - No Parallelism is permitted - - - - - The test itself may be run in parallel with others at the same level - - - - - Descendants of the test may be run in parallel with one another - - - - - Descendants of the test down to the level of TestFixtures may be run in parallel - - - - - Provide actions to execute before and after tests. - - - - - When implemented by an attribute, this interface implemented to provide actions to execute before and after tests. - - - - - Executed before each test is run - - The test that is going to be run. - - - - Executed after each test is run - - The test that has just been run. - - - - Provides the target for the action attribute - - The target for the action attribute - - - - Executed before each test is run - - The test that is going to be run. - - - - Executed after each test is run - - The test that has just been run. - - - - Provides the target for the action attribute - - - - - TestCaseSourceAttribute indicates the source to be used to - provide test fixture instances for a test class. - - - - - The IFixtureBuilder interface is exposed by a class that knows how to - build a TestFixture from one or more Types. In general, it is exposed - by an attribute, but may be implemented in a helper class used by the - attribute in some cases. - - - - - Build one or more TestFixtures from type provided. At least one - non-null TestSuite must always be returned, since the method is - generally called because the user has marked the target class as - a fixture. If something prevents the fixture from being used, it - will be returned nonetheless, labelled as non-runnable. - - The type info of the fixture to be used. - A TestSuite object or one derived from TestSuite. - - - - Error message string is public so the tests can use it - - - - - Construct with the name of the method, property or field that will provide data - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Construct with a Type - - The type that will provide data - - - - Construct one or more TestFixtures from a given Type, - using available parameter data. - - The TypeInfo for which fixures are to be constructed. - One or more TestFixtures as TestSuite - - - - Returns a set of ITestFixtureData items for use as arguments - to a parameterized test fixture. - - The type for which data is needed. - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - Gets or sets the category associated with every fixture created from - this attribute. May be a single category or a comma-separated list. - - - - - Indicates which class the test or test fixture is testing - - - - - Initializes a new instance of the class. - - The type that is being tested. - - - - Initializes a new instance of the class. - - The type that is being tested. - - - - Provides a platform-independent methods for getting attributes - for use by AttributeConstraint and AttributeExistsConstraint. - - - - - Gets the custom attributes from the given object. - - Portable libraries do not have an ICustomAttributeProvider, so we need to cast to each of - it's direct subtypes and try to get attributes off those instead. - The actual. - Type of the attribute. - if set to true [inherit]. - A list of the given attribute on the given object. - - - - Enables compiling extension methods in .NET 2.0 - - - - - Provides NUnit specific extensions to aid in Reflection - across multiple frameworks - - - This version of the class supplies GetTypeInfo() on platforms - that don't support it. - - - - - GetTypeInfo gives access to most of the Type information we take for granted - on .NET Core and Windows Runtime. Rather than #ifdef different code for different - platforms, it is easiest to just code all platforms as if they worked this way, - thus the simple passthrough. - - - - - - - This class is a System.Diagnostics.Stopwatch on operating systems that support it. On those that don't, - it replicates the functionality at the resolution supported. - - - - - CollectionSupersetConstraint is used to determine whether - one collection is a superset of another - - - - - CollectionItemsEqualConstraint is the abstract base class for all - collection constraints that apply some notion of item equality - as a part of their operation. - - - - - CollectionConstraint is the abstract base class for - constraints that operate on collections. - - - - - The Constraint class is the base of all built-in constraints - within NUnit. It provides the operator overloads used to combine - constraints. - - - - - Interface for all constraints - - - - - The IResolveConstraint interface is implemented by all - complete and resolvable constraints and expressions. - - - - - Return the top-level constraint for this expression - - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - The default implementation simply dereferences the value but - derived classes may override it to provide for delayed processing. - - A reference to the value to be tested - A ConstraintResult - - - - The display name of this Constraint for use by ToString(). - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Arguments provided to this Constraint, for use in - formatting the description. - - - - - The ConstraintBuilder holding this constraint - - - - - Construct a constraint with optional arguments - - Arguments to be saved - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - The default implementation simply dereferences the value but - derived classes may override it to provide for delayed processing. - - A reference to the value to be tested - A ConstraintResult - - - - Default override of ToString returns the constraint DisplayName - followed by any arguments within angle brackets. - - - - - - Returns the string representation of this constraint - - - - - This operator creates a constraint that is satisfied only if both - argument constraints are satisfied. - - - - - This operator creates a constraint that is satisfied if either - of the argument constraints is satisfied. - - - - - This operator creates a constraint that is satisfied if the - argument constraint is not satisfied. - - - - - Returns a DelayedConstraint with the specified delay time. - - The delay in milliseconds. - - - - - Returns a DelayedConstraint with the specified delay time - and polling interval. - - The delay in milliseconds. - The interval at which to test the constraint. - - - - - Resolves any pending operators and returns the resolved constraint. - - - - - The display name of this Constraint for use by ToString(). - The default value is the name of the constraint with - trailing "Constraint" removed. Derived classes may set - this to another name in their constructors. - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Arguments provided to this Constraint, for use in - formatting the description. - - - - - The ConstraintBuilder holding this constraint - - - - - Returns a ConstraintExpression by appending And - to the current constraint. - - - - - Returns a ConstraintExpression by appending And - to the current constraint. - - - - - Returns a ConstraintExpression by appending Or - to the current constraint. - - - - - Construct an empty CollectionConstraint - - - - - Construct a CollectionConstraint - - - - - - Determines whether the specified enumerable is empty. - - The enumerable. - - true if the specified enumerable is empty; otherwise, false. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Protected method to be implemented by derived classes - - - - - - - Construct an empty CollectionConstraint - - - - - Construct a CollectionConstraint - - - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Compares two collection members for equality - - - - - Return a new CollectionTally for use in making tests - - The collection to be included in the tally - - - - Flag the constraint to ignore case and return self. - - - - - Construct a CollectionSupersetConstraint - - The collection that the actual value is expected to be a superset of - - - - Test whether the actual collection is a superset of - the expected collection provided. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - DictionaryContainsValueConstraint is used to test whether a dictionary - contains an expected object as a value. - - - - - CollectionContainsConstraint is used to test whether a collection - contains an expected object as a member. - - - - - Construct a CollectionContainsConstraint - - - - - - Test whether the expected item is contained in the collection - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Gets the expected object - - - - - Construct a DictionaryContainsValueConstraint - - - - - - Test whether the expected value is contained in the dictionary - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - The EqualConstraintResult class is tailored for formatting - and displaying the result of an EqualConstraint. - - - - - Contain the result of matching a against an actual value. - - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - The status of the new ConstraintResult. - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - If true, applies a status of Success to the result, otherwise Failure. - - - - Write the failure message to the MessageWriter provided - as an argument. The default implementation simply passes - the result and the actual value to the writer, which - then displays the constraint description and the value. - - Constraints that need to provide additional details, - such as where the error occured can override this. - - The MessageWriter on which to display the message - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - The actual value that was passed to the method. - - - - - Gets and sets the ResultStatus for this result. - - - - - True if actual value meets the Constraint criteria otherwise false. - - - - - Display friendly name of the constraint. - - - - - Description of the constraint may be affected by the state the constraint had - when was performed against the actual value. - - - - - Construct an EqualConstraintResult - - - - - Write a failure message. Overridden to provide custom - failure messages for EqualConstraint. - - The MessageWriter to write to - - - - Display the failure information for two collections that did not match. - - The MessageWriter on which to display - The expected collection. - The actual collection - The depth of this failure in a set of nested collections - - - - Displays a single line showing the types and sizes of the expected - and actual collections or arrays. If both are identical, the value is - only shown once. - - The MessageWriter on which to display - The expected collection or array - The actual collection or array - The indentation level for the message line - - - - Displays a single line showing the point in the expected and actual - arrays at which the comparison failed. If the arrays have different - structures or dimensions, both _values are shown. - - The MessageWriter on which to display - The expected array - The actual array - Index of the failure point in the underlying collections - The indentation level for the message line - - - - Display the failure information for two IEnumerables that did not match. - - The MessageWriter on which to display - The expected enumeration. - The actual enumeration - The depth of this failure in a set of nested collections - - - - FileExistsConstraint is used to determine if a file exists - - - - - FileOrDirectoryExistsConstraint is used to determine if a file or directory exists - - - - - Initializes a new instance of the class that - will check files and directories. - - - - - Initializes a new instance of the class that - will only check files if ignoreDirectories is true. - - if set to true [ignore directories]. - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - If true, the constraint will only check if files exist, not directories - - - - - If true, the constraint will only check if directories exist, not files - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Initializes a new instance of the class. - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Represents a constraint that succeeds if all the - members of a collection match a base constraint. - - - - - Abstract base for operators that indicate how to - apply a constraint to items in a collection. - - - - - PrefixOperator takes a single constraint and modifies - it's action in some way. - - - - - The ConstraintOperator class is used internally by a - ConstraintBuilder to represent an operator that - modifies or combines constraints. - - Constraint operators use left and right precedence - _values to determine whether the top operator on the - stack should be reduced before pushing a new operator. - - - - - The precedence value used when the operator - is about to be pushed to the stack. - - - - - The precedence value used when the operator - is on the top of the stack. - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - The syntax element preceding this operator - - - - - The syntax element following this operator - - - - - The precedence value used when the operator - is about to be pushed to the stack. - - - - - The precedence value used when the operator - is on the top of the stack. - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Returns the constraint created by applying this - prefix to another constraint. - - - - - - - Constructs a CollectionOperator - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - they all succeed. - - - - - Represents a constraint that succeeds if none of the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - none of them succeed. - - - - - Represents a constraint that succeeds if any of the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - any of them succeed. - - - - - SubPathConstraint tests that the actual path is under the expected path - - - - - PathConstraint serves as the abstract base of constraints - that operate on paths and provides several helper methods. - - - - - StringConstraint is the abstract base for constraints - that operate on strings. It supports the IgnoreCase - modifier for string operations. - - - - - The expected value - - - - - Indicates whether tests should be case-insensitive - - - - - Description of this constraint - - - - - Constructs a StringConstraint without an expected value - - - - - Constructs a StringConstraint given an expected value - - The expected value - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Test whether the constraint is satisfied by a given string - - The string to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Modify the constraint to ignore case in matching. - - - - - Construct a PathConstraint for a give expected path - - The expected path - - - - Returns the string representation of this constraint - - - - - Canonicalize the provided path - - - The path in standardized form - - - - Test whether one path in canonical form is a subpath of another path - - The first path - supposed to be the parent path - The second path - supposed to be the child path - - - - - Modifies the current instance to be case-sensitive - and returns it. - - - - - Initializes a new instance of the class. - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - ThrowsExceptionConstraint tests that an exception has - been thrown, without any further tests. - - - - - Executes the code and returns success if an exception is thrown. - - A delegate representing the code to be tested - True if an exception is thrown, otherwise false - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - The Assert class contains a collection of static methods that - implement the most common assertions used in NUnit. - - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestDelegate - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestDelegate - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - A TestDelegate - - - - Verifies that a delegate does not throw an exception - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate does not throw an exception. - - A TestDelegate - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - - - - Helper for Assert.AreEqual(double expected, double actual, ...) - allowing code generation to work consistently. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - - This method is provided for use by VB developers needing to test - the value of properties with private setters. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that the first int is greater than the second - int. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first int is greater than the second - int. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - - - - We don't actually want any instances of this object, but some people - like to inherit from it to add other static methods. Hence, the - protected constructor disallows any instances of this object. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - The message to initialize the with. - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - - - - Throws an with the message and arguments - that are passed in. This is used by the other Assert functions. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This is used by the other Assert functions. - - The message to initialize the with. - - - - Throws an . - This is used by the other Assert functions. - - - - - Throws an with the message and arguments - that are passed in. This causes the test to be reported as ignored. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This causes the test to be reported as ignored. - - The message to initialize the with. - - - - Throws an . - This causes the test to be reported as ignored. - - - - - Throws an with the message and arguments - that are passed in. This causes the test to be reported as inconclusive. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This causes the test to be reported as inconclusive. - - The message to initialize the with. - - - - Throws an . - This causes the test to be reported as Inconclusive. - - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - - - - Any ITest that implements this interface is at a level that the implementing - class should be disposed at the end of the test run - - - - - The IMethodInfo class is used to encapsulate information - about a method in a platform-independent manner. - - - - - The IReflectionInfo interface is implemented by NUnit wrapper objects that perform reflection. - - - - - Returns an array of custom attributes of the specified type applied to this object - - - - - Returns a value indicating whether an attribute of the specified type is defined on this object. - - - - - Gets the parameters of the method. - - - - - - Returns the Type arguments of a generic method or the Type parameters of a generic method definition. - - - - - Replaces the type parameters of the method with the array of types provided and returns a new IMethodInfo. - - The type arguments to be used - A new IMethodInfo with the type arguments replaced - - - - Invokes the method, converting any TargetInvocationException to an NUnitException. - - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - Gets the Type from which this method was reflected. - - - - - Gets the MethodInfo for this method. - - - - - Gets the name of the method. - - - - - Gets a value indicating whether the method is abstract. - - - - - Gets a value indicating whether the method is public. - - - - - Gets a value indicating whether the method contains unassigned generic type parameters. - - - - - Gets a value indicating whether the method is a generic method. - - - - - Gets a value indicating whether the MethodInfo represents the definition of a generic method. - - - - - Gets the return Type of the method. - - - - - The IParameterInfo interface is an abstraction of a .NET parameter. - - - - - Gets a value indicating whether the parameter is optional - - - - - Gets an IMethodInfo representing the method for which this is a parameter - - - - - Gets the underlying .NET ParameterInfo - - - - - Gets the Type of the parameter - - - - - The ITypeInfo interface is an abstraction of a .NET Type - - - - - Returns true if the Type wrapped is equal to the argument - - - - - Get the display name for this typeInfo. - - - - - Get the display name for an oject of this type, constructed with specific arguments - - - - - Returns a Type representing a generic type definition from which this Type can be constructed. - - - - - Returns a new ITypeInfo representing an instance of this generic Type using the supplied Type arguments - - - - - Returns a value indicating whether this type has a method with a specified public attribute - - - - - Returns an array of IMethodInfos for methods of this Type - that match the specified flags. - - - - - Returns a value indicating whether this Type has a public constructor taking the specified argument Types. - - - - - Construct an object of this Type, using the specified arguments. - - - - - Gets the underlying Type on which this ITypeInfo is based - - - - - Gets the base type of this type as an ITypeInfo - - - - - Gets the Name of the Type - - - - - Gets the FullName of the Type - - - - - Gets the assembly in which the type is declared - - - - - Gets the Namespace of the Type - - - - - Gets a value indicating whether the type is abstract. - - - - - Gets a value indicating whether the Type is a generic Type - - - - - Gets a value indicating whether the Type has generic parameters that have not been replaced by specific Types. - - - - - Gets a value indicating whether the Type is a generic Type definition - - - - - Gets a value indicating whether the type is sealed. - - - - - Gets a value indicating whether this type is a static class. - - - - - A base class for multi-part filters - - - - - Interface to be implemented by filters applied to tests. - The filter applies when running the test, after it has been - loaded, since this is the only time an ITest exists. - - - - - Interface to be implemented by filters applied to tests. - The filter applies when running the test, after it has been - loaded, since this is the only time an ITest exists. - - - - - An object implementing IXmlNodeBuilder is able to build - an XML representation of itself and any children. - - - - - Returns a TNode representing the current object. - - If true, children are included where applicable - A TNode representing the result - - - - Returns a TNode representing the current object after - adding it as a child of the supplied parent node. - - The parent node. - If true, children are included, where applicable - - - - - Determine if a particular test passes the filter criteria. Pass - may examine the parents and/or descendants of a test, depending - on the semantics of the particular filter - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - Unique Empty filter. - - - - - Determine if a particular test passes the filter criteria. The default - implementation checks the test itself, its parents and any descendants. - - Derived classes may override this method or any of the Match methods - to change the behavior of the filter. - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - Determine whether the test itself matches the filter criteria, without - examining either parents or descendants. This is overridden by each - different type of filter to perform the necessary tests. - - The test to which the filter is applied - True if the filter matches the any parent of the test - - - - Determine whether any ancestor of the test matches the filter criteria - - The test to which the filter is applied - True if the filter matches the an ancestor of the test - - - - Determine whether any descendant of the test matches the filter criteria. - - The test to be matched - True if at least one descendant matches the filter criteria - - - - Create a TestFilter instance from an xml representation. - - - - - Create a TestFilter from it's TNode representation - - - - - Adds an XML node - - True if recursive - The added XML node - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Indicates whether this is the EmptyFilter - - - - - Indicates whether this is a top-level filter, - not contained in any other filter. - - - - - Nested class provides an empty filter - one that always - returns true when called. It never matches explicitly. - - - - - Constructs an empty CompositeFilter - - - - - Constructs a CompositeFilter from an array of filters - - - - - - Adds a filter to the list of filters - - The filter to be added - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Return a list of the composing filters. - - - - - Gets the element name - - Element name - - - - PropertyFilter is able to select or exclude tests - based on their properties. - - - - - - ValueMatchFilter selects tests based on some value, which - is expected to be contained in the test. - - - - - Construct a ValueMatchFilter for a single value. - - The value to be included. - - - - Match the input provided by the derived class - - The value to be matchedT - True for a match, false otherwise. - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Returns the value matched by the filter - used for testing - - - - - Indicates whether the value is a regular expression - - - - - Gets the element name - - Element name - - - - Construct a PropertyFilter using a property name and expected value - - A property name - The expected value of the property - - - - Check whether the filter matches a test - - The test to be matched - - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Gets the element name - - Element name - - - - TestName filter selects tests based on their Name - - - - - Construct a TestNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - ClassName filter selects tests based on the class FullName - - - - - Construct a FullNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - FullName filter selects tests based on their FullName - - - - - Construct a MethodNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - The MethodWrapper class wraps a MethodInfo so that it may - be used in a platform-independent manner. - - - - - Construct a MethodWrapper for a Type and a MethodInfo. - - - - - Construct a MethodInfo for a given Type and method name. - - - - - Gets the parameters of the method. - - - - - - Returns the Type arguments of a generic method or the Type parameters of a generic method definition. - - - - - Replaces the type parameters of the method with the array of types provided and returns a new IMethodInfo. - - The type arguments to be used - A new IMethodInfo with the type arguments replaced - - - - Returns an array of custom attributes of the specified type applied to this method - - - - - Gets a value indicating whether one or more attributes of the spcified type are defined on the method. - - - - - Invokes the method, converting any TargetInvocationException to an NUnitException. - - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - Override ToString() so that error messages in NUnit's own tests make sense - - - - - Gets the Type from which this method was reflected. - - - - - Gets the MethodInfo for this method. - - - - - Gets the name of the method. - - - - - Gets a value indicating whether the method is abstract. - - - - - Gets a value indicating whether the method is public. - - - - - Gets a value indicating whether the method contains unassigned generic type parameters. - - - - - Gets a value indicating whether the method is a generic method. - - - - - Gets a value indicating whether the MethodInfo represents the definition of a generic method. - - - - - Gets the return Type of the method. - - - - - The ParameterWrapper class wraps a ParameterInfo so that it may - be used in a platform-independent manner. - - - - - Construct a ParameterWrapper for a given method and parameter - - - - - - - Returns an array of custom attributes of the specified type applied to this method - - - - - Gets a value indicating whether one or more attributes of the specified type are defined on the parameter. - - - - - Gets a value indicating whether the parameter is optional - - - - - Gets an IMethodInfo representing the method for which this is a parameter. - - - - - Gets the underlying ParameterInfo - - - - - Gets the Type of the parameter - - - - - TestNameGenerator is able to create test names according to - a coded pattern. - - - - - Construct a TestNameGenerator - - The pattern used by this generator. - - - - Get the display name for a TestMethod and it's arguments - - A TestMethod - The display name - - - - Get the display name for a TestMethod and it's arguments - - A TestMethod - Arguments to be used - The display name - - - - Get the display name for a MethodInfo - - A MethodInfo - The display name - - - - Get the display name for a method with args - - A MethodInfo - Argument list for the method - The display name - - - - The TypeWrapper class wraps a Type so it may be used in - a platform-independent manner. - - - - - Construct a TypeWrapper for a specified Type. - - - - - Returns true if the Type wrapped is T - - - - - Get the display name for this type - - - - - Get the display name for an object of this type, constructed with the specified args. - - - - - Returns a new ITypeInfo representing an instance of this generic Type using the supplied Type arguments - - - - - Returns a Type representing a generic type definition from which this Type can be constructed. - - - - - Returns an array of custom attributes of the specified type applied to this type - - - - - Returns a value indicating whether the type has an attribute of the specified type. - - - - - - - - Returns a flag indicating whether this type has a method with an attribute of the specified type. - - - - - - - Returns an array of IMethodInfos for methods of this Type - that match the specified flags. - - - - - Returns a value indicating whether this Type has a public constructor taking the specified argument Types. - - - - - Construct an object of this Type, using the specified arguments. - - - - - Override ToString() so that error messages in NUnit's own tests make sense - - - - - Gets the underlying Type on which this TypeWrapper is based. - - - - - Gets the base type of this type as an ITypeInfo - - - - - Gets the Name of the Type - - - - - Gets the FullName of the Type - - - - - Gets the assembly in which the type is declared - - - - - Gets the namespace of the Type - - - - - Gets a value indicating whether the type is abstract. - - - - - Gets a value indicating whether the Type is a generic Type - - - - - Gets a value indicating whether the Type has generic parameters that have not been replaced by specific Types. - - - - - Gets a value indicating whether the Type is a generic Type definition - - - - - Gets a value indicating whether the type is sealed. - - - - - Gets a value indicating whether this type represents a static class. - - - - - The TestFixtureData class represents a set of arguments - and other parameter info to be used for a parameterized - fixture. It is derived from TestFixtureParameters and adds a - fluent syntax for use in initializing the fixture. - - - - - The TestCaseParameters class encapsulates method arguments and - other selected parameters needed for constructing - a parameterized test case. - - - - - TestParameters is the abstract base class for all classes - that know how to provide data for constructing a test. - - - - - The ITestData interface is implemented by a class that - represents a single instance of a parameterized test. - - - - - Gets the name to be used for the test - - - - - Gets the RunState for this test case. - - - - - Gets the argument list to be provided to the test - - - - - Gets the property dictionary for the test case - - - - - Default Constructor creates an empty parameter set - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a ParameterSet from an object implementing ITestData - - - - - - Applies ParameterSet _values to the test itself. - - A test. - - - - The RunState for this set of parameters. - - - - - The arguments to be used in running the test, - which must match the method signature. - - - - - A name to be used for this test case in lieu - of the standard generated name containing - the argument list. - - - - - Gets the property dictionary for this test - - - - - The original arguments provided by the user, - used for display purposes. - - - - - The ITestCaseData interface is implemented by a class - that is able to return the data required to create an - instance of a parameterized test fixture. - - - - - Get the TypeArgs if separately set - - - - - Default Constructor creates an empty parameter set - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a ParameterSet from an object implementing ITestCaseData - - - - - - Type arguments used to create a generic fixture instance - - - - - Initializes a new instance of the class. - - The arguments. - - - - Initializes a new instance of the class. - - The argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - The third argument. - - - - Marks the test fixture as explicit. - - - - - Marks the test fixture as explicit, specifying the reason. - - - - - Ignores this TestFixture, specifying the reason. - - The reason. - - - - - Asserts on Directories - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Verifies that two directories are equal. Two directories are considered - equal if both are null, or if both point to the same directory. - If they are not equal an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - The message to display if the directories are not equal - Arguments to be used in formatting the message - - - - Verifies that two directories are equal. Two directories are considered - equal if both are null, or if both point to the same directory. - If they are not equal an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - - - - Asserts that two directories are not equal. If they are equal - an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that two directories are not equal. If they are equal - an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - - - - Asserts that the directory exists. If it does not exist - an is thrown. - - A directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory exists. If it does not exist - an is thrown. - - A directory containing the actual value - - - - Asserts that the directory exists. If it does not exist - an is thrown. - - The path to a directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory exists. If it does not exist - an is thrown. - - The path to a directory containing the actual value - - - - Asserts that the directory does not exist. If it does exist - an is thrown. - - A directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory does not exist. If it does exist - an is thrown. - - A directory containing the actual value - - - - Asserts that the directory does not exist. If it does exist - an is thrown. - - The path to a directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory does not exist. If it does exist - an is thrown. - - The path to a directory containing the actual value - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a constraint that succeeds if the value - is a file or directory and it exists. - - - - - Abstract base for Exceptions that terminate a test and provide a ResultState. - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - CombiningStrategy is the abstract base for classes that - know how to combine values provided for individual test - parameters to create a set of test cases. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - The ISimpleTestBuilder interface is exposed by a class that knows how to - build a single TestMethod from a suitable MethodInfo Types. In general, - it is exposed by an attribute, but may be implemented in a helper class - used by the attribute in some cases. - - - - - Build a TestMethod from the provided MethodInfo. - - The method to be used as a test - The TestSuite to which the method will be added - A TestMethod object - - - - The IDataPointProvider interface is used by extensions - that provide data for a single test parameter. - - - - - Determine whether any data is available for a parameter. - - An IParameterInfo representing one - argument to a parameterized test - True if any data is available, otherwise false. - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - An IParameterInfo representing one - argument to a parameterized test - An IEnumerable providing the required data - - - - The IParameterDataSource interface is implemented by types - that can provide data for a test method parameter. - - - - - Gets an enumeration of data items for use as arguments - for a test method parameter. - - The parameter for which data is needed - An enumeration containing individual data items - - - - A PropertyBag represents a collection of name/value pairs - that allows duplicate entries with the same key. Methods - are provided for adding a new pair as well as for setting - a key to a single value. All keys are strings but _values - may be of any type. Null _values are not permitted, since - a null entry represents the absence of the key. - - The entries in a PropertyBag are of two kinds: those that - take a single value and those that take multiple _values. - However, the PropertyBag has no knowledge of which entries - fall into each category and the distinction is entirely - up to the code using the PropertyBag. - - When working with multi-valued properties, client code - should use the Add method to add name/value pairs and - indexing to retrieve a list of all _values for a given - key. For example: - - bag.Add("Tag", "one"); - bag.Add("Tag", "two"); - Assert.That(bag["Tag"], - Is.EqualTo(new string[] { "one", "two" })); - - When working with single-valued propeties, client code - should use the Set method to set the value and Get to - retrieve the value. The GetSetting methods may also be - used to retrieve the value in a type-safe manner while - also providing default. For example: - - bag.Set("Priority", "low"); - bag.Set("Priority", "high"); // replaces value - Assert.That(bag.Get("Priority"), - Is.EqualTo("high")); - Assert.That(bag.GetSetting("Priority", "low"), - Is.EqualTo("high")); - - - - - Adds a key/value pair to the property bag - - The key - The value - - - - Sets the value for a key, removing any other - _values that are already in the property set. - - - - - - - Gets a single value for a key, using the first - one if multiple _values are present and returning - null if the value is not found. - - - - - Gets a flag indicating whether the specified key has - any entries in the property set. - - The key to be checked - True if their are _values present, otherwise false - - - - Gets or sets the list of _values for a particular key - - The key for which the _values are to be retrieved or set - - - - Gets a collection containing all the keys in the property set - - - - - Common interface supported by all representations - of a test. Only includes informational fields. - The Run method is specifically excluded to allow - for data-only representations of a test. - - - - - Gets the id of the test - - - - - Gets the name of the test - - - - - Gets the fully qualified name of the test - - - - - Gets the name of the class containing this test. Returns - null if the test is not associated with a class. - - - - - Gets the name of the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the Type of the test fixture, if applicable, or - null if no fixture type is associated with this test. - - - - - Gets an IMethod for the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the RunState of the test, indicating whether it can be run. - - - - - Count of the test cases ( 1 if this is a test case ) - - - - - Gets the properties of the test - - - - - Gets the parent test, if any. - - The parent test or null if none exists. - - - - Returns true if this is a test suite - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets a fixture object for running this test. - - - - - The ITestAssemblyBuilder interface is implemented by a class - that is able to build a suite of tests given an assembly or - an assembly filename. - - - - - Build a suite of tests from a provided assembly - - The assembly from which tests are to be built - A dictionary of options to use in building the suite - A TestSuite containing the tests found in the assembly - - - - Build a suite of tests given the filename of an assembly - - The filename of the assembly from which tests are to be built - A dictionary of options to use in building the suite - A TestSuite containing the tests found in the assembly - - - - The ITestCaseData interface is implemented by a class - that is able to return complete testcases for use by - a parameterized test method. - - - - - Gets the expected result of the test case - - - - - Returns true if an expected result has been set - - - - - The ITestListener interface is used internally to receive - notifications of significant events while a test is being - run. The events are propagated to clients by means of an - AsyncCallback. NUnit extensions may also monitor these events. - - - - - Called when a test has just started - - The test that is starting - - - - Called when a test has finished - - The result of the test - - - - The ITestResult interface represents the result of a test. - - - - - Gets the ResultState of the test result, which - indicates the success or failure of the test. - - - - - Gets the name of the test result - - - - - Gets the full name of the test result - - - - - Gets the elapsed time for running the test in seconds - - - - - Gets or sets the time the test started running. - - - - - Gets or sets the time the test finished running. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. Not available in - the Compact Framework 1.0. - - - - - Gets the number of asserts executed - when running the test and all its children. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Indicates whether this result has any child results. - Accessing HasChildren should not force creation of the - Children collection in classes implementing this interface. - - - - - Gets the the collection of child results. - - - - - Gets the Test to which this result applies. - - - - - Gets any text output written to this result. - - - - - SetUpTearDownItem holds the setup and teardown methods - for a single level of the inheritance hierarchy. - - - - - Construct a SetUpTearDownNode - - A list of setup methods for this level - A list teardown methods for this level - - - - Run SetUp on this level. - - The execution context to use for running. - - - - Run TearDown for this level. - - - - - - Returns true if this level has any methods at all. - This flag is used to discard levels that do nothing. - - - - - TestActionCommand runs the BeforeTest actions for a test, - then runs the test and finally runs the AfterTestActions. - - - - - Initializes a new instance of the class. - - The inner command. - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - TestActionItem represents a single execution of an - ITestAction. It is used to track whether the BeforeTest - method has been called and suppress calling the - AfterTest method if it has not. - - - - - Construct a TestActionItem - - The ITestAction to be included - - - - Run the BeforeTest method of the action and remember that it has been run. - - The test to which the action applies - - - - Run the AfterTest action, but only if the BeforeTest - action was actually run. - - The test to which the action applies - - - - A utility class to create TestCommands - - - - - Gets the command to be executed before any of - the child tests are run. - - A TestCommand - - - - Gets the command to be executed after all of the - child tests are run. - - A TestCommand - - - - Creates a test command for use in running this test. - - - - - - Creates a command for skipping a test. The result returned will - depend on the test RunState. - - - - - Builds the set up tear down list. - - Type of the fixture. - Type of the set up attribute. - Type of the tear down attribute. - A list of SetUpTearDownItems - - - - An IWorkItemDispatcher handles execution of work items. - - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and used when stopping the run. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - The ResultState class represents the outcome of running a test. - It contains two pieces of information. The Status of the test - is an enum indicating whether the test passed, failed, was - skipped or was inconclusive. The Label provides a more - detailed breakdown for use by client runners. - - - - - Initializes a new instance of the class. - - The TestStatus. - - - - Initializes a new instance of the class. - - The TestStatus. - The label. - - - - Initializes a new instance of the class. - - The TestStatus. - The stage at which the result was produced - - - - Initializes a new instance of the class. - - The TestStatus. - The label. - The stage at which the result was produced - - - - The result is inconclusive - - - - - The test has been skipped. - - - - - The test has been ignored. - - - - - The test was skipped because it is explicit - - - - - The test succeeded - - - - - The test failed - - - - - The test encountered an unexpected exception - - - - - The test was cancelled by the user - - - - - The test was not runnable. - - - - - A suite failed because one or more child tests failed or had errors - - - - - A suite failed in its OneTimeSetUp - - - - - A suite had an unexpected exception in its OneTimeSetUp - - - - - A suite had an unexpected exception in its OneTimeDown - - - - - Get a new ResultState, which is the same as the current - one but with the FailureSite set to the specified value. - - The FailureSite to use - A new ResultState - - - - Determines whether the specified , is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Returns a hash code for this instance. - - - A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Gets the TestStatus for the test. - - The status. - - - - Gets the label under which this test result is - categorized, if any. - - - - - Gets the stage of test execution in which - the failure or other result took place. - - - - - The FailureSite enum indicates the stage of a test - in which an error or failure occurred. - - - - - Failure in the test itself - - - - - Failure in the SetUp method - - - - - Failure in the TearDown method - - - - - Failure of a parent test - - - - - Failure of a child test - - - - - The RunState enum indicates whether a test can be executed. - - - - - The test is not runnable. - - - - - The test is runnable. - - - - - The test can only be run explicitly - - - - - The test has been skipped. This value may - appear on a Test when certain attributes - are used to skip the test. - - - - - The test has been ignored. May appear on - a Test, when the IgnoreAttribute is used. - - - - - The TestStatus enum indicates the result of running a test - - - - - The test was inconclusive - - - - - The test has skipped - - - - - The test succeeded - - - - - The test failed - - - - - TNode represents a single node in the XML representation - of a Test or TestResult. It replaces System.Xml.XmlNode and - System.Xml.Linq.XElement, providing a minimal set of methods - for operating on the XML in a platform-independent manner. - - - - - Constructs a new instance of TNode - - The name of the node - - - - Constructs a new instance of TNode with a value - - The name of the node - The text content of the node - - - - Constructs a new instance of TNode with a value - - The name of the node - The text content of the node - Flag indicating whether to use CDATA when writing the text - - - - Create a TNode from it's XML text representation - - The XML text to be parsed - A TNode - - - - Adds a new element as a child of the current node and returns it. - - The element name. - The newly created child element - - - - Adds a new element with a value as a child of the current node and returns it. - - The element name - The text content of the new element - The newly created child element - - - - Adds a new element with a value as a child of the current node and returns it. - The value will be output using a CDATA section. - - The element name - The text content of the new element - The newly created child element - - - - Adds an attribute with a specified name and value to the XmlNode. - - The name of the attribute. - The value of the attribute. - - - - Finds a single descendant of this node matching an xpath - specification. The format of the specification is - limited to what is needed by NUnit and its tests. - - - - - - - Finds all descendants of this node matching an xpath - specification. The format of the specification is - limited to what is needed by NUnit and its tests. - - - - - Writes the XML representation of the node to an XmlWriter - - - - - - Gets the name of the node - - - - - Gets the value of the node - - - - - Gets a flag indicating whether the value should be output using CDATA. - - - - - Gets the dictionary of attributes - - - - - Gets a list of child nodes - - - - - Gets the first ChildNode - - - - - Gets the XML representation of this node. - - - - - Class used to represent a list of XmlResults - - - - - Class used to represent the attributes of a node - - - - - Gets or sets the value associated with the specified key. - Overridden to return null if attribute is not found. - - The key. - Value of the attribute or null - - - - Built-in SuiteBuilder for all types of test classes. - - - - - The ISuiteBuilder interface is exposed by a class that knows how to - build a suite from one or more Types. - - - - - Examine the type and determine if it is suitable for - this builder to use in building a TestSuite. - - Note that returning false will cause the type to be ignored - in loading the tests. If it is desired to load the suite - but label it as non-runnable, ignored, etc., then this - method must return true. - - The type of the fixture to be used - True if the type can be used to build a TestSuite - - - - Build a TestSuite from type provided. - - The type of the fixture to be used - A TestSuite - - - - Checks to see if the provided Type is a fixture. - To be considered a fixture, it must be a non-abstract - class with one or more attributes implementing the - IFixtureBuilder interface or one or more methods - marked as tests. - - The fixture type to check - True if the fixture can be built, false if not - - - - Build a TestSuite from TypeInfo provided. - - The fixture type to build - A TestSuite built from that type - - - - We look for attributes implementing IFixtureBuilder at one level - of inheritance at a time. Attributes on base classes are not used - unless there are no fixture builder attributes at all on the derived - class. This is by design. - - The type being examined for attributes - A list of the attributes found. - - - - NUnitTestCaseBuilder is a utility class used by attributes - that build test cases. - - - - - Constructs an - - - - - Builds a single NUnitTestMethod, either as a child of the fixture - or as one of a set of test cases under a ParameterizedTestMethodSuite. - - The MethodInfo from which to construct the TestMethod - The suite or fixture to which the new test will be added - The ParameterSet to be used, or null - - - - - Helper method that checks the signature of a TestMethod and - any supplied parameters to determine if the test is valid. - - Currently, NUnitTestMethods are required to be public, - non-abstract methods, either static or instance, - returning void. They may take arguments but the _values must - be provided or the TestMethod is not considered runnable. - - Methods not meeting these criteria will be marked as - non-runnable and the method will return false in that case. - - The TestMethod to be checked. If it - is found to be non-runnable, it will be modified. - Parameters to be used for this test, or null - True if the method signature is valid, false if not - - The return value is no longer used internally, but is retained - for testing purposes. - - - - - SimpleWorkItemDispatcher handles execution of WorkItems by - directly executing them. It is provided so that a dispatcher - is always available in the context, thereby simplifying the - code needed to run child tests. - - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and a thread is created on which to - run it. Subsequent calls come from the top level - item or its descendants on the proper thread. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - The TextCapture class intercepts console output and writes it - to the current execution context, if one is present on the thread. - If no execution context is found, the output is written to a - default destination, normally the original destination of the - intercepted output. - - - - - Construct a TextCapture object - - The default destination for non-intercepted output - - - - Writes a single character - - The char to write - - - - Writes a string - - The string to write - - - - Writes a string followed by a line terminator - - The string to write - - - - Gets the Encoding in use by this TextWriter - - - - - The dispatcher needs to do different things at different, - non-overlapped times. For example, non-parallel tests may - not be run at the same time as parallel tests. We model - this using the metaphor of a working shift. The WorkShift - class associates one or more WorkItemQueues with one or - more TestWorkers. - - Work in the queues is processed until all queues are empty - and all workers are idle. Both tests are needed because a - worker that is busy may end up adding more work to one of - the queues. At that point, the shift is over and another - shift may begin. This cycle continues until all the tests - have been run. - - - - - Construct a WorkShift - - - - - Add a WorkItemQueue to the shift, starting it if the - shift is currently active. - - - - - Assign a worker to the shift. - - - - - - Start or restart processing for the shift - - - - - End the shift, pausing all queues and raising - the EndOfShift event. - - - - - Shut down the shift. - - - - - Cancel the shift without completing all work - - - - - Event that fires when the shift has ended - - - - - Gets a flag indicating whether the shift is currently active - - - - - Gets a list of the queues associated with this shift. - - Used for testing - - - - Gets the list of workers associated with this shift. - - - - - Gets a bool indicating whether this shift has any work to do - - - - - IdFilter selects tests based on their id - - - - - Construct an IdFilter for a single value - - The id the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - IImplyFixture is an empty marker interface used by attributes like - TestAttribute that cause the class where they are used to be treated - as a TestFixture even without a TestFixtureAttribute. - - Marker interfaces are not usually considered a good practice, but - we use it here to avoid cluttering the attribute hierarchy with - classes that don't contain any extra implementation. - - - - - Class that can build a tree of automatic namespace - suites from a group of fixtures. - - - - - NamespaceDictionary of all test suites we have created to represent - namespaces. Used to locate namespace parent suites for fixtures. - - - - - The root of the test suite being created by this builder. - - - - - Initializes a new instance of the class. - - The root suite. - - - - Adds the specified fixtures to the tree. - - The fixtures to be added. - - - - Adds the specified fixture to the tree. - - The fixture to be added. - - - - Gets the root entry in the tree created by the NamespaceTreeBuilder. - - The root suite. - - - - ContextSettingsCommand applies specified changes to the - TestExecutionContext prior to running a test. No special - action is needed after the test runs, since the prior - context will be restored automatically. - - - - - TODO: Documentation needed for class - - - - - Initializes a new instance of the class. - - The inner command. - The max time allowed in milliseconds - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext - - The context in which the test should run. - A TestResult - - - - OneTimeSetUpCommand runs any one-time setup methods for a suite, - constructing the user test object if necessary. - - - - - Constructs a OneTimeSetUpCommand for a suite - - The suite to which the command applies - A SetUpTearDownList for use by the command - A List of TestActionItems to be run after Setup - - - - Overridden to run the one-time setup for a suite. - - The TestExecutionContext to be used. - A TestResult - - - - OneTimeTearDownCommand performs any teardown actions - specified for a suite and calls Dispose on the user - test object, if any. - - - - - Construct a OneTimeTearDownCommand - - The test suite to which the command applies - A SetUpTearDownList for use by the command - A List of TestActionItems to be run before teardown. - - - - Overridden to run the teardown methods specified on the test. - - The TestExecutionContext to be used. - A TestResult - - - - SetUpTearDownCommand runs any SetUp methods for a suite, - runs the test and then runs any TearDown methods. - - - - - Initializes a new instance of the class. - - The inner command. - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - TODO: Documentation needed for class - - - - - Initializes a new instance of the class. - - The test being skipped. - - - - Overridden to simply set the CurrentResult to the - appropriate Skipped state. - - The execution context for the test - A TestResult - - - - TestMethodCommand is the lowest level concrete command - used to run actual test cases. - - - - - Initializes a new instance of the class. - - The test. - - - - Runs the test, saving a TestResult in the execution context, as - well as returning it. If the test has an expected result, it - is asserts on that value. Since failed tests and errors throw - an exception, this command must be wrapped in an outer command, - will handle that exception and records the failure. This role - is usually played by the SetUpTearDown command. - - The execution context - - - - TheoryResultCommand adjusts the result of a Theory so that - it fails if all the results were inconclusive. - - - - - Constructs a TheoryResultCommand - - The command to be wrapped by this one - - - - Overridden to call the inner command and adjust the result - in case all chlid results were inconclusive. - - - - - - - A CompositeWorkItem represents a test suite and - encapsulates the execution of the suite as well - as all its child tests. - - - - - A WorkItem may be an individual test case, a fixture or - a higher level grouping of tests. All WorkItems inherit - from the abstract WorkItem class, which uses the template - pattern to allow derived classes to perform work in - whatever way is needed. - - A WorkItem is created with a particular TestExecutionContext - and is responsible for re-establishing that context in the - current thread before it begins or resumes execution. - - - - - Creates a work item. - - The test for which this WorkItem is being created. - The filter to be used in selecting any child Tests. - - - - - Construct a WorkItem for a particular test. - - The test that the WorkItem will run - - - - Initialize the TestExecutionContext. This must be done - before executing the WorkItem. - - - Originally, the context was provided in the constructor - but delaying initialization of the context until the item - is about to be dispatched allows changes in the parent - context during OneTimeSetUp to be reflected in the child. - - The TestExecutionContext to use - - - - Execute the current work item, including any - child work items. - - - - - Method that performs actually performs the work. It should - set the State to WorkItemState.Complete when done. - - - - - Method called by the derived class when all work is complete - - - - - Event triggered when the item is complete - - - - - Gets the current state of the WorkItem - - - - - The test being executed by the work item - - - - - The execution context - - - - - The test actions to be performed before and after this test - - - - - Indicates whether this WorkItem may be run in parallel - - - - - The test result - - - - - Construct a CompositeWorkItem for executing a test suite - using a filter to select child tests. - - The TestSuite to be executed - A filter used to select child tests - - - - Method that actually performs the work. Overridden - in CompositeWorkItem to do setup, run all child - items and then do teardown. - - - - - A simplified implementation of .NET 4 CountdownEvent - for use in earlier versions of .NET. Only the methods - used by NUnit are implemented. - - - - - Construct a CountdownEvent - - The initial count - - - - Decrement the count by one - - - - - Block the thread until the count reaches zero - - - - - Gets the initial count established for the CountdownEvent - - - - - Gets the current count remaining for the CountdownEvent - - - - - The EventPumpState enum represents the state of an - EventPump. - - - - - The pump is stopped - - - - - The pump is pumping events with no stop requested - - - - - The pump is pumping events but a stop has been requested - - - - - EventPump pulls events out of an EventQueue and sends - them to a listener. It is used to send events back to - the client without using the CallContext of the test - runner thread. - - - - - The handle on which a thread enqueuing an event with == true - waits, until the EventPump has sent the event to its listeners. - - - - - The downstream listener to which we send events - - - - - The queue that holds our events - - - - - Thread to do the pumping - - - - - The current state of the eventpump - - - - - Constructor - - The EventListener to receive events - The event queue to pull events from - - - - Dispose stops the pump - Disposes the used WaitHandle, too. - - - - - Start the pump - - - - - Tell the pump to stop after emptying the queue. - - - - - Our thread proc for removing items from the event - queue and sending them on. Note that this would - need to do more locking if any other thread were - removing events from the queue. - - - - - Gets or sets the current state of the pump - - - On volatile and , see - "http://www.albahari.com/threading/part4.aspx". - - - - - Gets or sets the name of this EventPump - (used only internally and for testing). - - - - - NUnit.Core.Event is the abstract base for all stored events. - An Event is the stored representation of a call to the - ITestListener interface and is used to record such calls - or to queue them for forwarding on another thread or at - a later time. - - - - - The Send method is implemented by derived classes to send the event to the specified listener. - - The listener. - - - - Gets a value indicating whether this event is delivered synchronously by the NUnit . - - If true, and if has been used to - set a WaitHandle, blocks its calling thread until the - thread has delivered the event and sets the WaitHandle. - - - - - - TestStartedEvent holds information needed to call the TestStarted method. - - - - - Initializes a new instance of the class. - - The test. - - - - Calls TestStarted on the specified listener. - - The listener. - - - - TestFinishedEvent holds information needed to call the TestFinished method. - - - - - Initializes a new instance of the class. - - The result. - - - - Calls TestFinished on the specified listener. - - The listener. - - - - Implements a queue of work items each of which - is queued as a WaitCallback. - - - - - Construct a new EventQueue - - - - - WaitHandle for synchronous event delivery in . - - Having just one handle for the whole implies that - there may be only one producer (the test thread) for synchronous events. - If there can be multiple producers for synchronous events, one would have - to introduce one WaitHandle per event. - - - - - - Sets a handle on which to wait, when is called - for an with == true. - - - The wait handle on which to wait, when is called - for an with == true. - The caller is responsible for disposing this wait handle. - - - - - Enqueues the specified event - - The event to enqueue. - - - - Removes the first element from the queue and returns it (or null). - - - If true and the queue is empty, the calling thread is blocked until - either an element is enqueued, or is called. - - - - - If the queue not empty - the first element. - - - otherwise, if ==false - or has been called - null. - - - - - - - Stop processing of the queue - - - - - Gets the count of items in the queue. - - - - - QueuingEventListener uses an EventQueue to store any - events received on its EventListener interface. - - - - - A test has started - - The test that is starting - - - - A test case finished - - Result of the test case - - - - The EvenQueue created and filled by this listener - - - - - A SimpleWorkItem represents a single test case and is - marked as completed immediately upon execution. This - class is also used for skipped or ignored test suites. - - - - - Construct a simple work item for a test. - - The test to be executed - The filter used to select this test - - - - Method that performs actually performs the work. - - - - - A TestWorker pulls work items from a queue - and executes them. - - - - - Construct a new TestWorker. - - The queue from which to pull work items - The name of this worker - The apartment state to use for running tests - - - - Our ThreadProc, which pulls and runs tests in a loop - - - - - Start processing work items. - - - - - Stop the thread, either immediately or after finishing the current WorkItem - - - - - Event signaled immediately before executing a WorkItem - - - - - Event signaled immediately after executing a WorkItem - - - - - The name of this worker - also used for the thread - - - - - Indicates whether the worker thread is running - - - - - TextMessageWriter writes constraint descriptions and messages - in displayable form as a text stream. It tailors the display - of individual message components to form the standard message - format of NUnit assertion failure messages. - - - - - MessageWriter is the abstract base for classes that write - constraint descriptions and messages in some form. The - class has separate methods for writing various components - of a message, allowing implementations to tailor the - presentation as needed. - - - - - Construct a MessageWriter given a culture - - - - - Method to write single line message with optional args, usually - written to precede the general failure message. - - The message to be written - Any arguments used in formatting the message - - - - Method to write single line message with optional args, usually - written to precede the general failure message, at a givel - indentation level. - - The indentation level of the message - The message to be written - Any arguments used in formatting the message - - - - Display Expected and Actual lines for a constraint. This - is called by MessageWriter's default implementation of - WriteMessageTo and provides the generic two-line display. - - The failing constraint result - - - - Display Expected and Actual lines for given _values. This - method may be called by constraints that need more control over - the display of actual and expected _values than is provided - by the default implementation. - - The expected value - The actual value causing the failure - - - - Display Expected and Actual lines for given _values, including - a tolerance value on the Expected line. - - The expected value - The actual value causing the failure - The tolerance within which the test was made - - - - Display the expected and actual string _values on separate lines. - If the mismatch parameter is >=0, an additional line is displayed - line containing a caret that points to the mismatch point. - - The expected string value - The actual string value - The point at which the strings don't match or -1 - If true, case is ignored in locating the point where the strings differ - If true, the strings should be clipped to fit the line - - - - Writes the text for an actual value. - - The actual value. - - - - Writes the text for a generalized value. - - The value. - - - - Writes the text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Abstract method to get the max line length - - - - - Prefix used for the expected value line of a message - - - - - Prefix used for the actual value line of a message - - - - - Length of a message prefix - - - - - Construct a TextMessageWriter - - - - - Construct a TextMessageWriter, specifying a user message - and optional formatting arguments. - - - - - - - Method to write single line message with optional args, usually - written to precede the general failure message, at a given - indentation level. - - The indentation level of the message - The message to be written - Any arguments used in formatting the message - - - - Display Expected and Actual lines for a constraint. This - is called by MessageWriter's default implementation of - WriteMessageTo and provides the generic two-line display. - - The result of the constraint that failed - - - - Display Expected and Actual lines for given _values. This - method may be called by constraints that need more control over - the display of actual and expected _values than is provided - by the default implementation. - - The expected value - The actual value causing the failure - - - - Display Expected and Actual lines for given _values, including - a tolerance value on the expected line. - - The expected value - The actual value causing the failure - The tolerance within which the test was made - - - - Display the expected and actual string _values on separate lines. - If the mismatch parameter is >=0, an additional line is displayed - line containing a caret that points to the mismatch point. - - The expected string value - The actual string value - The point at which the strings don't match or -1 - If true, case is ignored in string comparisons - If true, clip the strings to fit the max line length - - - - Writes the text for an actual value. - - The actual value. - - - - Writes the text for a generalized value. - - The value. - - - - Writes the text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Write the generic 'Expected' line for a constraint - - The constraint that failed - - - - Write the generic 'Expected' line for a given value - - The expected value - - - - Write the generic 'Expected' line for a given value - and tolerance. - - The expected value - The tolerance within which the test was made - - - - Write the generic 'Actual' line for a constraint - - The ConstraintResult for which the actual value is to be written - - - - Write the generic 'Actual' line for a given value - - The actual value causing a failure - - - - Gets or sets the maximum line length for this writer - - - - - ParallelWorkItemDispatcher handles execution of work items by - queuing them for worker threads to process. - - - - - Construct a ParallelWorkItemDispatcher - - Number of workers to use - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and used when stopping the run. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - Enumerates all the shifts supported by the dispatcher - - - - - WorkItemQueueState indicates the current state of a WorkItemQueue - - - - - The queue is paused - - - - - The queue is running - - - - - The queue is stopped - - - - - A WorkItemQueue holds work items that are ready to - be run, either initially or after some dependency - has been satisfied. - - - - - Initializes a new instance of the class. - - The name of the queue. - - - - Enqueue a WorkItem to be processed - - The WorkItem to process - - - - Dequeue a WorkItem for processing - - A WorkItem or null if the queue has stopped - - - - Start or restart processing of items from the queue - - - - - Signal the queue to stop - - - - - Pause the queue for restarting later - - - - - Gets the name of the work item queue. - - - - - Gets the total number of items processed so far - - - - - Gets the maximum number of work items. - - - - - Gets the current state of the queue - - - - - Get a bool indicating whether the queue is empty. - - - - - The current state of a work item - - - - - Ready to run or continue - - - - - Work Item is executing - - - - - Complete - - - - - GenericMethodHelper is able to deduce the Type arguments for - a generic method from the actual arguments provided. - - - - - Construct a GenericMethodHelper for a method - - MethodInfo for the method to examine - - - - Return the type argments for the method, deducing them - from the arguments actually provided. - - The arguments to the method - An array of type arguments. - - - - InvalidTestFixtureException is thrown when an appropriate test - fixture constructor using the provided arguments cannot be found. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The message. - - - - Initializes a new instance of the class. - - The message. - The inner. - - - - Serialization Constructor - - - - - Randomizer returns a set of random _values in a repeatable - way, to allow re-running of tests if necessary. It extends - the .NET Random class, providing random values for a much - wider range of types. - - The class is used internally by the framework to generate - test case data and is also exposed for use by users through - the TestContext.Random property. - - - For consistency with the underlying Random Type, methods - returning a single value use the prefix "Next..." Those - without an argument return a non-negative value up to - the full positive range of the Type. Overloads are provided - for specifying a maximum or a range. Methods that return - arrays or strings use the prefix "Get..." to avoid - confusion with the single-value methods. - - - - - Default characters for random functions. - - Default characters are the English alphabet (uppercase & lowercase), arabic numerals, and underscore - - - - Get a Randomizer for a particular member, returning - one that has already been created if it exists. - This ensures that the same _values are generated - each time the tests are reloaded. - - - - - Get a randomizer for a particular parameter, returning - one that has already been created if it exists. - This ensures that the same values are generated - each time the tests are reloaded. - - - - - Create a new Randomizer using the next seed - available to ensure that each randomizer gives - a unique sequence of values. - - - - - - Default constructor - - - - - Construct based on seed value - - - - - - Returns a random unsigned int. - - - - - Returns a random unsigned int less than the specified maximum. - - - - - Returns a random unsigned int within a specified range. - - - - - Returns a non-negative random short. - - - - - Returns a non-negative random short less than the specified maximum. - - - - - Returns a non-negative random short within a specified range. - - - - - Returns a random unsigned short. - - - - - Returns a random unsigned short less than the specified maximum. - - - - - Returns a random unsigned short within a specified range. - - - - - Returns a random long. - - - - - Returns a random long less than the specified maximum. - - - - - Returns a non-negative random long within a specified range. - - - - - Returns a random ulong. - - - - - Returns a random ulong less than the specified maximum. - - - - - Returns a non-negative random long within a specified range. - - - - - Returns a random Byte - - - - - Returns a random Byte less than the specified maximum. - - - - - Returns a random Byte within a specified range - - - - - Returns a random SByte - - - - - Returns a random sbyte less than the specified maximum. - - - - - Returns a random sbyte within a specified range - - - - - Returns a random bool - - - - - Returns a random bool based on the probablility a true result - - - - - Returns a random double between 0.0 and the specified maximum. - - - - - Returns a random double within a specified range. - - - - - Returns a random float. - - - - - Returns a random float between 0.0 and the specified maximum. - - - - - Returns a random float within a specified range. - - - - - Returns a random enum value of the specified Type as an object. - - - - - Returns a random enum value of the specified Type. - - - - - Generate a random string based on the characters from the input string. - - desired length of output string. - string representing the set of characters from which to construct the resulting string - A random string of arbitrary length - - - - Generate a random string based on the characters from the input string. - - desired length of output string. - A random string of arbitrary length - Uses DefaultStringChars as the input character set - - - - Generate a random string based on the characters from the input string. - - A random string of the default length - Uses DefaultStringChars as the input character set - - - - Returns a random decimal. - - - - - Returns a random decimal between positive zero and the specified maximum. - - - - - Returns a random decimal within a specified range, which is not - permitted to exceed decimal.MaxVal in the current implementation. - - - A limitation of this implementation is that the range from min - to max must not exceed decimal.MaxVal. - - - - - Initial seed used to create randomizers for this run - - - - - StackFilter class is used to remove internal NUnit - entries from a stack trace so that the resulting - trace provides better information about the test. - - - - - Filters a raw stack trace and returns the result. - - The original stack trace - A filtered stack trace - - - - Provides methods to support legacy string comparison methods. - - - - - Compares two strings for equality, ignoring case if requested. - - The first string. - The second string.. - if set to true, the case of the letters in the strings is ignored. - Zero if the strings are equivalent, a negative number if strA is sorted first, a positive number if - strB is sorted first - - - - Compares two strings for equality, ignoring case if requested. - - The first string. - The second string.. - if set to true, the case of the letters in the strings is ignored. - True if the strings are equivalent, false if not. - - - - Enumeration indicating whether the tests are - running normally or being cancelled. - - - - - Running normally with no stop requested - - - - - A graceful stop has been requested - - - - - A forced stop has been requested - - - - - The PropertyNames class provides static constants for the - standard property ids that NUnit uses on tests. - - - - - The FriendlyName of the AppDomain in which the assembly is running - - - - - The selected strategy for joining parameter data into test cases - - - - - The process ID of the executing assembly - - - - - The stack trace from any data provider that threw - an exception. - - - - - The reason a test was not run - - - - - The author of the tests - - - - - The ApartmentState required for running the test - - - - - The categories applying to a test - - - - - The Description of a test - - - - - The number of threads to be used in running tests - - - - - The maximum time in ms, above which the test is considered to have failed - - - - - The ParallelScope associated with a test - - - - - The number of times the test should be repeated - - - - - Indicates that the test should be run on a separate thread - - - - - The culture to be set for a test - - - - - The UI culture to be set for a test - - - - - The type that is under test - - - - - The timeout value for the test - - - - - The test will be ignored until the given date - - - - - Delegate used by tests that execute code and - capture any thrown exception. - - - - - AssertionHelper is an optional base class for user tests, - allowing the use of shorter ids for constraints and - asserts and avoiding conflict with the definition of - , from which it inherits much of its - behavior, in certain mock object frameworks. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that fails if the actual - value matches the pattern supplied as an argument. - - - - - Returns a constraint that tests whether the path provided - is the same as an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is a subpath of the expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether an object graph is serializable in binary format. - - - - - Returns a constraint that tests whether an object graph is serializable in xml format. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically to - . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically to . - - The evaluated condition - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Returns a ListMapper based on a collection. - - The original collection - - - - - Provides static methods to express the assumptions - that must be met for a test to give a meaningful - result. If an assumption is not met, the test - should produce an inconclusive result. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the - method throws an . - - The evaluated condition - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Attribute used to apply a category to a test - - - - - The name of the category - - - - - Construct attribute for a given category based on - a name. The name may not contain the characters ',', - '+', '-' or '!'. However, this is not checked in the - constructor since it would cause an error to arise at - as the test was loaded without giving a clear indication - of where the problem is located. The error is handled - in NUnitFramework.cs by marking the test as not - runnable. - - The name of the category - - - - Protected constructor uses the Type name as the name - of the category. - - - - - Modifies a test by adding a category to it. - - The test to modify - - - - The name of the category - - - - - Marks a test to use a combinatorial join of any argument - data provided. Since this is the default, the attribute is - optional. - - - - - Default constructor - - - - - CultureAttribute is used to mark a test fixture or an - individual method as applying to a particular Culture only. - - - - - Abstract base for Attributes that are used to include tests - in the test run based on environmental settings. - - - - - Constructor with no included items specified, for use - with named property syntax. - - - - - Constructor taking one or more included items - - Comma-delimited list of included items - - - - Name of the item that is needed in order for - a test to run. Multiple items may be given, - separated by a comma. - - - - - Name of the item to be excluded. Multiple items - may be given, separated by a comma. - - - - - The reason for including or excluding the test - - - - - Constructor with no cultures specified, for use - with named property syntax. - - - - - Constructor taking one or more cultures - - Comma-deliminted list of cultures - - - - Causes a test to be skipped if this CultureAttribute is not satisfied. - - The test to modify - - - - Tests to determine if the current culture is supported - based on the properties of this attribute. - - True, if the current culture is supported - - - - Test to determine if the a particular culture or comma- - delimited set of cultures is in use. - - Name of the culture or comma-separated list of culture ids - True if the culture is in use on the system - - - - Test to determine if one of a collection of cultures - is being used currently. - - - - - - - The abstract base class for all data-providing attributes - defined by NUnit. Used to select all data sources for a - method, class or parameter. - - - - - Default constructor - - - - - Used to mark a field for use as a datapoint when executing a theory - within the same fixture that requires an argument of the field's Type. - - - - - Used to mark a field, property or method providing a set of datapoints to - be used in executing any theories within the same fixture that require an - argument of the Type provided. The data source may provide an array of - the required Type or an . - Synonymous with DatapointSourceAttribute. - - - - - Used to mark a field, property or method providing a set of datapoints to - be used in executing any theories within the same fixture that require an - argument of the Type provided. The data source may provide an array of - the required Type or an . - Synonymous with DatapointsAttribute. - - - - - Attribute used to provide descriptive text about a - test case or fixture. - - - - - Construct a description Attribute - - The text of the description - - - - ExplicitAttribute marks a test or test fixture so that it will - only be run if explicitly executed from the gui or command line - or if it is included by use of a filter. The test will not be - run simply because an enclosing suite is run. - - - - - Default constructor - - - - - Constructor with a reason - - The reason test is marked explicit - - - - Modifies a test by marking it as explicit. - - The test to modify - - - - Attribute used to mark a test that is to be ignored. - Ignored tests result in a warning message when the - tests are run. - - - - - Constructs the attribute giving a reason for ignoring the test - - The reason for ignoring the test - - - - Modifies a test by marking it as Ignored. - - The test to modify - - - - The date in the future to stop ignoring the test as a string in UTC time. - For example for a date and time, "2014-12-25 08:10:00Z" or for just a date, - "2014-12-25". If just a date is given, the Ignore will expire at midnight UTC. - - - Once the ignore until date has passed, the test will be marked - as runnable. Tests with an ignore until date will have an IgnoreUntilDate - property set which will appear in the test results. - - The string does not contain a valid string representation of a date and time. - - - - Summary description for MaxTimeAttribute. - - - - - Construct a MaxTimeAttribute, given a time in milliseconds. - - The maximum elapsed time in milliseconds - - - - Marks a test to use a pairwise join of any argument - data provided. Arguments will be combined in such a - way that all possible pairs of arguments are used. - - - - - Default constructor - - - - - PlatformAttribute is used to mark a test fixture or an - individual method as applying to a particular platform only. - - - - - Constructor with no platforms specified, for use - with named property syntax. - - - - - Constructor taking one or more platforms - - Comma-delimited list of platforms - - - - Causes a test to be skipped if this PlatformAttribute is not satisfied. - - The test to modify - - - - RandomAttribute is used to supply a set of random _values - to a single parameter of a parameterized test. - - - - - Construct a random set of values appropriate for the Type of the - parameter on which the attribute appears, specifying only the count. - - - - - - Construct a set of ints within a specified range - - - - - Construct a set of unsigned ints within a specified range - - - - - Construct a set of longs within a specified range - - - - - Construct a set of unsigned longs within a specified range - - - - - Construct a set of shorts within a specified range - - - - - Construct a set of unsigned shorts within a specified range - - - - - Construct a set of doubles within a specified range - - - - - Construct a set of floats within a specified range - - - - - Construct a set of bytes within a specified range - - - - - Construct a set of sbytes within a specified range - - - - - Get the collection of _values to be used as arguments. - - - - - RangeAttribute is used to supply a range of _values to an - individual parameter of a parameterized test. - - - - - ValuesAttribute is used to provide literal arguments for - an individual parameter of a test. - - - - - The collection of data to be returned. Must - be set by any derived attribute classes. - We use an object[] so that the individual - elements may have their type changed in GetData - if necessary - - - - - Constructs for use with an Enum parameter. Will pass every enum - value in to the test. - - - - - Construct with one argument - - - - - - Construct with two arguments - - - - - - - Construct with three arguments - - - - - - - - Construct with an array of arguments - - - - - - Get the collection of _values to be used as arguments - - - - - Construct a range of ints using default step of 1 - - - - - - - Construct a range of ints specifying the step size - - - - - - - - Construct a range of unsigned ints using default step of 1 - - - - - - - Construct a range of unsigned ints specifying the step size - - - - - - - - Construct a range of longs using a default step of 1 - - - - - - - Construct a range of longs - - - - - - - - Construct a range of unsigned longs using default step of 1 - - - - - - - Construct a range of unsigned longs specifying the step size - - - - - - - - Construct a range of doubles - - - - - - - - Construct a range of floats - - - - - - - - RepeatAttribute may be applied to test case in order - to run it multiple times. - - - - - Construct a RepeatAttribute - - The number of times to run the test - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - The test command for the RepeatAttribute - - - - - Initializes a new instance of the class. - - The inner command. - The number of repetitions - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - Marks a test that must run in the MTA, causing it - to run in a separate thread if necessary. - - On methods, you may also use MTAThreadAttribute - to serve the same purpose. - - - - - Construct a RequiresMTAAttribute - - - - - Marks a test that must run in the STA, causing it - to run in a separate thread if necessary. - - - - - Construct a RequiresSTAAttribute - - - - - Marks a test that must run on a separate thread. - - - - - Construct a RequiresThreadAttribute - - - - - Construct a RequiresThreadAttribute, specifying the apartment - - - - - Marks a test to use a Sequential join of any argument - data provided. Arguments will be combined into test cases, - taking the next value of each argument until all are used. - - - - - Default constructor - - - - - Summary description for SetCultureAttribute. - - - - - Construct given the name of a culture - - - - - - Summary description for SetUICultureAttribute. - - - - - Construct given the name of a culture - - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - SetUpFixtureAttribute is used to identify a SetUpFixture - - - - - Build a SetUpFixture from type provided. Normally called for a Type - on which the attribute has been placed. - - The type info of the fixture to be used. - A SetUpFixture object as a TestSuite. - - - - Attribute used to identify a method that is called - immediately after each test is run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Adding this attribute to a method within a - class makes the method callable from the NUnit test runner. There is a property - called Description which is optional which you can provide a more detailed test - description. This class cannot be inherited. - - - - [TestFixture] - public class Fixture - { - [Test] - public void MethodToTest() - {} - - [Test(Description = "more detailed description")] - public void TestDescriptionMethod() - {} - } - - - - - - Modifies a test by adding a description, if not already set. - - The test to modify - - - - Construct a TestMethod from a given method. - - The method for which a test is to be constructed. - The suite to which the test will be added. - A TestMethod - - - - Descriptive text for this test - - - - - The author of this test - - - - - The type that this test is testing - - - - - Gets or sets the expected result. - - The result. - - - - Returns true if an expected result has been set - - - - - TestCaseAttribute is used to mark parameterized test cases - and provide them with their arguments. - - - - - Construct a TestCaseAttribute with a list of arguments. - This constructor is not CLS-Compliant - - - - - - Construct a TestCaseAttribute with a single argument - - - - - - Construct a TestCaseAttribute with a two arguments - - - - - - - Construct a TestCaseAttribute with a three arguments - - - - - - - - Performs several special conversions allowed by NUnit in order to - permit arguments with types that cannot be used in the constructor - of an Attribute such as TestCaseAttribute or to simplify their use. - - The arguments to be converted - The ParameterInfo array for the method - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Gets or sets the name of the test. - - The name of the test. - - - - Gets or sets the RunState of this test case. - - - - - Gets the list of arguments to a test case - - - - - Gets the properties of the test case - - - - - Gets or sets the expected result. - - The result. - - - - Returns true if the expected result has been set - - - - - Gets or sets the description. - - The description. - - - - The author of this test - - - - - The type that this test is testing - - - - - Gets or sets the reason for ignoring the test - - - - - Gets or sets a value indicating whether this is explicit. - - - true if explicit; otherwise, false. - - - - - Gets or sets the reason for not running the test. - - The reason. - - - - Gets or sets the ignore reason. When set to a non-null - non-empty value, the test is marked as ignored. - - The ignore reason. - - - - Comma-delimited list of platforms to run the test for - - - - - Comma-delimited list of platforms to not run the test for - - - - - Gets and sets the category for this test case. - May be a comma-separated list of categories. - - - - - TestCaseSourceAttribute indicates the source to be used to - provide test cases for a test method. - - - - - Construct with the name of the method, property or field that will provide data - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Construct with a Type - - The type that will provide data - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The IMethod for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Returns a set of ITestCaseDataItems for use as arguments - to a parameterized test method. - - The method for which data is needed. - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - Gets or sets the category associated with every fixture created from - this attribute. May be a single category or a comma-separated list. - - - - - TestFixtureAttribute is used to mark a class that represents a TestFixture. - - - - - Default constructor - - - - - Construct with a object[] representing a set of arguments. - In .NET 2.0, the arguments may later be separated into - type arguments and constructor arguments. - - - - - - Build a fixture from type provided. Normally called for a Type - on which the attribute has been placed. - - The type info of the fixture to be used. - A an IEnumerable holding one TestFixture object. - - - - Gets or sets the name of the test. - - The name of the test. - - - - Gets or sets the RunState of this test fixture. - - - - - The arguments originally provided to the attribute - - - - - Properties pertaining to this fixture - - - - - Get or set the type arguments. If not set - explicitly, any leading arguments that are - Types are taken as type arguments. - - - - - Descriptive text for this fixture - - - - - The author of this fixture - - - - - The type that this fixture is testing - - - - - Gets or sets the ignore reason. May set RunState as a side effect. - - The ignore reason. - - - - Gets or sets the reason for not running the fixture. - - The reason. - - - - Gets or sets the ignore reason. When set to a non-null - non-empty value, the test is marked as ignored. - - The ignore reason. - - - - Gets or sets a value indicating whether this is explicit. - - - true if explicit; otherwise, false. - - - - - Gets and sets the category for this fixture. - May be a comma-separated list of categories. - - - - - Attribute used to identify a method that is - called before any tests in a fixture are run. - - - - - Attribute used to identify a method that is called after - all the tests in a fixture have run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Adding this attribute to a method within a - class makes the method callable from the NUnit test runner. There is a property - called Description which is optional which you can provide a more detailed test - description. This class cannot be inherited. - - - - [TestFixture] - public class Fixture - { - [Test] - public void MethodToTest() - {} - - [Test(Description = "more detailed description")] - public void TestDescriptionMethod() - {} - } - - - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Used on a method, marks the test with a timeout value in milliseconds. - The test will be run in a separate thread and is cancelled if the timeout - is exceeded. Used on a class or assembly, sets the default timeout - for all contained test methods. - - - - - Construct a TimeoutAttribute given a time in milliseconds - - The timeout value in milliseconds - - - - ValueSourceAttribute indicates the source to be used to - provide data for one parameter of a test method. - - - - - Construct with the name of the factory - for use with languages - that don't support params arrays. - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - for use with languages - that don't support params arrays. - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Gets an enumeration of data items for use as arguments - for a test method parameter. - - The parameter for which data is needed - - An enumeration containing individual data items - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - A set of Assert methods operating on one or more collections - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable containing objects to be considered - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the superset does not contain the subset - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - - - - Asserts that the superset does not contain the subset - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the superset contains the subset. - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - - - - Asserts that the superset contains the subset. - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the subset does not contain the superset - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - - - - Asserts that the subset does not contain the superset - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the subset contains the superset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - - - - Asserts that the subset contains the superset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array,list or other collection is empty - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array,list or other collection is empty - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - - - - AllItemsConstraint applies another constraint to each - item in a collection, succeeding if they all succeed. - - - - - Abstract base class used for prefixes - - - - - The base constraint - - - - - Prefix used in forming the constraint description - - - - - Construct given a base constraint - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Construct an AllItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - failing if any item fails. - - - - - - - AndConstraint succeeds only if both members succeed. - - - - - BinaryConstraint is the abstract base of all constraints - that combine two other constraints in some fashion. - - - - - The first constraint being combined - - - - - The second constraint being combined - - - - - Construct a BinaryConstraint from two other constraints - - The first constraint - The second constraint - - - - Create an AndConstraint from two other constraints - - The first constraint - The second constraint - - - - Apply both member constraints to an actual value, succeeding - succeeding only if both of them succeed. - - The actual value - True if the constraints both succeeded - - - - Gets text describing a constraint - - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - AssignableFromConstraint is used to test that an object - can be assigned from a given Type. - - - - - TypeConstraint is the abstract base for constraints - that take a Type as their expected value. - - - - - The expected Type used by the constraint - - - - - The type of the actual argument to which the constraint was applied - - - - - Construct a TypeConstraint for a given Type - - The expected type for the constraint - Prefix used in forming the constraint description - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - Construct an AssignableFromConstraint for the type provided - - - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - AssignableToConstraint is used to test that an object - can be assigned to a given Type. - - - - - Construct an AssignableToConstraint for the type provided - - - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - AttributeConstraint tests that a specified attribute is present - on a Type or other provider and that the value of the attribute - satisfies some other constraint. - - - - - Constructs an AttributeConstraint for a specified attribute - Type and base constraint. - - - - - - - Determines whether the Type or other provider has the - expected attribute and if its value matches the - additional constraint specified. - - - - - Returns a string representation of the constraint. - - - - - AttributeExistsConstraint tests for the presence of a - specified attribute on a Type. - - - - - Constructs an AttributeExistsConstraint for a specific attribute Type - - - - - - Tests whether the object provides the expected attribute. - - A Type, MethodInfo, or other ICustomAttributeProvider - True if the expected attribute is present, otherwise false - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - BinarySerializableConstraint tests whether - an object is serializable in binary format. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Returns the string representation - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionEquivalentConstraint is used to determine whether two - collections are equivalent. - - - - - Construct a CollectionEquivalentConstraint - - - - - - Test whether two collections are equivalent - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionOrderedConstraint is used to test whether a collection is ordered. - - - - - Construct a CollectionOrderedConstraint - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use a and returns self. - - - - - Modifies the constraint to test ordering by the value of - a specified property and returns self. - - - - - Test whether the collection is ordered - - - - - - - Returns the string representation of the constraint. - - - - - - If used performs a reverse comparison - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionSubsetConstraint is used to determine whether - one collection is a subset of another - - - - - Construct a CollectionSubsetConstraint - - The collection that the actual value is expected to be a subset of - - - - Test whether the actual collection is a subset of - the expected collection provided. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionTally counts (tallies) the number of - occurrences of each object in one or more enumerations. - - - - - Construct a CollectionTally object from a comparer and a collection - - - - - Try to remove an object from the tally - - The object to remove - True if successful, false if the object was not found - - - - Try to remove a set of objects from the tally - - The objects to remove - True if successful, false if any object was not found - - - - The number of objects remaining in the tally - - - - - ComparisonAdapter class centralizes all comparisons of - _values in NUnit, adapting to the use of any provided - , - or . - - - - - Returns a ComparisonAdapter that wraps an - - - - - Returns a ComparisonAdapter that wraps an - - - - - Returns a ComparisonAdapter that wraps a - - - - - Compares two objects - - - - - Gets the default ComparisonAdapter, which wraps an - NUnitComparer object. - - - - - Construct a ComparisonAdapter for an - - - - - Compares two objects - - - - - - - - Construct a default ComparisonAdapter - - - - - ComparerAdapter extends and - allows use of an or - to actually perform the comparison. - - - - - Construct a ComparisonAdapter for an - - - - - Compare a Type T to an object - - - - - Construct a ComparisonAdapter for a - - - - - Compare a Type T to an object - - - - - Abstract base class for constraints that compare _values to - determine if one is greater than, equal to or less than - the other. - - - - - The value against which a comparison is to be made - - - - - If true, less than returns success - - - - - if true, equal returns success - - - - - if true, greater than returns success - - - - - ComparisonAdapter to be used in making the comparison - - - - - Initializes a new instance of the class. - - The value against which to make a comparison. - if set to true less succeeds. - if set to true equal succeeds. - if set to true greater succeeds. - String used in describing the constraint. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Modifies the constraint to use an and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Modifies the constraint to use an and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Modifies the constraint to use a and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Delegate used to delay evaluation of the actual value - to be used in evaluating a constraint - - - - - ConstraintBuilder maintains the stacks that are used in - processing a ConstraintExpression. An OperatorStack - is used to hold operators that are waiting for their - operands to be reorganized. a ConstraintStack holds - input constraints as well as the results of each - operator applied. - - - - - Initializes a new instance of the class. - - - - - Appends the specified operator to the expression by first - reducing the operator stack and then pushing the new - operator on the stack. - - The operator to push. - - - - Appends the specified constraint to the expression by pushing - it on the constraint stack. - - The constraint to push. - - - - Sets the top operator right context. - - The right context. - - - - Reduces the operator stack until the topmost item - precedence is greater than or equal to the target precedence. - - The target precedence. - - - - Resolves this instance, returning a Constraint. If the Builder - is not currently in a resolvable state, an exception is thrown. - - The resolved constraint - - - - Gets a value indicating whether this instance is resolvable. - - - true if this instance is resolvable; otherwise, false. - - - - - OperatorStack is a type-safe stack for holding ConstraintOperators - - - - - Initializes a new instance of the class. - - The ConstraintBuilder using this stack. - - - - Pushes the specified operator onto the stack. - - The operator to put onto the stack. - - - - Pops the topmost operator from the stack. - - The topmost operator on the stack - - - - Gets a value indicating whether this is empty. - - true if empty; otherwise, false. - - - - Gets the topmost operator without modifying the stack. - - - - - ConstraintStack is a type-safe stack for holding Constraints - - - - - Initializes a new instance of the class. - - The ConstraintBuilder using this stack. - - - - Pushes the specified constraint. As a side effect, - the constraint's Builder field is set to the - ConstraintBuilder owning this stack. - - The constraint to put onto the stack - - - - Pops this topmost constraint from the stack. - As a side effect, the constraint's Builder - field is set to null. - - The topmost contraint on the stack - - - - Gets a value indicating whether this is empty. - - true if empty; otherwise, false. - - - - ConstraintExpression represents a compound constraint in the - process of being constructed from a series of syntactic elements. - - Individual elements are appended to the expression as they are - reorganized. When a constraint is appended, it is returned as the - value of the operation so that modifiers may be applied. However, - any partially built expression is attached to the constraint for - later resolution. When an operator is appended, the partial - expression is returned. If it's a self-resolving operator, then - a ResolvableConstraintExpression is returned. - - - - - The ConstraintBuilder holding the elements recognized so far - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class passing in a ConstraintBuilder, which may be pre-populated. - - The builder. - - - - Returns a string representation of the expression as it - currently stands. This should only be used for testing, - since it has the side-effect of resolving the expression. - - - - - - Appends an operator to the expression and returns the - resulting expression itself. - - - - - Appends a self-resolving operator to the expression and - returns a new ResolvableConstraintExpression. - - - - - Appends a constraint to the expression and returns that - constraint, which is associated with the current state - of the expression being built. Note that the constraint - is not reduced at this time. For example, if there - is a NotOperator on the stack we don't reduce and - return a NotConstraint. The original constraint must - be returned because it may support modifiers that - are yet to be applied. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns the constraint provided as an argument - used to allow custom - custom constraints to easily participate in the syntax. - - - - - Returns the constraint provided as an argument - used to allow custom - custom constraints to easily participate in the syntax. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that tests whether the path provided - is the same as an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the a subpath of the expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - With is currently a NOP - reserved for future use. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether an object graph is serializable in binary format. - - - - - Returns a constraint that tests whether an object graph is serializable in xml format. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - Returns a constraint that succeeds if the value - is a file or directory and it exists. - - - - - ContainsConstraint tests a whether a string contains a substring - or a collection contains an object. It postpones the decision of - which test to use until the type of the actual argument is known. - This allows testing whether a string is contained in a collection - or as a substring of another string using the same syntax. - - - - - Initializes a new instance of the class. - - The _expected. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Flag the constraint to ignore case and return self. - - - - - Applies a delay to the match so that a match can be evaluated in the future. - - - - - Creates a new DelayedConstraint - - The inner constraint to decorate - The time interval after which the match is performed - If the value of is less than 0 - - - - Creates a new DelayedConstraint - - The inner constraint to decorate - The time interval after which the match is performed, in milliseconds - The time interval used for polling, in milliseconds - If the value of is less than 0 - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for if the base constraint fails, false if it succeeds - - - - Test whether the constraint is satisfied by a delegate - - The delegate whose value is to be tested - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - Overridden to wait for the specified delay period before - calling the base constraint with the dereferenced value. - - A reference to the value to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - Adjusts a Timestamp by a given TimeSpan - - - - - - - - Returns the difference between two Timestamps as a TimeSpan - - - - - - - - Gets text describing a constraint - - - - - DictionaryContainsKeyConstraint is used to test whether a dictionary - contains an expected object as a key. - - - - - Construct a DictionaryContainsKeyConstraint - - - - - - Test whether the expected key is contained in the dictionary - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyCollectionConstraint tests whether a collection is empty. - - - - - Check that the collection is empty - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyConstraint tests a whether a string or collection is empty, - postponing the decision about which test is applied until the - type of the actual argument is known. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyDirectoryConstraint is used to test that a directory is empty - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyStringConstraint tests whether a string is empty. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EndsWithConstraint can test whether a string ends - with an expected substring. - - - - - Initializes a new instance of the class. - - The expected string - - - - Test whether the constraint is matched by the actual value. - This is a template method, which calls the IsMatch method - of the derived class. - - - - - - - EqualConstraint is able to compare an actual value with the - expected value provided in its constructor. Two objects are - considered equal if both are null, or if both have the same - value. NUnit has special semantics for some object types. - - - - - NUnitEqualityComparer used to test equality. - - - - - Initializes a new instance of the class. - - The expected value. - - - - Flag the constraint to use a tolerance when determining equality. - - Tolerance value to be used - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Gets the tolerance for this comparison. - - - The tolerance. - - - - - Gets a value indicating whether to compare case insensitive. - - - true if comparing case insensitive; otherwise, false. - - - - - Gets a value indicating whether or not to clip strings. - - - true if set to clip strings otherwise, false. - - - - - Gets the failure points. - - - The failure points. - - - - - Flag the constraint to ignore case and return self. - - - - - Flag the constraint to suppress string clipping - and return self. - - - - - Flag the constraint to compare arrays as collections - and return self. - - - - - Flags the constraint to include - property in comparison of two values. - - - Using this modifier does not allow to use the - constraint modifier. - - - - - Switches the .Within() modifier to interpret its tolerance as - a distance in representable _values (see remarks). - - Self. - - Ulp stands for "unit in the last place" and describes the minimum - amount a given value can change. For any integers, an ulp is 1 whole - digit. For floating point _values, the accuracy of which is better - for smaller numbers and worse for larger numbers, an ulp depends - on the size of the number. Using ulps for comparison of floating - point results instead of fixed tolerances is safer because it will - automatically compensate for the added inaccuracy of larger numbers. - - - - - Switches the .Within() modifier to interpret its tolerance as - a percentage that the actual _values is allowed to deviate from - the expected value. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in days. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in hours. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in minutes. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in seconds. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in milliseconds. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in clock ticks. - - Self - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EqualityAdapter class handles all equality comparisons - that use an , - or a . - - - - - Compares two objects, returning true if they are equal - - - - - Returns true if the two objects can be compared by this adapter. - The base adapter cannot handle IEnumerables except for strings. - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps a . - - - - - that wraps an . - - - - - Returns true if the two objects can be compared by this adapter. - Generic adapter requires objects of the specified type. - - - - - that wraps an . - - - - - ExactTypeConstraint is used to test that an object - is of the exact type provided in the constructor - - - - - Construct an ExactTypeConstraint for a given Type - - The expected Type. - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - FalseConstraint tests that the actual value is false - - - - - Initializes a new instance of the class. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - Helper routines for working with floating point numbers - - - The floating point comparison code is based on this excellent article: - http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm - - - "ULP" means Unit in the Last Place and in the context of this library refers to - the distance between two adjacent floating point numbers. IEEE floating point - numbers can only represent a finite subset of natural numbers, with greater - accuracy for smaller numbers and lower accuracy for very large numbers. - - - If a comparison is allowed "2 ulps" of deviation, that means the _values are - allowed to deviate by up to 2 adjacent floating point _values, which might be - as low as 0.0000001 for small numbers or as high as 10.0 for large numbers. - - - - - Compares two floating point _values for equality - First floating point value to be compared - Second floating point value t be compared - - Maximum number of representable floating point _values that are allowed to - be between the left and the right floating point _values - - True if both numbers are equal or close to being equal - - - Floating point _values can only represent a finite subset of natural numbers. - For example, the _values 2.00000000 and 2.00000024 can be stored in a float, - but nothing inbetween them. - - - This comparison will count how many possible floating point _values are between - the left and the right number. If the number of possible _values between both - numbers is less than or equal to maxUlps, then the numbers are considered as - being equal. - - - Implementation partially follows the code outlined here: - http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ - - - - - Compares two double precision floating point _values for equality - First double precision floating point value to be compared - Second double precision floating point value t be compared - - Maximum number of representable double precision floating point _values that are - allowed to be between the left and the right double precision floating point _values - - True if both numbers are equal or close to being equal - - - Double precision floating point _values can only represent a limited series of - natural numbers. For example, the _values 2.0000000000000000 and 2.0000000000000004 - can be stored in a double, but nothing inbetween them. - - - This comparison will count how many possible double precision floating point - _values are between the left and the right number. If the number of possible - _values between both numbers is less than or equal to maxUlps, then the numbers - are considered as being equal. - - - Implementation partially follows the code outlined here: - http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ - - - - - - Reinterprets the memory contents of a floating point value as an integer value - - - Floating point value whose memory contents to reinterpret - - - The memory contents of the floating point value interpreted as an integer - - - - - Reinterprets the memory contents of a double precision floating point - value as an integer value - - - Double precision floating point value whose memory contents to reinterpret - - - The memory contents of the double precision floating point value - interpreted as an integer - - - - - Reinterprets the memory contents of an integer as a floating point value - - Integer value whose memory contents to reinterpret - - The memory contents of the integer value interpreted as a floating point value - - - - - Reinterprets the memory contents of an integer value as a double precision - floating point value - - Integer whose memory contents to reinterpret - - The memory contents of the integer interpreted as a double precision - floating point value - - - - Union of a floating point variable and an integer - - - The union's value as a floating point variable - - - The union's value as an integer - - - The union's value as an unsigned integer - - - Union of a double precision floating point variable and a long - - - The union's value as a double precision floating point variable - - - The union's value as a long - - - The union's value as an unsigned long - - - - Tests whether a value is greater than the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - Tests whether a value is greater than or equal to the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - ConstraintStatus represents the status of a ConstraintResult - returned by a Constraint being applied to an actual value. - - - - - The status has not yet been set - - - - - The constraint succeeded - - - - - The constraint failed - - - - - An error occured in applying the constraint (reserved for future use) - - - - - InstanceOfTypeConstraint is used to test that an object - is of the same type provided or derived from it. - - - - - Construct an InstanceOfTypeConstraint for the type provided - - The expected Type - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - Tests whether a value is less than the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - Tests whether a value is less than or equal to the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - Static methods used in creating messages - - - - - Static string used when strings are clipped - - - - - Formatting strings used for expected and actual _values - - - - - Formats text to represent a generalized value. - - The value - The formatted text - - - - Formats text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Returns the representation of a type as used in NUnitLite. - This is the same as Type.ToString() except for arrays, - which are displayed with their declared sizes. - - - - - - - Converts any control characters in a string - to their escaped representation. - - The string to be converted - The converted string - - - - Return the a string representation for a set of indices into an array - - Array of indices for which a string is needed - - - - Get an array of indices representing the point in a collection or - array corresponding to a single int index into the collection. - - The collection to which the indices apply - Index in the collection - Array of indices - - - - Clip a string to a given length, starting at a particular offset, returning the clipped - string with ellipses representing the removed parts - - The string to be clipped - The maximum permitted length of the result string - The point at which to start clipping - The clipped string - - - - Clip the expected and actual strings in a coordinated fashion, - so that they may be displayed together. - - - - - - - - - Shows the position two strings start to differ. Comparison - starts at the start index. - - The expected string - The actual string - The index in the strings at which comparison should start - Boolean indicating whether case should be ignored - -1 if no mismatch found, or the index where mismatch found - - - - NaNConstraint tests that the actual value is a double or float NaN - - - - - Test that the actual value is an NaN - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - NoItemConstraint applies another constraint to each - item in a collection, failing if any of them succeeds. - - - - - Construct a SomeItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - failing if any item fails. - - - - - - - NotConstraint negates the effect of some other constraint - - - - - Initializes a new instance of the class. - - The base constraint to be negated. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for if the base constraint fails, false if it succeeds - - - - NullConstraint tests that the actual value is null - - - - - Initializes a new instance of the class. - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - The Numerics class contains common operations on numeric _values. - - - - - Checks the type of the object, returning true if - the object is a numeric type. - - The object to check - true if the object is a numeric type - - - - Checks the type of the object, returning true if - the object is a floating point numeric type. - - The object to check - true if the object is a floating point numeric type - - - - Checks the type of the object, returning true if - the object is a fixed point numeric type. - - The object to check - true if the object is a fixed point numeric type - - - - Test two numeric _values for equality, performing the usual numeric - conversions and using a provided or default tolerance. If the tolerance - provided is Empty, this method may set it to a default tolerance. - - The expected value - The actual value - A reference to the tolerance in effect - True if the _values are equal - - - - Compare two numeric _values, performing the usual numeric conversions. - - The expected value - The actual value - The relationship of the _values to each other - - - - NUnitComparer encapsulates NUnit's default behavior - in comparing two objects. - - - - - Compares two objects - - - - - - - - Returns the default NUnitComparer. - - - - - NUnitEqualityComparer encapsulates NUnit's handling of - equality tests between objects. - - - - - If true, all string comparisons will ignore case - - - - - If true, arrays will be treated as collections, allowing - those of different dimensions to be compared - - - - - Comparison objects used in comparisons for some constraints. - - - - - List of points at which a failure occurred. - - - - - Compares two objects for equality within a tolerance. - - - - - Helper method to compare two arrays - - - - - Method to compare two DirectoryInfo objects - - first directory to compare - second directory to compare - true if equivalent, false if not - - - - Returns the default NUnitEqualityComparer - - - - - Gets and sets a flag indicating whether case should - be ignored in determining equality. - - - - - Gets and sets a flag indicating that arrays should be - compared as collections, without regard to their shape. - - - - - Gets the list of external comparers to be used to - test for equality. They are applied to members of - collections, in place of NUnit's own logic. - - - - - Gets the list of failure points for the last Match performed. - The list consists of objects to be interpreted by the caller. - This generally means that the caller may only make use of - objects it has placed on the list at a particular depthy. - - - - - Flags the comparer to include - property in comparison of two values. - - - Using this modifier does not allow to use the - modifier. - - - - - FailurePoint class represents one point of failure - in an equality test. - - - - - The location of the failure - - - - - The expected value - - - - - The actual value - - - - - Indicates whether the expected value is valid - - - - - Indicates whether the actual value is valid - - - - - Operator that requires both it's arguments to succeed - - - - - Abstract base class for all binary operators - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Abstract method that produces a constraint by applying - the operator to its left and right constraint arguments. - - - - - Gets the left precedence of the operator - - - - - Gets the right precedence of the operator - - - - - Construct an AndOperator - - - - - Apply the operator to produce an AndConstraint - - - - - Operator that tests for the presence of a particular attribute - on a type and optionally applies further tests to the attribute. - - - - - Abstract base class for operators that are able to reduce to a - constraint whether or not another syntactic element follows. - - - - - Construct an AttributeOperator for a particular Type - - The Type of attribute tested - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - Negates the test of the constraint it wraps. - - - - - Constructs a new NotOperator - - - - - Returns a NotConstraint applied to its argument. - - - - - Operator that requires at least one of it's arguments to succeed - - - - - Construct an OrOperator - - - - - Apply the operator to produce an OrConstraint - - - - - Operator used to test for the presence of a named Property - on an object and optionally apply further tests to the - value of that property. - - - - - Constructs a PropOperator for a particular named property - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Gets the name of the property to which the operator applies - - - - - Operator that tests that an exception is thrown and - optionally applies further tests to the exception. - - - - - Construct a ThrowsOperator - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - Represents a constraint that simply wraps the - constraint provided as an argument, without any - further functionality, but which modifies the - order of evaluation because of its precedence. - - - - - Constructor for the WithOperator - - - - - Returns a constraint that wraps its argument - - - - - OrConstraint succeeds if either member succeeds - - - - - Create an OrConstraint from two other constraints - - The first constraint - The second constraint - - - - Apply the member constraints to an actual value, succeeding - succeeding as soon as one of them succeeds. - - The actual value - True if either constraint succeeded - - - - Gets text describing a constraint - - - - - Predicate constraint wraps a Predicate in a constraint, - returning success if the predicate is true. - - - - - Construct a PredicateConstraint from a predicate - - - - - Determines whether the predicate succeeds when applied - to the actual value. - - - - - Gets text describing a constraint - - - - - PropertyConstraint extracts a named property and uses - its value as the actual value for a chained constraint. - - - - - Initializes a new instance of the class. - - The name. - The constraint to apply to the property. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - - PropertyExistsConstraint tests that a named property - exists on the object provided through Match. - - Originally, PropertyConstraint provided this feature - in addition to making optional tests on the value - of the property. The two constraints are now separate. - - - - - Initializes a new instance of the class. - - The name of the property. - - - - Test whether the property exists for a given object - - The object to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - RangeConstraint tests whether two _values are within a - specified range. - - - - - Initializes a new instance of the class. - - from must be less than or equal to true - Inclusive beginning of the range. Must be less than or equal to to. - Inclusive end of the range. Must be greater than or equal to from. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use a and returns self. - - - - - Gets text describing a constraint - - - - - RegexConstraint can test whether a string matches - the pattern provided. - - - - - Initializes a new instance of the class. - - The pattern. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - ResolvableConstraintExpression is used to represent a compound - constraint being constructed at a point where the last operator - may either terminate the expression or may have additional - qualifying constraints added to it. - - It is used, for example, for a Property element or for - an Exception element, either of which may be optionally - followed by constraints that apply to the property or - exception. - - - - - Create a new instance of ResolvableConstraintExpression - - - - - Create a new instance of ResolvableConstraintExpression, - passing in a pre-populated ConstraintBuilder. - - - - - Resolve the current expression to a Constraint - - - - - Appends an And Operator to the expression - - - - - Appends an Or operator to the expression. - - - - - ReusableConstraint wraps a constraint expression after - resolving it so that it can be reused consistently. - - - - - Construct a ReusableConstraint from a constraint expression - - The expression to be resolved and reused - - - - Converts a constraint to a ReusableConstraint - - The constraint to be converted - A ReusableConstraint - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Return the top-level constraint for this expression - - - - - - SameAsConstraint tests whether an object is identical to - the object passed to its constructor - - - - - Initializes a new instance of the class. - - The expected object. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Summary description for SamePathConstraint. - - - - - Initializes a new instance of the class. - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - SamePathOrUnderConstraint tests that one path is under another - - - - - Initializes a new instance of the class. - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - SomeItemsConstraint applies another constraint to each - item in a collection, succeeding if any of them succeeds. - - - - - Construct a SomeItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - succeeding if any item succeeds. - - - - - - - StartsWithConstraint can test whether a string starts - with an expected substring. - - - - - Initializes a new instance of the class. - - The expected string - - - - Test whether the constraint is matched by the actual value. - This is a template method, which calls the IsMatch method - of the derived class. - - - - - - - SubstringConstraint can test whether a string contains - the expected substring. - - - - - Initializes a new instance of the class. - - The expected. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - ThrowsConstraint is used to test the exception thrown by - a delegate by applying a constraint to it. - - - - - Initializes a new instance of the class, - using a constraint to be applied to the exception. - - A constraint to apply to the caught exception. - - - - Executes the code of the delegate and captures any exception. - If a non-null base constraint was provided, it applies that - constraint to the exception. - - A delegate representing the code to be tested - True if an exception is thrown and the constraint succeeds, otherwise false - - - - Converts an ActualValueDelegate to a TestDelegate - before calling the primary overload. - - - - - - - Get the actual exception thrown - used by Assert.Throws. - - - - - Gets text describing a constraint - - - - - Write the actual value for a failing constraint test to a - MessageWriter. This override only handles the special message - used when an exception is expected but none is thrown. - - The writer on which the actual value is displayed - - - - ThrowsNothingConstraint tests that a delegate does not - throw an exception. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True if no exception is thrown, otherwise false - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Gets text describing a constraint - - - - - The Tolerance class generalizes the notion of a tolerance - within which an equality test succeeds. Normally, it is - used with numeric types, but it can be used with any - type that supports taking a difference between two - objects and comparing that difference to a value. - - - - - Constructs a linear tolerance of a specified amount - - - - - Constructs a tolerance given an amount and - - - - - Tests that the current Tolerance is linear with a - numeric value, throwing an exception if it is not. - - - - - Returns a default Tolerance object, equivalent to - specifying an exact match unless - is set, in which case, the - will be used. - - - - - Returns an empty Tolerance object, equivalent to - specifying an exact match even if - is set. - - - - - Gets the for the current Tolerance - - - - - Gets the value of the current Tolerance instance. - - - - - Returns a new tolerance, using the current amount as a percentage. - - - - - Returns a new tolerance, using the current amount in Ulps - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of days. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of hours. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of minutes. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of seconds. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of milliseconds. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of clock ticks. - - - - - Returns true if the current tolerance has not been set or is using the . - - - - - Modes in which the tolerance value for a comparison can be interpreted. - - - - - The tolerance was created with a value, without specifying - how the value would be used. This is used to prevent setting - the mode more than once and is generally changed to Linear - upon execution of the test. - - - - - The tolerance is used as a numeric range within which - two compared _values are considered to be equal. - - - - - Interprets the tolerance as the percentage by which - the two compared _values my deviate from each other. - - - - - Compares two _values based in their distance in - representable numbers. - - - - - TrueConstraint tests that the actual value is true - - - - - Initializes a new instance of the class. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - UniqueItemsConstraint tests whether all the items in a - collection are unique. - - - - - Check that all items are unique. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - XmlSerializableConstraint tests whether - an object is serializable in xml format. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Returns the string representation of this constraint - - - - - Gets text describing a constraint - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new DictionaryContainsKeyConstraint checking for the - presence of a particular key in the dictionary. - - - - - Returns a new DictionaryContainsValueConstraint checking for the - presence of a particular value in the dictionary. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Thrown when an assertion failed. - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - Thrown when an assertion failed. - - - - - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - Thrown when a test executes inconclusively. - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - Thrown when an assertion failed. - - - - - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - The ITestCaseBuilder interface is exposed by a class that knows how to - build a test case from certain methods. - - - This interface is not the same as the ITestCaseBuilder interface in NUnit 2.x. - We have reused the name because the two products don't interoperate at all. - - - - - Examine the method and determine if it is suitable for - this builder to use in building a TestCase to be - included in the suite being populated. - - Note that returning false will cause the method to be ignored - in loading the tests. If it is desired to load the method - but label it as non-runnable, ignored, etc., then this - method must return true. - - The test method to examine - The suite being populated - True is the builder can use this method - - - - Build a TestCase from the provided MethodInfo for - inclusion in the suite being constructed. - - The method to be used as a test case - The test suite being populated, or null - A TestCase or null - - - - Asserts on Files - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Verifies that two Streams are equal. Two Streams are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The expected Stream - The actual Stream - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Verifies that two Streams are equal. Two Streams are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The expected Stream - The actual Stream - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A file containing the value that is expected - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A file containing the value that is expected - A file containing the actual value - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - - - - Asserts that two Streams are not equal. If they are equal - an is thrown. - - The expected Stream - The actual Stream - The message to be displayed when the two Stream are the same. - Arguments to be used in formatting the message - - - - Asserts that two Streams are not equal. If they are equal - an is thrown. - - The expected Stream - The actual Stream - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - A file containing the value that is expected - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - A file containing the value that is expected - A file containing the actual value - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - - - - Asserts that the file exists. If it does not exist - an is thrown. - - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that the file exists. If it does not exist - an is thrown. - - A file containing the actual value - - - - Asserts that the file exists. If it does not exist - an is thrown. - - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that the file exists. If it does not exist - an is thrown. - - The path to a file containing the actual value - - - - Asserts that the file does not exist. If it does exist - an is thrown. - - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that the file does not exist. If it does exist - an is thrown. - - A file containing the actual value - - - - Asserts that the file does not exist. If it does exist - an is thrown. - - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that the file does not exist. If it does exist - an is thrown. - - The path to a file containing the actual value - - - - GlobalSettings is a place for setting default _values used - by the framework in performing asserts. - - - - - Default tolerance for floating point equality - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - CombinatorialStrategy creates test cases by using all possible - combinations of the parameter data. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - Provides data from fields marked with the DatapointAttribute or the - DatapointsAttribute. - - - - - Determine whether any data is available for a parameter. - - A ParameterInfo representing one - argument to a parameterized test - - True if any data is available, otherwise false. - - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - A ParameterInfo representing one - argument to a parameterized test - - An IEnumerable providing the required data - - - - - Class to build ether a parameterized or a normal NUnitTestMethod. - There are four cases that the builder must deal with: - 1. The method needs no params and none are provided - 2. The method needs params and they are provided - 3. The method needs no params but they are provided in error - 4. The method needs params but they are not provided - This could have been done using two different builders, but it - turned out to be simpler to have just one. The BuildFrom method - takes a different branch depending on whether any parameters are - provided, but all four cases are dealt with in lower-level methods - - - - - Determines if the method can be used to build an NUnit test - test method of some kind. The method must normally be marked - with an identifying attribute for this to be true. - - Note that this method does not check that the signature - of the method for validity. If we did that here, any - test methods with invalid signatures would be passed - over in silence in the test run. Since we want such - methods to be reported, the check for validity is made - in BuildFrom rather than here. - - An IMethodInfo for the method being used as a test method - True if the builder can create a test case from this method - - - - Build a Test from the provided MethodInfo. Depending on - whether the method takes arguments and on the availability - of test case data, this method may return a single test - or a group of tests contained in a ParameterizedMethodSuite. - - The method for which a test is to be built - A Test representing one or more method invocations - - - - Determines if the method can be used to build an NUnit test - test method of some kind. The method must normally be marked - with an identifying attribute for this to be true. - - Note that this method does not check that the signature - of the method for validity. If we did that here, any - test methods with invalid signatures would be passed - over in silence in the test run. Since we want such - methods to be reported, the check for validity is made - in BuildFrom rather than here. - - An IMethodInfo for the method being used as a test method - The test suite being built, to which the new test would be added - True if the builder can create a test case from this method - - - - Build a Test from the provided MethodInfo. Depending on - whether the method takes arguments and on the availability - of test case data, this method may return a single test - or a group of tests contained in a ParameterizedMethodSuite. - - The method for which a test is to be built - The test fixture being populated, or null - A Test representing one or more method invocations - - - - Builds a ParameterizedMethodSuite containing individual test cases. - - The method for which a test is to be built. - The list of test cases to include. - A ParameterizedMethodSuite populated with test cases - - - - Build a simple, non-parameterized TestMethod for this method. - - The MethodInfo for which a test is to be built - The test suite for which the method is being built - A TestMethod. - - - - NUnitTestFixtureBuilder is able to build a fixture given - a class marked with a TestFixtureAttribute or an unmarked - class containing test methods. In the first case, it is - called by the attribute and in the second directly by - NUnitSuiteBuilder. - - - - - Build a TestFixture from type provided. A non-null TestSuite - must always be returned, since the method is generally called - because the user has marked the target class as a fixture. - If something prevents the fixture from being used, it should - be returned nonetheless, labelled as non-runnable. - - An ITypeInfo for the fixture to be used. - A TestSuite object or one derived from TestSuite. - - - - Overload of BuildFrom called by tests that have arguments. - Builds a fixture using the provided type and information - in the ITestFixtureData object. - - The TypeInfo for which to construct a fixture. - An object implementing ITestFixtureData or null. - - - - - Method to add test cases to the newly constructed fixture. - - The fixture to which cases should be added - - - - Method to create a test case from a MethodInfo and add - it to the fixture being built. It first checks to see if - any global TestCaseBuilder addin wants to build the - test case. If not, it uses the internal builder - collection maintained by this fixture builder. - - The default implementation has no test case builders. - Derived classes should add builders to the collection - in their constructor. - - The method for which a test is to be created - The test suite being built. - A newly constructed Test - - - - PairwiseStrategy creates test cases by combining the parameter - data so that all possible pairs of data items are used. - - - - The number of test cases that cover all possible pairs of test function - parameters values is significantly less than the number of test cases - that cover all possible combination of test function parameters values. - And because different studies show that most of software failures are - caused by combination of no more than two parameters, pairwise testing - can be an effective ways to test the system when it's impossible to test - all combinations of parameters. - - - The PairwiseStrategy code is based on "jenny" tool by Bob Jenkins: - http://burtleburtle.net/bob/math/jenny.html - - - - - - Gets the test cases generated by this strategy instance. - - A set of test cases. - - - - FleaRand is a pseudo-random number generator developed by Bob Jenkins: - http://burtleburtle.net/bob/rand/talksmall.html#flea - - - - - Initializes a new instance of the FleaRand class. - - The seed. - - - - FeatureInfo represents coverage of a single value of test function - parameter, represented as a pair of indices, Dimension and Feature. In - terms of unit testing, Dimension is the index of the test parameter and - Feature is the index of the supplied value in that parameter's list of - sources. - - - - - Initializes a new instance of FeatureInfo class. - - Index of a dimension. - Index of a feature. - - - - A FeatureTuple represents a combination of features, one per test - parameter, which should be covered by a test case. In the - PairwiseStrategy, we are only trying to cover pairs of features, so the - tuples actually may contain only single feature or pair of features, but - the algorithm itself works with triplets, quadruples and so on. - - - - - Initializes a new instance of FeatureTuple class for a single feature. - - Single feature. - - - - Initializes a new instance of FeatureTuple class for a pair of features. - - First feature. - Second feature. - - - - TestCase represents a single test case covering a list of features. - - - - - Initializes a new instance of TestCaseInfo class. - - A number of features in the test case. - - - - PairwiseTestCaseGenerator class implements an algorithm which generates - a set of test cases which covers all pairs of possible values of test - function. - - - - The algorithm starts with creating a set of all feature tuples which we - will try to cover (see method). This set - includes every single feature and all possible pairs of features. We - store feature tuples in the 3-D collection (where axes are "dimension", - "feature", and "all combinations which includes this feature"), and for - every two feature (e.g. "A" and "B") we generate both ("A", "B") and - ("B", "A") pairs. This data structure extremely reduces the amount of - time needed to calculate coverage for a single test case (this - calculation is the most time-consuming part of the algorithm). - - - Then the algorithm picks one tuple from the uncovered tuple, creates a - test case that covers this tuple, and then removes this tuple and all - other tuples covered by this test case from the collection of uncovered - tuples. - - - Picking a tuple to cover - - - There are no any special rules defined for picking tuples to cover. We - just pick them one by one, in the order they were generated. - - - Test generation - - - Test generation starts from creating a completely random test case which - covers, nevertheless, previously selected tuple. Then the algorithm - tries to maximize number of tuples which this test covers. - - - Test generation and maximization process repeats seven times for every - selected tuple and then the algorithm picks the best test case ("seven" - is a magic number which provides good results in acceptable time). - - Maximizing test coverage - - To maximize tests coverage, the algorithm walks thru the list of mutable - dimensions (mutable dimension is a dimension that are not included in - the previously selected tuple). Then for every dimension, the algorithm - walks thru the list of features and checks if this feature provides - better coverage than randomly selected feature, and if yes keeps this - feature. - - - This process repeats while it shows progress. If the last iteration - doesn't improve coverage, the process ends. - - - In addition, for better results, before start every iteration, the - algorithm "scrambles" dimensions - so for every iteration dimension - probes in a different order. - - - - - - Creates a set of test cases for specified dimensions. - - - An array which contains information about dimensions. Each element of - this array represents a number of features in the specific dimension. - - - A set of test cases. - - - - - ParameterDataProvider supplies individual argument _values for - single parameters using attributes derived from DataAttribute. - - - - - Determine whether any data is available for a parameter. - - A ParameterInfo representing one - argument to a parameterized test - - True if any data is available, otherwise false. - - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - An IParameterInfo representing one - argument to a parameterized test - - An IEnumerable providing the required data - - - - - SequentialStrategy creates test cases by using all of the - parameter data sources in parallel, substituting null - when any of them run out of data. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - The CommandStage enumeration represents the defined stages - of execution for a series of TestCommands. The int _values - of the enum are used to apply decorators in the proper - order. Lower _values are applied first and are therefore - "closer" to the actual test execution. - - - No CommandStage is defined for actual invocation of the test or - for creation of the context. Execution may be imagined as - proceeding from the bottom of the list upwards, with cleanup - after the test running in the opposite order. - - - - - Use an application-defined default value. - - - - - Make adjustments needed before and after running - the raw test - that is, after any SetUp has run - and before TearDown. - - - - - Run SetUp and TearDown for the test. This stage is used - internally by NUnit and should not normally appear - in user-defined decorators. - - - - - Make adjustments needed before and after running - the entire test - including SetUp and TearDown. - - - - - Objects implementing this interface are used to wrap - the TestMethodCommand itself. They apply after SetUp - has been run and before TearDown. - - - - - CultureDetector is a helper class used by NUnit to determine - whether a test should be run based on the current culture. - - - - - Default constructor uses the current culture. - - - - - Construct a CultureDetector for a particular culture for testing. - - The culture to be used - - - - Test to determine if one of a collection of cultures - is being used currently. - - - - - - - Tests to determine if the current culture is supported - based on a culture attribute. - - The attribute to examine - - - - - Test to determine if the a particular culture or comma- - delimited set of cultures is in use. - - Name of the culture or comma-separated list of culture ids - True if the culture is in use on the system - - - - Return the last failure reason. Results are not - defined if called before IsSupported( Attribute ) - is called. - - - - - DefaultTestAssemblyBuilder loads a single assembly and builds a TestSuite - containing test fixtures present in the assembly. - - - - - The default suite builder used by the test assembly builder. - - - - - Initializes a new instance of the class. - - - - - Build a suite of tests from a provided assembly - - The assembly from which tests are to be built - A dictionary of options to use in building the suite - - A TestSuite containing the tests found in the assembly - - - - - Build a suite of tests given the filename of an assembly - - The filename of the assembly from which tests are to be built - A dictionary of options to use in building the suite - - A TestSuite containing the tests found in the assembly - - - - - ExceptionHelper provides static methods for working with exceptions - - - - - Builds up a message, using the Message field of the specified exception - as well as any InnerExceptions. - - The exception. - A combined message string. - - - - Builds up a message, using the Message field of the specified exception - as well as any InnerExceptions. - - The exception. - A combined stack trace. - - - - Gets the stack trace of the exception. - - The exception. - A string representation of the stack trace. - - - - Combines multiple filters so that a test must pass all - of them in order to pass this filter. - - - - - Constructs an empty AndFilter - - - - - Constructs an AndFilter from an array of filters - - - - - - Checks whether the AndFilter is matched by a test - - The test to be matched - True if all the component filters pass, otherwise false - - - - Checks whether the AndFilter is matched by a test - - The test to be matched - True if all the component filters match, otherwise false - - - - Gets the element name - - Element name - - - - CategoryFilter is able to select or exclude tests - based on their categories. - - - - - - Construct a CategoryFilter using a single category name - - A category name - - - - Check whether the filter matches a test - - The test to be matched - - - - - Gets the element name - - Element name - - - - NotFilter negates the operation of another filter - - - - - Construct a not filter on another filter - - The filter to be negated - - - - Determine if a particular test passes the filter criteria. The default - implementation checks the test itself, its parents and any descendants. - - Derived classes may override this method or any of the Match methods - to change the behavior of the filter. - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Check whether the filter matches a test - - The test to be matched - True if it matches, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Gets the base filter - - - - - Combines multiple filters so that a test must pass one - of them in order to pass this filter. - - - - - Constructs an empty OrFilter - - - - - Constructs an AndFilter from an array of filters - - - - - - Checks whether the OrFilter is matched by a test - - The test to be matched - True if any of the component filters pass, otherwise false - - - - Checks whether the OrFilter is matched by a test - - The test to be matched - True if any of the component filters match, otherwise false - - - - Gets the element name - - Element name - - - - FullName filter selects tests based on their FullName - - - - - Construct a FullNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - InvalidTestFixtureException is thrown when an appropriate test - fixture constructor using the provided arguments cannot be found. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The message. - - - - Initializes a new instance of the class. - - The message. - The inner. - - - - Serialization Constructor - - - - - Thrown when an assertion failed. Here to preserve the inner - exception and hence its stack trace. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The error message that explains - the reason for the exception - - - - Initializes a new instance of the class. - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - OSPlatform represents a particular operating system platform - - - - - Platform ID for Unix as defined by Microsoft .NET 2.0 and greater - - - - - Platform ID for Unix as defined by Mono - - - - - Platform ID for XBox as defined by .NET and Mono, but not CF - - - - - Platform ID for MacOSX as defined by .NET and Mono, but not CF - - - - - Gets the actual OS Version, not the incorrect value that might be - returned for Win 8.1 and Win 10 - - - If an application is not manifested as Windows 8.1 or Windows 10, - the version returned from Environment.OSVersion will not be 6.3 and 10.0 - respectively, but will be 6.2 and 6.3. The correct value can be found in - the registry. - - The original version - The correct OS version - - - - Construct from a platform ID and version - - - - - Construct from a platform ID, version and product type - - - - - Get the OSPlatform under which we are currently running - - - - - Get the platform ID of this instance - - - - - Get the Version of this instance - - - - - Get the Product Type of this instance - - - - - Return true if this is a windows platform - - - - - Return true if this is a Unix or Linux platform - - - - - Return true if the platform is Win32S - - - - - Return true if the platform is Win32Windows - - - - - Return true if the platform is Win32NT - - - - - Return true if the platform is Windows CE - - - - - Return true if the platform is Xbox - - - - - Return true if the platform is MacOSX - - - - - Return true if the platform is Windows 95 - - - - - Return true if the platform is Windows 98 - - - - - Return true if the platform is Windows ME - - - - - Return true if the platform is NT 3 - - - - - Return true if the platform is NT 4 - - - - - Return true if the platform is NT 5 - - - - - Return true if the platform is Windows 2000 - - - - - Return true if the platform is Windows XP - - - - - Return true if the platform is Windows 2003 Server - - - - - Return true if the platform is NT 6 - - - - - Return true if the platform is NT 6.0 - - - - - Return true if the platform is NT 6.1 - - - - - Return true if the platform is NT 6.2 - - - - - Return true if the platform is NT 6.3 - - - - - Return true if the platform is Vista - - - - - Return true if the platform is Windows 2008 Server (original or R2) - - - - - Return true if the platform is Windows 2008 Server (original) - - - - - Return true if the platform is Windows 2008 Server R2 - - - - - Return true if the platform is Windows 2012 Server (original or R2) - - - - - Return true if the platform is Windows 2012 Server (original) - - - - - Return true if the platform is Windows 2012 Server R2 - - - - - Return true if the platform is Windows 7 - - - - - Return true if the platform is Windows 8 - - - - - Return true if the platform is Windows 8.1 - - - - - Return true if the platform is Windows 10 - - - - - Return true if the platform is Windows Server. This is named Windows - Server 10 to distinguish it from previous versions of Windows Server. - - - - - Product Type Enumeration used for Windows - - - - - Product type is unknown or unspecified - - - - - Product type is Workstation - - - - - Product type is Domain Controller - - - - - Product type is Server - - - - - The TestCaseParameters class encapsulates method arguments and - other selected parameters needed for constructing - a parameterized test case. - - - - - The expected result to be returned - - - - - Default Constructor creates an empty parameter set - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a ParameterSet from an object implementing ITestCaseData - - - - - - The expected result of the test, which - must match the method return type. - - - - - Gets a value indicating whether an expected result was specified. - - - - - PlatformHelper class is used by the PlatformAttribute class to - determine whether a platform is supported. - - - - - Comma-delimited list of all supported OS platform constants - - - - - Comma-delimited list of all supported Runtime platform constants - - - - - Default constructor uses the operating system and - common language runtime of the system. - - - - - Construct a PlatformHelper for a particular operating - system and common language runtime. Used in testing. - - OperatingSystem to be used - RuntimeFramework to be used - - - - Test to determine if one of a collection of platforms - is being used currently. - - - - - - - Tests to determine if the current platform is supported - based on a platform attribute. - - The attribute to examine - - - - - Tests to determine if the current platform is supported - based on a platform attribute. - - The attribute to examine - - - - - Test to determine if the a particular platform or comma- - delimited set of platforms is in use. - - Name of the platform or comma-separated list of platform ids - True if the platform is in use on the system - - - - Return the last failure reason. Results are not - defined if called before IsSupported( Attribute ) - is called. - - - - - A PropertyBag represents a collection of name value pairs - that allows duplicate entries with the same key. Methods - are provided for adding a new pair as well as for setting - a key to a single value. All keys are strings but _values - may be of any type. Null _values are not permitted, since - a null entry represents the absence of the key. - - - - - Adds a key/value pair to the property set - - The key - The value - - - - Sets the value for a key, removing any other - _values that are already in the property set. - - - - - - - Gets a single value for a key, using the first - one if multiple _values are present and returning - null if the value is not found. - - - - - - - Gets a flag indicating whether the specified key has - any entries in the property set. - - The key to be checked - - True if their are _values present, otherwise false - - - - - Returns an XmlNode representating the current PropertyBag. - - Not used - An XmlNode representing the PropertyBag - - - - Returns an XmlNode representing the PropertyBag after - adding it as a child of the supplied parent node. - - The parent node. - Not used - - - - - Gets a collection containing all the keys in the property set - - - - - - Gets or sets the list of _values for a particular key - - - - - Helper methods for inspecting a type by reflection. - - Many of these methods take ICustomAttributeProvider as an - argument to avoid duplication, even though certain attributes can - only appear on specific types of members, like MethodInfo or Type. - - In the case where a type is being examined for the presence of - an attribute, interface or named member, the Reflect methods - operate with the full name of the member being sought. This - removes the necessity of the caller having a reference to the - assembly that defines the item being sought and allows the - NUnit core to inspect assemblies that reference an older - version of the NUnit framework. - - - - - Examine a fixture type and return an array of methods having a - particular attribute. The array is order with base methods first. - - The type to examine - The attribute Type to look for - Specifies whether to search the fixture type inheritance chain - The array of methods found - - - - Examine a fixture type and return true if it has a method with - a particular attribute. - - The type to examine - The attribute Type to look for - True if found, otherwise false - - - - Invoke the default constructor on a Type - - The Type to be constructed - An instance of the Type - - - - Invoke a constructor on a Type with arguments - - The Type to be constructed - Arguments to the constructor - An instance of the Type - - - - Returns an array of types from an array of objects. - Used because the compact framework doesn't support - Type.GetTypeArray() - - An array of objects - An array of Types - - - - Invoke a parameterless method returning void on an object. - - A MethodInfo for the method to be invoked - The object on which to invoke the method - - - - Invoke a method, converting any TargetInvocationException to an NUnitException. - - A MethodInfo for the method to be invoked - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - The TestResult class represents the result of a test. - - - - - The minimum duration for tests - - - - - Error message for when child tests have errors - - - - - Error message for when child tests are ignored - - - - - List of child results - - - - - Construct a test result given a Test - - The test to be used - - - - Returns the Xml representation of the result. - - If true, descendant results are included - An XmlNode representing the result - - - - Adds the XML representation of the result as a child of the - supplied parent node.. - - The parent node. - If true, descendant results are included - - - - - Adds a child result to this result, setting this result's - ResultState to Failure if the child result failed. - - The result to be added - - - - Set the result of the test - - The ResultState to use in the result - - - - Set the result of the test - - The ResultState to use in the result - A message associated with the result state - - - - Set the result of the test - - The ResultState to use in the result - A message associated with the result state - Stack trace giving the location of the command - - - - Set the test result based on the type of exception thrown - - The exception that was thrown - - - - Set the test result based on the type of exception thrown - - The exception that was thrown - THe FailureSite to use in the result - - - - RecordTearDownException appends the message and stacktrace - from an exception arising during teardown of the test - to any previously recorded information, so that any - earlier failure information is not lost. Note that - calling Assert.Ignore, Assert.Inconclusive, etc. during - teardown is treated as an error. If the current result - represents a suite, it may show a teardown error even - though all contained tests passed. - - The Exception to be recorded - - - - Adds a reason element to a node and returns it. - - The target node. - The new reason element. - - - - Adds a failure element to a node and returns it. - - The target node. - The new failure element. - - - - Gets the test with which this result is associated. - - - - - Gets the ResultState of the test result, which - indicates the success or failure of the test. - - - - - Gets the name of the test result - - - - - Gets the full name of the test result - - - - - Gets or sets the elapsed time for running the test in seconds - - - - - Gets or sets the time the test started running. - - - - - Gets or sets the time the test finished running. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. - - - - - Gets or sets the count of asserts executed - when running the test. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Indicates whether this result has any child results. - Test HasChildren before accessing Children to avoid - the creation of an empty collection. - - - - - Gets the collection of child results. - - - - - Gets a TextWriter, which will write output to be included in the result. - - - - - Gets any text output written to this result. - - - - - Enumeration identifying a common language - runtime implementation. - - - - Any supported runtime framework - - - Microsoft .NET Framework - - - Microsoft .NET Compact Framework - - - Microsoft Shared Source CLI - - - Mono - - - Silverlight - - - MonoTouch - - - - RuntimeFramework represents a particular version - of a common language runtime implementation. - - - - - DefaultVersion is an empty Version, used to indicate that - NUnit should select the CLR version to use for the test. - - - - - Construct from a runtime type and version. If the version has - two parts, it is taken as a framework version. If it has three - or more, it is taken as a CLR version. In either case, the other - version is deduced based on the runtime type and provided version. - - The runtime type of the framework - The version of the framework - - - - Parses a string representing a RuntimeFramework. - The string may be just a RuntimeType name or just - a Version or a hyphenated RuntimeType-Version or - a Version prefixed by 'versionString'. - - - - - - - Overridden to return the short name of the framework - - - - - - Returns true if the current framework matches the - one supplied as an argument. Two frameworks match - if their runtime types are the same or either one - is RuntimeType.Any and all specified version components - are equal. Negative (i.e. unspecified) version - components are ignored. - - The RuntimeFramework to be matched. - True on match, otherwise false - - - - Static method to return a RuntimeFramework object - for the framework that is currently in use. - - - - - The type of this runtime framework - - - - - The framework version for this runtime framework - - - - - The CLR version for this runtime framework - - - - - Return true if any CLR version may be used in - matching this RuntimeFramework object. - - - - - Returns the Display name for this framework - - - - - Helper class used to save and restore certain static or - singleton settings in the environment that affect tests - or which might be changed by the user tests. - - An internal class is used to hold settings and a stack - of these objects is pushed and popped as Save and Restore - are called. - - - - - Link to a prior saved context - - - - - Indicates that a stop has been requested - - - - - The event listener currently receiving notifications - - - - - The number of assertions for the current test - - - - - The current culture - - - - - The current UI culture - - - - - The current test result - - - - - The current Principal. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - An existing instance of TestExecutionContext. - - - - The current context, head of the list of saved contexts. - - - - - Get the current context or return null if none is found. - - - - - Clear the current context. This is provided to - prevent "leakage" of the CallContext containing - the current context back to any runners. - - - - - Record any changes in the environment made by - the test code in the execution context so it - will be passed on to lower level tests. - - - - - Set up the execution environment to match a context. - Note that we may be running on the same thread where the - context was initially created or on a different thread. - - - - - Increments the assert count by one. - - - - - Increments the assert count by a specified amount. - - - - - Obtain lifetime service object - - - - - - Gets the current context. - - The current context. - - - - Gets or sets the current test - - - - - The time the current test started execution - - - - - The time the current test started in Ticks - - - - - Gets or sets the current test result - - - - - Gets a TextWriter that will send output to the current test result. - - - - - The current test object - that is the user fixture - object on which tests are being executed. - - - - - Get or set the working directory - - - - - Get or set indicator that run should stop on the first error - - - - - Gets an enum indicating whether a stop has been requested. - - - - - The current test event listener - - - - - The current WorkItemDispatcher - - - - - The ParallelScope to be used by tests running in this context. - For builds with out the parallel feature, it has no effect. - - - - - Gets the RandomGenerator specific to this Test - - - - - Gets the assert count. - - The assert count. - - - - Gets or sets the test case timeout value - - - - - Gets a list of ITestActions set by upstream tests - - - - - Saves or restores the CurrentCulture - - - - - Saves or restores the CurrentUICulture - - - - - Gets or sets the current for the Thread. - - - - - TestListener provides an implementation of ITestListener that - does nothing. It is used only through its NULL property. - - - - - Called when a test has just started - - The test that is starting - - - - Called when a test case has finished - - The result of the test - - - - Construct a new TestListener - private so it may not be used. - - - - - Get a listener that does nothing - - - - - TestProgressReporter translates ITestListener events into - the async callbacks that are used to inform the client - software about the progress of a test run. - - - - - Initializes a new instance of the class. - - The callback handler to be used for reporting progress. - - - - Called when a test has just started - - The test that is starting - - - - Called when a test has finished. Sends a result summary to the callback. - to - - The result of the test - - - - Returns the parent test item for the targer test item if it exists - - - parent test item - - - - Makes a string safe for use as an attribute, replacing - characters characters that can't be used with their - corresponding xml representations. - - The string to be used - A new string with the _values replaced - - - - ParameterizedFixtureSuite serves as a container for the set of test - fixtures created from a given Type using various parameters. - - - - - TestSuite represents a composite test, which contains other tests. - - - - - The Test abstract class represents a test within the framework. - - - - - Static value to seed ids. It's started at 1000 so any - uninitialized ids will stand out. - - - - - The SetUp methods. - - - - - The teardown methods - - - - - Constructs a test given its name - - The name of the test - - - - Constructs a test given the path through the - test hierarchy to its parent and a name. - - The parent tests full name - The name of the test - - - - TODO: Documentation needed for constructor - - - - - - Construct a test from a MethodInfo - - - - - - Creates a TestResult for this test. - - A TestResult suitable for this type of test. - - - - Modify a newly constructed test by applying any of NUnit's common - attributes, based on a supplied ICustomAttributeProvider, which is - usually the reflection element from which the test was constructed, - but may not be in some instances. The attributes retrieved are - saved for use in subsequent operations. - - An object implementing ICustomAttributeProvider - - - - Add standard attributes and members to a test node. - - - - - - - Returns the Xml representation of the test - - If true, include child tests recursively - - - - - Returns an XmlNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Compares this test to another test for sorting purposes - - The other test - Value of -1, 0 or +1 depending on whether the current test is less than, equal to or greater than the other test - - - - Gets or sets the id of the test - - - - - - Gets or sets the name of the test - - - - - Gets or sets the fully qualified name of the test - - - - - - Gets the name of the class containing this test. Returns - null if the test is not associated with a class. - - - - - Gets the name of the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the TypeInfo of the fixture used in running this test - or null if no fixture type is associated with it. - - - - - Gets a MethodInfo for the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Whether or not the test should be run - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Gets a string representing the type of test. Used as an attribute - value in the XML representation of a test and has no other - function in the framework. - - - - - Gets a count of test cases represented by - or contained under this test. - - - - - Gets the properties for this test - - - - - Returns true if this is a TestSuite - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets the parent as a Test object. - Used by the core to set the parent. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets or sets a fixture object for running this test. - - - - - Static prefix used for ids in this AppDomain. - Set by FrameworkController. - - - - - Gets or Sets the Int value representing the seed for the RandomGenerator - - - - - - Our collection of child tests - - - - - Initializes a new instance of the class. - - The name of the suite. - - - - Initializes a new instance of the class. - - Name of the parent suite. - The name of the suite. - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - Sorts tests under this suite. - - - - - Adds a test to the suite. - - The test. - - - - Overridden to return a TestSuiteResult. - - A TestResult for this test. - - - - Returns an XmlNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Check that setup and teardown methods marked by certain attributes - meet NUnit's requirements and mark the tests not runnable otherwise. - - The attribute type to check for - - - - Gets this test's child tests - - The list of child tests - - - - Gets a count of test cases represented by - or contained under this test. - - - - - - The arguments to use in creating the fixture - - - - - Set to true to suppress sorting this suite's contents - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Initializes a new instance of the class. - - The ITypeInfo for the type that represents the suite. - - - - Gets a string representing the type of test - - - - - - ParameterizedMethodSuite holds a collection of individual - TestMethods with their arguments applied. - - - - - Construct from a MethodInfo - - - - - - Gets a string representing the type of test - - - - - - SetUpFixture extends TestSuite and supports - Setup and TearDown methods. - - - - - Initializes a new instance of the class. - - The type. - - - - TestAssembly is a TestSuite that represents the execution - of tests in a managed assembly. - - - - - Initializes a new instance of the class - specifying the Assembly and the path from which it was loaded. - - The assembly this test represents. - The path used to load the assembly. - - - - Initializes a new instance of the class - for a path which could not be loaded. - - The path used to load the assembly. - - - - Gets the Assembly represented by this instance. - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - TestFixture is a surrogate for a user test fixture class, - containing one or more tests. - - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - The TestMethod class represents a Test implemented as a method. - - - - - The ParameterSet used to create this test method - - - - - Initializes a new instance of the class. - - The method to be used as a test. - - - - Initializes a new instance of the class. - - The method to be used as a test. - The suite or fixture to which the new test will be added - - - - Overridden to return a TestCaseResult. - - A TestResult for this test. - - - - Returns a TNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Returns the name of the method - - - - - ThreadUtility provides a set of static methods convenient - for working with threads. - - - - - Do our best to Kill a thread - - The thread to kill - - - - Do our best to kill a thread, passing state info - - The thread to kill - Info for the ThreadAbortException handler - - - - TypeHelper provides static methods that operate on Types. - - - - - A special value, which is used to indicate that BestCommonType() method - was unable to find a common type for the specified arguments. - - - - - Gets the display name for a Type as used by NUnit. - - The Type for which a display name is needed. - The display name for the Type - - - - Gets the display name for a Type as used by NUnit. - - The Type for which a display name is needed. - The arglist provided. - The display name for the Type - - - - Returns the best fit for a common type to be used in - matching actual arguments to a methods Type parameters. - - The first type. - The second type. - Either type1 or type2, depending on which is more general. - - - - Determines whether the specified type is numeric. - - The type to be examined. - - true if the specified type is numeric; otherwise, false. - - - - - Convert an argument list to the required parameter types. - Currently, only widening numeric conversions are performed. - - An array of args to be converted - A ParameterInfo[] whose types will be used as targets - - - - Determines whether this instance can deduce type args for a generic type from the supplied arguments. - - The type to be examined. - The arglist. - The type args to be used. - - true if this the provided args give sufficient information to determine the type args to be used; otherwise, false. - - - - - Gets the _values for an enumeration, using Enum.GetTypes - where available, otherwise through reflection. - - - - - - - Gets the ids of the _values for an enumeration, - using Enum.GetNames where available, otherwise - through reflection. - - - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable to the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable to the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that tests whether the path provided - is the same as an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is a subpath of the expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the actual value falls - inclusively within a specified range. - - from must be less than or equal to true - Inclusive beginning of the range. Must be less than or equal to to. - Inclusive end of the range. Must be greater than or equal to from. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether an object graph is serializable in binary format. - - - - - Returns a constraint that tests whether an object graph is serializable in xml format. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - The Iz class is a synonym for Is intended for use in VB, - which regards Is as a keyword. - - - - - The List class is a helper class with properties and methods - that supply a number of constraints used with lists and collections. - - - - - List.Map returns a ListMapper, which can be used to map - the original collection to another collection. - - - - - - - ListMapper is used to transform a collection used as an actual argument - producing another collection to be used in the assertion. - - - - - Construct a ListMapper based on a collection - - The collection to be transformed - - - - Produces a collection containing all the _values of a property - - The collection of property _values - - - - - The SpecialValue enum is used to represent TestCase arguments - that cannot be used as arguments to an Attribute. - - - - - Null represents a null value, which cannot be used as an - argument to an attriute under .NET 1.x - - - - - Basic Asserts on strings. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - - - - Asserts that a string is not found within another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - - - - Asserts that two strings are not equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that two strings are not equal, without regard to case. - - The expected string - The actual string - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - - - - The TestCaseData class represents a set of arguments - and other parameter info to be used for a parameterized - test case. It is derived from TestCaseParameters and adds a - fluent syntax for use in initializing the test case. - - - - - Initializes a new instance of the class. - - The arguments. - - - - Initializes a new instance of the class. - - The argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - The third argument. - - - - Sets the expected result for the test - - The expected result - A modified TestCaseData - - - - Sets the name of the test case - - The modified TestCaseData instance - - - - Sets the description for the test case - being constructed. - - The description. - The modified TestCaseData instance. - - - - Applies a category to the test - - - - - - - Applies a named property to the test - - - - - - - - Applies a named property to the test - - - - - - - - Applies a named property to the test - - - - - - - - Marks the test case as explicit. - - - - - Marks the test case as explicit, specifying the reason. - - - - - Ignores this TestCase, specifying the reason. - - The reason. - - - - - Provide the context information of the current test. - This is an adapter for the internal ExecutionContext - class, hiding the internals from the user test. - - - - - Construct a TestContext for an ExecutionContext - - The ExecutionContext to adapt - - - Write the string representation of a boolean value to the current result - - - Write a char to the current result - - - Write a char array to the current result - - - Write the string representation of a double to the current result - - - Write the string representation of an Int32 value to the current result - - - Write the string representation of an Int64 value to the current result - - - Write the string representation of a decimal value to the current result - - - Write the string representation of an object to the current result - - - Write the string representation of a Single value to the current result - - - Write a string to the current result - - - Write the string representation of a UInt32 value to the current result - - - Write the string representation of a UInt64 value to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a line terminator to the current result - - - Write the string representation of a boolean value to the current result followed by a line terminator - - - Write a char to the current result followed by a line terminator - - - Write a char array to the current result followed by a line terminator - - - Write the string representation of a double to the current result followed by a line terminator - - - Write the string representation of an Int32 value to the current result followed by a line terminator - - - Write the string representation of an Int64 value to the current result followed by a line terminator - - - Write the string representation of a decimal value to the current result followed by a line terminator - - - Write the string representation of an object to the current result followed by a line terminator - - - Write the string representation of a Single value to the current result followed by a line terminator - - - Write a string to the current result followed by a line terminator - - - Write the string representation of a UInt32 value to the current result followed by a line terminator - - - Write the string representation of a UInt64 value to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - - Get the current test context. This is created - as needed. The user may save the context for - use within a test, but it should not be used - outside the test for which it is created. - - - - - Gets a TextWriter that will send output to the current test result. - - - - - Get a representation of the current test. - - - - - Gets a Representation of the TestResult for the current test. - - - - - Gets the directory containing the current test assembly. - - - - - Gets the directory to be used for outputting files created - by this test run. - - - - - Gets the random generator. - - - The random generator. - - - - - TestAdapter adapts a Test for consumption by - the user test code. - - - - - Construct a TestAdapter for a Test - - The Test to be adapted - - - - Gets the unique Id of a test - - - - - The name of the test, which may or may not be - the same as the method name. - - - - - The name of the method representing the test. - - - - - The FullName of the test - - - - - The ClassName of the test - - - - - The properties of the test. - - - - - ResultAdapter adapts a TestResult for consumption by - the user test code. - - - - - Construct a ResultAdapter for a TestResult - - The TestResult to be adapted - - - - Gets a ResultState representing the outcome of the test. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Helper class with properties and methods that supply - constraints that operate on exceptions. - - - - - Creates a constraint specifying the exact type of exception expected - - - - - Creates a constraint specifying the exact type of exception expected - - - - - Creates a constraint specifying the type of exception expected - - - - - Creates a constraint specifying the type of exception expected - - - - - Creates a constraint specifying an expected exception - - - - - Creates a constraint specifying an exception with a given InnerException - - - - - Creates a constraint specifying an expected TargetInvocationException - - - - - Creates a constraint specifying an expected ArgumentException - - - - - Creates a constraint specifying an expected ArgumentNUllException - - - - - Creates a constraint specifying an expected InvalidOperationException - - - - - Creates a constraint specifying that no exception is thrown - - - - - Represents the result of running a single test case. - - - - - Construct a TestCaseResult based on a TestMethod - - A TestMethod to which the result applies. - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Represents the result of running a test suite - - - - - Construct a TestSuiteResult base on a TestSuite - - The TestSuite to which the result applies - - - - Add a child result - - The child result to be added - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - ExactCountConstraint applies another constraint to each - item in a collection, succeeding only if a specified - number of items succeed. - - - - - Construct an ExactCountConstraint on top of an existing constraint - - - - - - - Apply the item constraint to each item in the collection, - succeeding only if the expected number of items pass. - - - - - - - Represents a constraint that succeeds if the specified - count of members of a collection match a base constraint. - - - - - Construct an ExactCountOperator for a specified count - - The expected count - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - none of them succeed. - - - - - ExceptionTypeConstraint is a special version of ExactTypeConstraint - used to provided detailed info about the exception thrown in - an error message. - - - - - Constructs an ExceptionTypeConstraint - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - diff --git a/packages/NUnit.3.0.0/lib/net40/nunit.framework.dll b/packages/NUnit.3.0.0/lib/net40/nunit.framework.dll deleted file mode 100644 index e21641c31..000000000 Binary files a/packages/NUnit.3.0.0/lib/net40/nunit.framework.dll and /dev/null differ diff --git a/packages/NUnit.3.0.0/lib/net40/nunit.framework.xml b/packages/NUnit.3.0.0/lib/net40/nunit.framework.xml deleted file mode 100644 index 638c23867..000000000 --- a/packages/NUnit.3.0.0/lib/net40/nunit.framework.xml +++ /dev/null @@ -1,16852 +0,0 @@ - - - - nunit.framework - - - - - AssemblyHelper provides static methods for working - with assemblies. - - - - - Gets the path from which the assembly defining a type was loaded. - - The Type. - The path. - - - - Gets the path from which an assembly was loaded. - - The assembly. - The path. - - - - Gets the path to the directory from which an assembly was loaded. - - The assembly. - The path. - - - - Gets the AssemblyName of an assembly. - - The assembly - An AssemblyName - - - - Loads an assembly given a string, which may be the - path to the assembly or the AssemblyName - - - - - - - Gets the assembly path from code base. - - Public for testing purposes - The code base. - - - - - Env is a static class that provides some of the features of - System.Environment that are not available under all runtimes - - - - - The newline sequence in the current environment. - - - - - Path to the 'My Documents' folder - - - - - Directory used for file output if not specified on commandline. - - - - - Class used to guard against unexpected argument values - or operations by throwing an appropriate exception. - - - - - Throws an exception if an argument is null - - The value to be tested - The name of the argument - - - - Throws an exception if a string argument is null or empty - - The value to be tested - The name of the argument - - - - Throws an ArgumentOutOfRangeException if the specified condition is not met. - - The condition that must be met - The exception message to be used - The name of the argument - - - - Throws an ArgumentException if the specified condition is not met. - - The condition that must be met - The exception message to be used - The name of the argument - - - - Throws an InvalidOperationException if the specified condition is not met. - - The condition that must be met - The exception message to be used - - - - Interface for logging within the engine - - - - - Logs the specified message at the error level. - - The message. - - - - Logs the specified message at the error level. - - The message. - The arguments. - - - - Logs the specified message at the warning level. - - The message. - - - - Logs the specified message at the warning level. - - The message. - The arguments. - - - - Logs the specified message at the info level. - - The message. - - - - Logs the specified message at the info level. - - The message. - The arguments. - - - - Logs the specified message at the debug level. - - The message. - - - - Logs the specified message at the debug level. - - The message. - The arguments. - - - - InternalTrace provides facilities for tracing the execution - of the NUnit framework. Tests and classes under test may make use - of Console writes, System.Diagnostics.Trace or various loggers and - NUnit itself traps and processes each of them. For that reason, a - separate internal trace is needed. - - Note: - InternalTrace uses a global lock to allow multiple threads to write - trace messages. This can easily make it a bottleneck so it must be - used sparingly. Keep the trace Level as low as possible and only - insert InternalTrace writes where they are needed. - TODO: add some buffering and a separate writer thread as an option. - TODO: figure out a way to turn on trace in specific classes only. - - - - - Initialize the internal trace facility using the name of the log - to be written to and the trace level. - - The log name - The trace level - - - - Initialize the internal trace using a provided TextWriter and level - - A TextWriter - The InternalTraceLevel - - - - Get a named Logger - - - - - - Get a logger named for a particular Type. - - - - - Gets a flag indicating whether the InternalTrace is initialized - - - - - InternalTraceLevel is an enumeration controlling the - level of detailed presented in the internal log. - - - - - Use the default settings as specified by the user. - - - - - Do not display any trace messages - - - - - Display Error messages only - - - - - Display Warning level and higher messages - - - - - Display informational and higher messages - - - - - Display debug messages and higher - i.e. all messages - - - - - Display debug messages and higher - i.e. all messages - - - - - A trace listener that writes to a separate file per domain - and process using it. - - - - - Construct an InternalTraceWriter that writes to a file. - - Path to the file to use - - - - Construct an InternalTraceWriter that writes to a - TextWriter provided by the caller. - - - - - - Writes a character to the text string or stream. - - The character to write to the text stream. - - - - Writes a string to the text string or stream. - - The string to write. - - - - Writes a string followed by a line terminator to the text string or stream. - - The string to write. If is null, only the line terminator is written. - - - - Releases the unmanaged resources used by the and optionally releases the managed resources. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - Clears all buffers for the current writer and causes any buffered data to be written to the underlying device. - - - - - Returns the character encoding in which the output is written. - - The character encoding in which the output is written. - - - - Provides internal logging to the NUnit framework - - - - - Initializes a new instance of the class. - - The name. - The log level. - The writer where logs are sent. - - - - Logs the message at error level. - - The message. - - - - Logs the message at error level. - - The message. - The message arguments. - - - - Logs the message at warm level. - - The message. - - - - Logs the message at warning level. - - The message. - The message arguments. - - - - Logs the message at info level. - - The message. - - - - Logs the message at info level. - - The message. - The message arguments. - - - - Logs the message at debug level. - - The message. - - - - Logs the message at debug level. - - The message. - The message arguments. - - - - PackageSettings is a static class containing constant values that - are used as keys in setting up a TestPackage. These values are used in - the engine and framework. Setting values may be a string, int or bool. - - - - - Flag (bool) indicating whether tests are being debugged. - - - - - The InternalTraceLevel for this run. Values are: "Default", - "Off", "Error", "Warning", "Info", "Debug", "Verbose". - Default is "Off". "Debug" and "Verbose" are synonyms. - - - - - Full path of the directory to be used for work and result files. - This path is provided to tests by the frameowrk TestContext. - - - - - The name of the config to use in loading a project. - If not specified, the first config found is used. - - - - - Bool indicating whether the engine should determine the private - bin path by examining the paths to all the tests. Defaults to - true unless PrivateBinPath is specified. - - - - - The ApplicationBase to use in loading the tests. If not - specified, and each assembly has its own process, then the - location of the assembly is used. For multiple assemblies - in a single process, the closest common root directory is used. - - - - - Path to the config file to use in running the tests. - - - - - Bool flag indicating whether a debugger should be launched at agent - startup. Used only for debugging NUnit itself. - - - - - Indicates how to load tests across AppDomains. Values are: - "Default", "None", "Single", "Multiple". Default is "Multiple" - if more than one assembly is loaded in a process. Otherwise, - it is "Single". - - - - - The private binpath used to locate assemblies. Directory paths - is separated by a semicolon. It's an error to specify this and - also set AutoBinPath to true. - - - - - The maximum number of test agents permitted to run simultneously. - Ignored if the ProcessModel is not set or defaulted to Multiple. - - - - - Indicates how to allocate assemblies to processes. Values are: - "Default", "Single", "Separate", "Multiple". Default is "Multiple" - for more than one assembly, "Separate" for a single assembly. - - - - - Indicates the desired runtime to use for the tests. Values - are strings like "net-4.5", "mono-4.0", etc. Default is to - use the target framework for which an assembly was built. - - - - - Bool flag indicating that the test should be run in a 32-bit process - on a 64-bit system. By default, NUNit runs in a 64-bit process on - a 64-bit system. Ignored if set on a 32-bit system. - - - - - Indicates that test runners should be disposed after the tests are executed - - - - - Bool flag indicating that the test assemblies should be shadow copied. - Defaults to false. - - - - - Integer value in milliseconds for the default timeout value - for test cases. If not specified, there is no timeout except - as specified by attributes on the tests themselves. - - - - - A TextWriter to which the internal trace will be sent. - - - - - A list of tests to be loaded. - - - - - The number of test threads to run for the assembly. If set to - 1, a single queue is used. If set to 0, tests are executed - directly, without queuing. - - - - - The random seed to be used for this assembly. If specified - as the value reported from a prior run, the framework should - generate identical random values for tests as were used for - that run, provided that no change has been made to the test - assembly. Default is a random value itself. - - - - - If true, execution stops after the first error or failure. - - - - - If true, use of the event queue is suppressed and test events are synchronous. - - - - - The different targets a test action attribute can be applied to - - - - - Default target, which is determined by where the action attribute is attached - - - - - Target a individual test case - - - - - Target a suite of test cases - - - - - DefaultTestAssemblyBuilder loads a single assembly and builds a TestSuite - containing test fixtures present in the assembly. - - - - - The ITestAssemblyBuilder interface is implemented by a class - that is able to build a suite of tests given an assembly or - an assembly filename. - - - - - Build a suite of tests from a provided assembly - - The assembly from which tests are to be built - A dictionary of options to use in building the suite - A TestSuite containing the tests found in the assembly - - - - Build a suite of tests given the filename of an assembly - - The filename of the assembly from which tests are to be built - A dictionary of options to use in building the suite - A TestSuite containing the tests found in the assembly - - - - The default suite builder used by the test assembly builder. - - - - - Initializes a new instance of the class. - - - - - Build a suite of tests from a provided assembly - - The assembly from which tests are to be built - A dictionary of options to use in building the suite - - A TestSuite containing the tests found in the assembly - - - - - Build a suite of tests given the filename of an assembly - - The filename of the assembly from which tests are to be built - A dictionary of options to use in building the suite - - A TestSuite containing the tests found in the assembly - - - - - FrameworkController provides a facade for use in loading, browsing - and running tests without requiring a reference to the NUnit - framework. All calls are encapsulated in constructors for - this class and its nested classes, which only require the - types of the Common Type System as arguments. - - The controller supports four actions: Load, Explore, Count and Run. - They are intended to be called by a driver, which should allow for - proper sequencing of calls. Load must be called before any of the - other actions. The driver may support other actions, such as - reload on run, by combining these calls. - - - - - A MarshalByRefObject that lives forever - - - - - Obtains a lifetime service object to control the lifetime policy for this instance. - - - - - Construct a FrameworkController using the default builder and runner. - - The AssemblyName or path to the test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - - - - Construct a FrameworkController using the default builder and runner. - - The test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - - - - Construct a FrameworkController, specifying the types to be used - for the runner and builder. This constructor is provided for - purposes of development. - - The full AssemblyName or the path to the test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - The Type of the test runner - The Type of the test builder - - - - Construct a FrameworkController, specifying the types to be used - for the runner and builder. This constructor is provided for - purposes of development. - - The test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - The Type of the test runner - The Type of the test builder - - - - Inserts environment element - - Target node - The new node - - - - Inserts settings element - - Target node - Settings dictionary - The new node - - - - Gets the ITestAssemblyBuilder used by this controller instance. - - The builder. - - - - Gets the ITestAssemblyRunner used by this controller instance. - - The runner. - - - - Gets the AssemblyName or the path for which this FrameworkController was created - - - - - Gets the Assembly for which this - - - - - Gets a dictionary of settings for the FrameworkController - - - - - FrameworkControllerAction is the base class for all actions - performed against a FrameworkController. - - - - - LoadTestsAction loads a test into the FrameworkController - - - - - LoadTestsAction loads the tests in an assembly. - - The controller. - The callback handler. - - - - ExploreTestsAction returns info about the tests in an assembly - - - - - Initializes a new instance of the class. - - The controller for which this action is being performed. - Filter used to control which tests are included (NYI) - The callback handler. - - - - CountTestsAction counts the number of test cases in the loaded TestSuite - held by the FrameworkController. - - - - - Construct a CountsTestAction and perform the count of test cases. - - A FrameworkController holding the TestSuite whose cases are to be counted - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - RunTestsAction runs the loaded TestSuite held by the FrameworkController. - - - - - Construct a RunTestsAction and run all tests in the loaded TestSuite. - - A FrameworkController holding the TestSuite to run - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - RunAsyncAction initiates an asynchronous test run, returning immediately - - - - - Construct a RunAsyncAction and run all tests in the loaded TestSuite. - - A FrameworkController holding the TestSuite to run - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - StopRunAction stops an ongoing run. - - - - - Construct a StopRunAction and stop any ongoing run. If no - run is in process, no error is raised. - - The FrameworkController for which a run is to be stopped. - True the stop should be forced, false for a cooperative stop. - >A callback handler used to report results - A forced stop will cause threads and processes to be killed as needed. - - - - The ITestAssemblyRunner interface is implemented by classes - that are able to execute a suite of tests loaded - from an assembly. - - - - - Loads the tests found in an Assembly, returning an - indication of whether or not the load succeeded. - - File name of the assembly to load - Dictionary of options to use in loading the test - An ITest representing the loaded tests - - - - Loads the tests found in an Assembly, returning an - indication of whether or not the load succeeded. - - The assembly to load - Dictionary of options to use in loading the test - An ITest representing the loaded tests - - - - Count Test Cases using a filter - - The filter to apply - The number of test cases found - - - - Run selected tests and return a test result. The test is run synchronously, - and the listener interface is notified as it progresses. - - Interface to receive ITestListener notifications. - A test filter used to select tests to be run - - - - Run selected tests asynchronously, notifying the listener interface as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - - - Wait for the ongoing run to complete. - - Time to wait in milliseconds - True if the run completed, otherwise false - - - - Signal any test run that is in process to stop. Return without error if no test is running. - - If true, kill any test-running threads - - - - Gets the tree of loaded tests, or null if - no tests have been loaded. - - - - - Gets the tree of test results, if the test - run is completed, otherwise null. - - - - - Indicates whether a test has been loaded - - - - - Indicates whether a test is currently running - - - - - Indicates whether a test run is complete - - - - - Implementation of ITestAssemblyRunner - - - - - Initializes a new instance of the class. - - The builder. - - - - Loads the tests found in an Assembly - - File name of the assembly to load - Dictionary of option settings for loading the assembly - True if the load was successful - - - - Loads the tests found in an Assembly - - The assembly to load - Dictionary of option settings for loading the assembly - True if the load was successful - - - - Count Test Cases using a filter - - The filter to apply - The number of test cases found - - - - Run selected tests and return a test result. The test is run synchronously, - and the listener interface is notified as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - - - - Run selected tests asynchronously, notifying the listener interface as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - RunAsync is a template method, calling various abstract and - virtual methods to be overridden by derived classes. - - - - - Wait for the ongoing run to complete. - - Time to wait in milliseconds - True if the run completed, otherwise false - - - - Initiate the test run. - - - - - Signal any test run that is in process to stop. Return without error if no test is running. - - If true, kill any test-running threads - - - - Create the initial TestExecutionContext used to run tests - - The ITestListener specified in the RunAsync call - - - - Handle the the Completed event for the top level work item - - - - - Gets the default level of parallel execution (worker threads) - - - - - The tree of tests that was loaded by the builder - - - - - The test result, if a run has completed - - - - - Indicates whether a test is loaded - - - - - Indicates whether a test is running - - - - - Indicates whether a test run is complete - - - - - Our settings, specified when loading the assembly - - - - - The top level WorkItem created for the assembly as a whole - - - - - The TestExecutionContext for the top level WorkItem - - - - - The Assert class contains a collection of static methods that - implement the most common assertions used in NUnit. - - - - - Verifies that the first int is greater than the second - int. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first int is greater than the second - int. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - - - - We don't actually want any instances of this object, but some people - like to inherit from it to add other static methods. Hence, the - protected constructor disallows any instances of this object. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - The message to initialize the with. - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - - - - Throws an with the message and arguments - that are passed in. This is used by the other Assert functions. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This is used by the other Assert functions. - - The message to initialize the with. - - - - Throws an . - This is used by the other Assert functions. - - - - - Throws an with the message and arguments - that are passed in. This causes the test to be reported as ignored. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This causes the test to be reported as ignored. - - The message to initialize the with. - - - - Throws an . - This causes the test to be reported as ignored. - - - - - Throws an with the message and arguments - that are passed in. This causes the test to be reported as inconclusive. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This causes the test to be reported as inconclusive. - - The message to initialize the with. - - - - Throws an . - This causes the test to be reported as Inconclusive. - - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - - - - Helper for Assert.AreEqual(double expected, double actual, ...) - allowing code generation to work consistently. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestDelegate - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestDelegate - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - A TestDelegate - - - - Verifies that a delegate does not throw an exception - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate does not throw an exception. - - A TestDelegate - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - - This method is provided for use by VB developers needing to test - the value of properties with private setters. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Delegate used by tests that execute code and - capture any thrown exception. - - - - - AssertionHelper is an optional base class for user tests, - allowing the use of shorter ids for constraints and - asserts and avoiding conflict with the definition of - , from which it inherits much of its - behavior, in certain mock object frameworks. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that fails if the actual - value matches the pattern supplied as an argument. - - - - - Returns a constraint that tests whether the path provided - is the same as an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is a subpath of the expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether an object graph is serializable in binary format. - - - - - Returns a constraint that tests whether an object graph is serializable in xml format. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically to - . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically to . - - The evaluated condition - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Returns a ListMapper based on a collection. - - The original collection - - - - - Provides static methods to express the assumptions - that must be met for a test to give a meaningful - result. If an assumption is not met, the test - should produce an inconclusive result. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the - method throws an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Marks a test that must run in a particular threading apartment state, causing it - to run in a separate thread if necessary. - - - - - PropertyAttribute is used to attach information to a test as a name/value pair.. - - - - - The abstract base class for all custom attributes defined by NUnit. - - - - - Default constructor - - - - - The IApplyToTest interface is implemented by self-applying - attributes that modify the state of a test in some way. - - - - - Modifies a test as defined for the specific attribute. - - The test to modify - - - - Construct a PropertyAttribute with a name and string value - - The name of the property - The property value - - - - Construct a PropertyAttribute with a name and int value - - The name of the property - The property value - - - - Construct a PropertyAttribute with a name and double value - - The name of the property - The property value - - - - Constructor for derived classes that set the - property dictionary directly. - - - - - Constructor for use by derived classes that use the - name of the type as the property name. Derived classes - must ensure that the Type of the property value is - a standard type supported by the BCL. Any custom - types will cause a serialization Exception when - in the client. - - - - - Modifies a test by adding properties to it. - - The test to modify - - - - Gets the property dictionary for this attribute - - - - - Construct an ApartmentAttribute - - The apartment state that this test must be run under. You must pass in a valid apartment state. - - - - Provides the Author of a test or test fixture. - - - - - Initializes a new instance of the class. - - The name of the author. - - - - Initializes a new instance of the class. - - The name of the author. - The email address of the author. - - - - Attribute used to apply a category to a test - - - - - The name of the category - - - - - Construct attribute for a given category based on - a name. The name may not contain the characters ',', - '+', '-' or '!'. However, this is not checked in the - constructor since it would cause an error to arise at - as the test was loaded without giving a clear indication - of where the problem is located. The error is handled - in NUnitFramework.cs by marking the test as not - runnable. - - The name of the category - - - - Protected constructor uses the Type name as the name - of the category. - - - - - Modifies a test by adding a category to it. - - The test to modify - - - - The name of the category - - - - - Marks a test to use a combinatorial join of any argument - data provided. Since this is the default, the attribute is - optional. - - - - - Marks a test to use a particular CombiningStrategy to join - any parameter data provided. Since this is the default, the - attribute is optional. - - - - - The ITestBuilder interface is exposed by a class that knows how to - build one or more TestMethods from a MethodInfo. In general, it is exposed - by an attribute, which has additional information available to provide - the necessary test parameters to distinguish the test cases built. - - - - - Build one or more TestMethods from the provided MethodInfo. - - The method to be used as a test - The TestSuite to which the method will be added - A TestMethod object - - - - Construct a CombiningStrategyAttribute incorporating an object - that implements ICombiningStrategy. - - Combining strategy to be used - - - - Construct a CombiningStrategyAttribute incorporating an object - that implements ICombiningStrategy. This constructor is provided - for CLS compliance. - - Combining strategy to be used - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Modify the test by adding the name of the combining strategy - to the properties. - - The test to modify - - - - Default constructor - - - - - LevelOfParallelismAttribute is used to set the number of worker threads - that may be allocated by the framework for running tests. - - - - - Construct a LevelOfParallelismAttribute. - - The number of worker threads to be created by the framework. - - - - Attribute used to identify a method that is called once - to perform setup before any child tests are run. - - - - - Attribute used to identify a method that is called once - after all the child tests have run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - RepeatAttribute may be applied to test case in order - to run it multiple times. - - - - - Objects implementing this interface are used to wrap - the entire test, including SetUp and TearDown. - - - - - ICommandWrapper is implemented by attributes and other - objects able to wrap a TestCommand with another command. - - - Attributes or other objects should implement one of the - derived interfaces, rather than this one, since they - indicate in which part of the command chain the wrapper - should be applied. - - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - Construct a RepeatAttribute - - The number of times to run the test - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - The test command for the RetryAttribute - - - - - TODO: Documentation needed for class - - - - - TestCommand is the abstract base class for all test commands - in the framework. A TestCommand represents a single stage in - the execution of a test, e.g.: SetUp/TearDown, checking for - Timeout, verifying the returned result from a method, etc. - - TestCommands may decorate other test commands so that the - execution of a lower-level command is nested within that - of a higher level command. All nested commands are executed - synchronously, as a single unit. Scheduling test execution - on separate threads is handled at a higher level, using the - task dispatcher. - - - - - Construct a TestCommand for a test. - - The test to be executed - - - - Runs the test in a specified context, returning a TestResult. - - The TestExecutionContext to be used for running the test. - A TestResult - - - - Gets the test associated with this command. - - - - TODO: Documentation needed for field - - - - TODO: Documentation needed for constructor - - - - - - Initializes a new instance of the class. - - The inner command. - The number of repetitions - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - Provide actions to execute before and after tests. - - - - - When implemented by an attribute, this interface implemented to provide actions to execute before and after tests. - - - - - Executed before each test is run - - The test that is going to be run. - - - - Executed after each test is run - - The test that has just been run. - - - - Provides the target for the action attribute - - The target for the action attribute - - - - Executed before each test is run - - The test that is going to be run. - - - - Executed after each test is run - - The test that has just been run. - - - - Provides the target for the action attribute - - - - - ParallelizableAttribute is used to mark tests that may be run in parallel. - - - - - The IApplyToContext interface is implemented by attributes - that want to make changes to the execution context before - a test is run. - - - - - Apply changes to the execution context - - The execution context - - - - Construct a ParallelizableAttribute using default ParallelScope.Self. - - - - - Construct a ParallelizableAttribute with a specified scope. - - The ParallelScope associated with this attribute. - - - - Modify the context to be used for child tests - - The current TestExecutionContext - - - - CultureAttribute is used to mark a test fixture or an - individual method as applying to a particular Culture only. - - - - - Abstract base for Attributes that are used to include tests - in the test run based on environmental settings. - - - - - Constructor with no included items specified, for use - with named property syntax. - - - - - Constructor taking one or more included items - - Comma-delimited list of included items - - - - Name of the item that is needed in order for - a test to run. Multiple items may be given, - separated by a comma. - - - - - Name of the item to be excluded. Multiple items - may be given, separated by a comma. - - - - - The reason for including or excluding the test - - - - - Constructor with no cultures specified, for use - with named property syntax. - - - - - Constructor taking one or more cultures - - Comma-deliminted list of cultures - - - - Causes a test to be skipped if this CultureAttribute is not satisfied. - - The test to modify - - - - Tests to determine if the current culture is supported - based on the properties of this attribute. - - True, if the current culture is supported - - - - Test to determine if the a particular culture or comma- - delimited set of cultures is in use. - - Name of the culture or comma-separated list of culture ids - True if the culture is in use on the system - - - - Test to determine if one of a collection of cultures - is being used currently. - - - - - - - The abstract base class for all data-providing attributes - defined by NUnit. Used to select all data sources for a - method, class or parameter. - - - - - Default constructor - - - - - Used to mark a field for use as a datapoint when executing a theory - within the same fixture that requires an argument of the field's Type. - - - - - Used to mark a field, property or method providing a set of datapoints to - be used in executing any theories within the same fixture that require an - argument of the Type provided. The data source may provide an array of - the required Type or an . - Synonymous with DatapointSourceAttribute. - - - - - Used to mark a field, property or method providing a set of datapoints to - be used in executing any theories within the same fixture that require an - argument of the Type provided. The data source may provide an array of - the required Type or an . - Synonymous with DatapointsAttribute. - - - - - Attribute used to provide descriptive text about a - test case or fixture. - - - - - Construct a description Attribute - - The text of the description - - - - ExplicitAttribute marks a test or test fixture so that it will - only be run if explicitly executed from the gui or command line - or if it is included by use of a filter. The test will not be - run simply because an enclosing suite is run. - - - - - Default constructor - - - - - Constructor with a reason - - The reason test is marked explicit - - - - Modifies a test by marking it as explicit. - - The test to modify - - - - Attribute used to mark a test that is to be ignored. - Ignored tests result in a warning message when the - tests are run. - - - - - Constructs the attribute giving a reason for ignoring the test - - The reason for ignoring the test - - - - Modifies a test by marking it as Ignored. - - The test to modify - - - - The date in the future to stop ignoring the test as a string in UTC time. - For example for a date and time, "2014-12-25 08:10:00Z" or for just a date, - "2014-12-25". If just a date is given, the Ignore will expire at midnight UTC. - - - Once the ignore until date has passed, the test will be marked - as runnable. Tests with an ignore until date will have an IgnoreUntilDate - property set which will appear in the test results. - - The string does not contain a valid string representation of a date and time. - - - - Summary description for MaxTimeAttribute. - - - - - Construct a MaxTimeAttribute, given a time in milliseconds. - - The maximum elapsed time in milliseconds - - - - Marks a test to use a pairwise join of any argument - data provided. Arguments will be combined in such a - way that all possible pairs of arguments are used. - - - - - Default constructor - - - - - The ParallelScope enumeration permits specifying the degree to - which a test and its descendants may be run in parallel. - - - - - No Parallelism is permitted - - - - - The test itself may be run in parallel with others at the same level - - - - - Descendants of the test may be run in parallel with one another - - - - - Descendants of the test down to the level of TestFixtures may be run in parallel - - - - - PlatformAttribute is used to mark a test fixture or an - individual method as applying to a particular platform only. - - - - - Constructor with no platforms specified, for use - with named property syntax. - - - - - Constructor taking one or more platforms - - Comma-delimited list of platforms - - - - Causes a test to be skipped if this PlatformAttribute is not satisfied. - - The test to modify - - - - RandomAttribute is used to supply a set of random _values - to a single parameter of a parameterized test. - - - - - The IParameterDataSource interface is implemented by types - that can provide data for a test method parameter. - - - - - Gets an enumeration of data items for use as arguments - for a test method parameter. - - The parameter for which data is needed - An enumeration containing individual data items - - - - Construct a random set of values appropriate for the Type of the - parameter on which the attribute appears, specifying only the count. - - - - - - Construct a set of ints within a specified range - - - - - Construct a set of unsigned ints within a specified range - - - - - Construct a set of longs within a specified range - - - - - Construct a set of unsigned longs within a specified range - - - - - Construct a set of shorts within a specified range - - - - - Construct a set of unsigned shorts within a specified range - - - - - Construct a set of doubles within a specified range - - - - - Construct a set of floats within a specified range - - - - - Construct a set of bytes within a specified range - - - - - Construct a set of sbytes within a specified range - - - - - Get the collection of _values to be used as arguments. - - - - - RangeAttribute is used to supply a range of _values to an - individual parameter of a parameterized test. - - - - - ValuesAttribute is used to provide literal arguments for - an individual parameter of a test. - - - - - The collection of data to be returned. Must - be set by any derived attribute classes. - We use an object[] so that the individual - elements may have their type changed in GetData - if necessary - - - - - Constructs for use with an Enum parameter. Will pass every enum - value in to the test. - - - - - Construct with one argument - - - - - - Construct with two arguments - - - - - - - Construct with three arguments - - - - - - - - Construct with an array of arguments - - - - - - Get the collection of _values to be used as arguments - - - - - Construct a range of ints using default step of 1 - - - - - - - Construct a range of ints specifying the step size - - - - - - - - Construct a range of unsigned ints using default step of 1 - - - - - - - Construct a range of unsigned ints specifying the step size - - - - - - - - Construct a range of longs using a default step of 1 - - - - - - - Construct a range of longs - - - - - - - - Construct a range of unsigned longs using default step of 1 - - - - - - - Construct a range of unsigned longs specifying the step size - - - - - - - - Construct a range of doubles - - - - - - - - Construct a range of floats - - - - - - - - RepeatAttribute may be applied to test case in order - to run it multiple times. - - - - - Construct a RepeatAttribute - - The number of times to run the test - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - The test command for the RepeatAttribute - - - - - Initializes a new instance of the class. - - The inner command. - The number of repetitions - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - Marks a test that must run in the MTA, causing it - to run in a separate thread if necessary. - - On methods, you may also use MTAThreadAttribute - to serve the same purpose. - - - - - Construct a RequiresMTAAttribute - - - - - Marks a test that must run in the STA, causing it - to run in a separate thread if necessary. - - - - - Construct a RequiresSTAAttribute - - - - - Marks a test that must run on a separate thread. - - - - - Construct a RequiresThreadAttribute - - - - - Construct a RequiresThreadAttribute, specifying the apartment - - - - - Marks a test to use a Sequential join of any argument - data provided. Arguments will be combined into test cases, - taking the next value of each argument until all are used. - - - - - Default constructor - - - - - Summary description for SetCultureAttribute. - - - - - Construct given the name of a culture - - - - - - Summary description for SetUICultureAttribute. - - - - - Construct given the name of a culture - - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - SetUpFixtureAttribute is used to identify a SetUpFixture - - - - - The IFixtureBuilder interface is exposed by a class that knows how to - build a TestFixture from one or more Types. In general, it is exposed - by an attribute, but may be implemented in a helper class used by the - attribute in some cases. - - - - - Build one or more TestFixtures from type provided. At least one - non-null TestSuite must always be returned, since the method is - generally called because the user has marked the target class as - a fixture. If something prevents the fixture from being used, it - will be returned nonetheless, labelled as non-runnable. - - The type info of the fixture to be used. - A TestSuite object or one derived from TestSuite. - - - - Build a SetUpFixture from type provided. Normally called for a Type - on which the attribute has been placed. - - The type info of the fixture to be used. - A SetUpFixture object as a TestSuite. - - - - Attribute used to identify a method that is called - immediately after each test is run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Adding this attribute to a method within a - class makes the method callable from the NUnit test runner. There is a property - called Description which is optional which you can provide a more detailed test - description. This class cannot be inherited. - - - - [TestFixture] - public class Fixture - { - [Test] - public void MethodToTest() - {} - - [Test(Description = "more detailed description")] - public void TestDescriptionMethod() - {} - } - - - - - - The ISimpleTestBuilder interface is exposed by a class that knows how to - build a single TestMethod from a suitable MethodInfo Types. In general, - it is exposed by an attribute, but may be implemented in a helper class - used by the attribute in some cases. - - - - - Build a TestMethod from the provided MethodInfo. - - The method to be used as a test - The TestSuite to which the method will be added - A TestMethod object - - - - IImplyFixture is an empty marker interface used by attributes like - TestAttribute that cause the class where they are used to be treated - as a TestFixture even without a TestFixtureAttribute. - - Marker interfaces are not usually considered a good practice, but - we use it here to avoid cluttering the attribute hierarchy with - classes that don't contain any extra implementation. - - - - - Modifies a test by adding a description, if not already set. - - The test to modify - - - - Construct a TestMethod from a given method. - - The method for which a test is to be constructed. - The suite to which the test will be added. - A TestMethod - - - - Descriptive text for this test - - - - - The author of this test - - - - - The type that this test is testing - - - - - Gets or sets the expected result. - - The result. - - - - Returns true if an expected result has been set - - - - - TestCaseAttribute is used to mark parameterized test cases - and provide them with their arguments. - - - - - The ITestCaseData interface is implemented by a class - that is able to return complete testcases for use by - a parameterized test method. - - - - - The ITestData interface is implemented by a class that - represents a single instance of a parameterized test. - - - - - Gets the name to be used for the test - - - - - Gets the RunState for this test case. - - - - - Gets the argument list to be provided to the test - - - - - Gets the property dictionary for the test case - - - - - Gets the expected result of the test case - - - - - Returns true if an expected result has been set - - - - - Construct a TestCaseAttribute with a list of arguments. - This constructor is not CLS-Compliant - - - - - - Construct a TestCaseAttribute with a single argument - - - - - - Construct a TestCaseAttribute with a two arguments - - - - - - - Construct a TestCaseAttribute with a three arguments - - - - - - - - Performs several special conversions allowed by NUnit in order to - permit arguments with types that cannot be used in the constructor - of an Attribute such as TestCaseAttribute or to simplify their use. - - The arguments to be converted - The ParameterInfo array for the method - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Gets or sets the name of the test. - - The name of the test. - - - - Gets or sets the RunState of this test case. - - - - - Gets the list of arguments to a test case - - - - - Gets the properties of the test case - - - - - Gets or sets the expected result. - - The result. - - - - Returns true if the expected result has been set - - - - - Gets or sets the description. - - The description. - - - - The author of this test - - - - - The type that this test is testing - - - - - Gets or sets the reason for ignoring the test - - - - - Gets or sets a value indicating whether this is explicit. - - - true if explicit; otherwise, false. - - - - - Gets or sets the reason for not running the test. - - The reason. - - - - Gets or sets the ignore reason. When set to a non-null - non-empty value, the test is marked as ignored. - - The ignore reason. - - - - Comma-delimited list of platforms to run the test for - - - - - Comma-delimited list of platforms to not run the test for - - - - - Gets and sets the category for this test case. - May be a comma-separated list of categories. - - - - - TestCaseSourceAttribute indicates the source to be used to - provide test cases for a test method. - - - - - Construct with the name of the method, property or field that will provide data - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Construct with a Type - - The type that will provide data - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The IMethod for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Returns a set of ITestCaseDataItems for use as arguments - to a parameterized test method. - - The method for which data is needed. - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - Gets or sets the category associated with every fixture created from - this attribute. May be a single category or a comma-separated list. - - - - - TestFixtureAttribute is used to mark a class that represents a TestFixture. - - - - - The ITestCaseData interface is implemented by a class - that is able to return the data required to create an - instance of a parameterized test fixture. - - - - - Get the TypeArgs if separately set - - - - - Default constructor - - - - - Construct with a object[] representing a set of arguments. - In .NET 2.0, the arguments may later be separated into - type arguments and constructor arguments. - - - - - - Build a fixture from type provided. Normally called for a Type - on which the attribute has been placed. - - The type info of the fixture to be used. - A an IEnumerable holding one TestFixture object. - - - - Gets or sets the name of the test. - - The name of the test. - - - - Gets or sets the RunState of this test fixture. - - - - - The arguments originally provided to the attribute - - - - - Properties pertaining to this fixture - - - - - Get or set the type arguments. If not set - explicitly, any leading arguments that are - Types are taken as type arguments. - - - - - Descriptive text for this fixture - - - - - The author of this fixture - - - - - The type that this fixture is testing - - - - - Gets or sets the ignore reason. May set RunState as a side effect. - - The ignore reason. - - - - Gets or sets the reason for not running the fixture. - - The reason. - - - - Gets or sets the ignore reason. When set to a non-null - non-empty value, the test is marked as ignored. - - The ignore reason. - - - - Gets or sets a value indicating whether this is explicit. - - - true if explicit; otherwise, false. - - - - - Gets and sets the category for this fixture. - May be a comma-separated list of categories. - - - - - Attribute used to identify a method that is - called before any tests in a fixture are run. - - - - - TestCaseSourceAttribute indicates the source to be used to - provide test fixture instances for a test class. - - - - - Error message string is public so the tests can use it - - - - - Construct with the name of the method, property or field that will provide data - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Construct with a Type - - The type that will provide data - - - - Construct one or more TestFixtures from a given Type, - using available parameter data. - - The TypeInfo for which fixures are to be constructed. - One or more TestFixtures as TestSuite - - - - Returns a set of ITestFixtureData items for use as arguments - to a parameterized test fixture. - - The type for which data is needed. - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - Gets or sets the category associated with every fixture created from - this attribute. May be a single category or a comma-separated list. - - - - - Attribute used to identify a method that is called after - all the tests in a fixture have run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Indicates which class the test or test fixture is testing - - - - - Initializes a new instance of the class. - - The type that is being tested. - - - - Initializes a new instance of the class. - - The type that is being tested. - - - - Adding this attribute to a method within a - class makes the method callable from the NUnit test runner. There is a property - called Description which is optional which you can provide a more detailed test - description. This class cannot be inherited. - - - - [TestFixture] - public class Fixture - { - [Test] - public void MethodToTest() - {} - - [Test(Description = "more detailed description")] - public void TestDescriptionMethod() - {} - } - - - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Used on a method, marks the test with a timeout value in milliseconds. - The test will be run in a separate thread and is cancelled if the timeout - is exceeded. Used on a class or assembly, sets the default timeout - for all contained test methods. - - - - - Construct a TimeoutAttribute given a time in milliseconds - - The timeout value in milliseconds - - - - ValueSourceAttribute indicates the source to be used to - provide data for one parameter of a test method. - - - - - Construct with the name of the factory - for use with languages - that don't support params arrays. - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - for use with languages - that don't support params arrays. - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Gets an enumeration of data items for use as arguments - for a test method parameter. - - The parameter for which data is needed - - An enumeration containing individual data items - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - A set of Assert methods operating on one or more collections - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable containing objects to be considered - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the superset does not contain the subset - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - - - - Asserts that the superset does not contain the subset - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the superset contains the subset. - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - - - - Asserts that the superset contains the subset. - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the subset does not contain the superset - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - - - - Asserts that the subset does not contain the superset - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the subset contains the superset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - - - - Asserts that the subset contains the superset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array,list or other collection is empty - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array,list or other collection is empty - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - - - - Provides a platform-independent methods for getting attributes - for use by AttributeConstraint and AttributeExistsConstraint. - - - - - Gets the custom attributes from the given object. - - Portable libraries do not have an ICustomAttributeProvider, so we need to cast to each of - it's direct subtypes and try to get attributes off those instead. - The actual. - Type of the attribute. - if set to true [inherit]. - A list of the given attribute on the given object. - - - - Provides NUnit specific extensions to aid in Reflection - across multiple frameworks - - - This version of the class supplies GetTypeInfo() on platforms - that don't support it. - - - - - GetTypeInfo gives access to most of the Type information we take for granted - on .NET Core and Windows Runtime. Rather than #ifdef different code for different - platforms, it is easiest to just code all platforms as if they worked this way, - thus the simple passthrough. - - - - - - - This class is a System.Diagnostics.Stopwatch on operating systems that support it. On those that don't, - it replicates the functionality at the resolution supported. - - - - - AllItemsConstraint applies another constraint to each - item in a collection, succeeding if they all succeed. - - - - - Abstract base class used for prefixes - - - - - The Constraint class is the base of all built-in constraints - within NUnit. It provides the operator overloads used to combine - constraints. - - - - - Interface for all constraints - - - - - The IResolveConstraint interface is implemented by all - complete and resolvable constraints and expressions. - - - - - Return the top-level constraint for this expression - - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - The default implementation simply dereferences the value but - derived classes may override it to provide for delayed processing. - - A reference to the value to be tested - A ConstraintResult - - - - The display name of this Constraint for use by ToString(). - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Arguments provided to this Constraint, for use in - formatting the description. - - - - - The ConstraintBuilder holding this constraint - - - - - Construct a constraint with optional arguments - - Arguments to be saved - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - The default implementation simply dereferences the value but - derived classes may override it to provide for delayed processing. - - A reference to the value to be tested - A ConstraintResult - - - - Default override of ToString returns the constraint DisplayName - followed by any arguments within angle brackets. - - - - - - Returns the string representation of this constraint - - - - - This operator creates a constraint that is satisfied only if both - argument constraints are satisfied. - - - - - This operator creates a constraint that is satisfied if either - of the argument constraints is satisfied. - - - - - This operator creates a constraint that is satisfied if the - argument constraint is not satisfied. - - - - - Returns a DelayedConstraint with the specified delay time. - - The delay in milliseconds. - - - - - Returns a DelayedConstraint with the specified delay time - and polling interval. - - The delay in milliseconds. - The interval at which to test the constraint. - - - - - Resolves any pending operators and returns the resolved constraint. - - - - - The display name of this Constraint for use by ToString(). - The default value is the name of the constraint with - trailing "Constraint" removed. Derived classes may set - this to another name in their constructors. - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Arguments provided to this Constraint, for use in - formatting the description. - - - - - The ConstraintBuilder holding this constraint - - - - - Returns a ConstraintExpression by appending And - to the current constraint. - - - - - Returns a ConstraintExpression by appending And - to the current constraint. - - - - - Returns a ConstraintExpression by appending Or - to the current constraint. - - - - - The base constraint - - - - - Prefix used in forming the constraint description - - - - - Construct given a base constraint - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Construct an AllItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - failing if any item fails. - - - - - - - AndConstraint succeeds only if both members succeed. - - - - - BinaryConstraint is the abstract base of all constraints - that combine two other constraints in some fashion. - - - - - The first constraint being combined - - - - - The second constraint being combined - - - - - Construct a BinaryConstraint from two other constraints - - The first constraint - The second constraint - - - - Create an AndConstraint from two other constraints - - The first constraint - The second constraint - - - - Apply both member constraints to an actual value, succeeding - succeeding only if both of them succeed. - - The actual value - True if the constraints both succeeded - - - - Gets text describing a constraint - - - - - Contain the result of matching a against an actual value. - - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - The status of the new ConstraintResult. - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - If true, applies a status of Success to the result, otherwise Failure. - - - - Write the failure message to the MessageWriter provided - as an argument. The default implementation simply passes - the result and the actual value to the writer, which - then displays the constraint description and the value. - - Constraints that need to provide additional details, - such as where the error occured can override this. - - The MessageWriter on which to display the message - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - The actual value that was passed to the method. - - - - - Gets and sets the ResultStatus for this result. - - - - - True if actual value meets the Constraint criteria otherwise false. - - - - - Display friendly name of the constraint. - - - - - Description of the constraint may be affected by the state the constraint had - when was performed against the actual value. - - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - AssignableFromConstraint is used to test that an object - can be assigned from a given Type. - - - - - TypeConstraint is the abstract base for constraints - that take a Type as their expected value. - - - - - The expected Type used by the constraint - - - - - The type of the actual argument to which the constraint was applied - - - - - Construct a TypeConstraint for a given Type - - The expected type for the constraint - Prefix used in forming the constraint description - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - Construct an AssignableFromConstraint for the type provided - - - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - AssignableToConstraint is used to test that an object - can be assigned to a given Type. - - - - - Construct an AssignableToConstraint for the type provided - - - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - AttributeConstraint tests that a specified attribute is present - on a Type or other provider and that the value of the attribute - satisfies some other constraint. - - - - - Constructs an AttributeConstraint for a specified attribute - Type and base constraint. - - - - - - - Determines whether the Type or other provider has the - expected attribute and if its value matches the - additional constraint specified. - - - - - Returns a string representation of the constraint. - - - - - AttributeExistsConstraint tests for the presence of a - specified attribute on a Type. - - - - - Constructs an AttributeExistsConstraint for a specific attribute Type - - - - - - Tests whether the object provides the expected attribute. - - A Type, MethodInfo, or other ICustomAttributeProvider - True if the expected attribute is present, otherwise false - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - BinarySerializableConstraint tests whether - an object is serializable in binary format. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Returns the string representation - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionConstraint is the abstract base class for - constraints that operate on collections. - - - - - Construct an empty CollectionConstraint - - - - - Construct a CollectionConstraint - - - - - - Determines whether the specified enumerable is empty. - - The enumerable. - - true if the specified enumerable is empty; otherwise, false. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Protected method to be implemented by derived classes - - - - - - - CollectionContainsConstraint is used to test whether a collection - contains an expected object as a member. - - - - - CollectionItemsEqualConstraint is the abstract base class for all - collection constraints that apply some notion of item equality - as a part of their operation. - - - - - Construct an empty CollectionConstraint - - - - - Construct a CollectionConstraint - - - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Compares two collection members for equality - - - - - Return a new CollectionTally for use in making tests - - The collection to be included in the tally - - - - Flag the constraint to ignore case and return self. - - - - - Construct a CollectionContainsConstraint - - - - - - Test whether the expected item is contained in the collection - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Gets the expected object - - - - - CollectionEquivalentConstraint is used to determine whether two - collections are equivalent. - - - - - Construct a CollectionEquivalentConstraint - - - - - - Test whether two collections are equivalent - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionOrderedConstraint is used to test whether a collection is ordered. - - - - - Construct a CollectionOrderedConstraint - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use a and returns self. - - - - - Modifies the constraint to test ordering by the value of - a specified property and returns self. - - - - - Test whether the collection is ordered - - - - - - - Returns the string representation of the constraint. - - - - - - If used performs a reverse comparison - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionSupersetConstraint is used to determine whether - one collection is a superset of another - - - - - Construct a CollectionSupersetConstraint - - The collection that the actual value is expected to be a superset of - - - - Test whether the actual collection is a superset of - the expected collection provided. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionSubsetConstraint is used to determine whether - one collection is a subset of another - - - - - Construct a CollectionSubsetConstraint - - The collection that the actual value is expected to be a subset of - - - - Test whether the actual collection is a subset of - the expected collection provided. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionTally counts (tallies) the number of - occurrences of each object in one or more enumerations. - - - - - Construct a CollectionTally object from a comparer and a collection - - - - - Try to remove an object from the tally - - The object to remove - True if successful, false if the object was not found - - - - Try to remove a set of objects from the tally - - The objects to remove - True if successful, false if any object was not found - - - - The number of objects remaining in the tally - - - - - ComparisonAdapter class centralizes all comparisons of - _values in NUnit, adapting to the use of any provided - , - or . - - - - - Returns a ComparisonAdapter that wraps an - - - - - Returns a ComparisonAdapter that wraps an - - - - - Returns a ComparisonAdapter that wraps a - - - - - Compares two objects - - - - - Gets the default ComparisonAdapter, which wraps an - NUnitComparer object. - - - - - Construct a ComparisonAdapter for an - - - - - Compares two objects - - - - - - - - Construct a default ComparisonAdapter - - - - - ComparerAdapter extends and - allows use of an or - to actually perform the comparison. - - - - - Construct a ComparisonAdapter for an - - - - - Compare a Type T to an object - - - - - Construct a ComparisonAdapter for a - - - - - Compare a Type T to an object - - - - - Abstract base class for constraints that compare _values to - determine if one is greater than, equal to or less than - the other. - - - - - The value against which a comparison is to be made - - - - - If true, less than returns success - - - - - if true, equal returns success - - - - - if true, greater than returns success - - - - - ComparisonAdapter to be used in making the comparison - - - - - Initializes a new instance of the class. - - The value against which to make a comparison. - if set to true less succeeds. - if set to true equal succeeds. - if set to true greater succeeds. - String used in describing the constraint. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Modifies the constraint to use an and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Modifies the constraint to use an and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Modifies the constraint to use a and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Delegate used to delay evaluation of the actual value - to be used in evaluating a constraint - - - - - ConstraintBuilder maintains the stacks that are used in - processing a ConstraintExpression. An OperatorStack - is used to hold operators that are waiting for their - operands to be reorganized. a ConstraintStack holds - input constraints as well as the results of each - operator applied. - - - - - Initializes a new instance of the class. - - - - - Appends the specified operator to the expression by first - reducing the operator stack and then pushing the new - operator on the stack. - - The operator to push. - - - - Appends the specified constraint to the expression by pushing - it on the constraint stack. - - The constraint to push. - - - - Sets the top operator right context. - - The right context. - - - - Reduces the operator stack until the topmost item - precedence is greater than or equal to the target precedence. - - The target precedence. - - - - Resolves this instance, returning a Constraint. If the Builder - is not currently in a resolvable state, an exception is thrown. - - The resolved constraint - - - - Gets a value indicating whether this instance is resolvable. - - - true if this instance is resolvable; otherwise, false. - - - - - OperatorStack is a type-safe stack for holding ConstraintOperators - - - - - Initializes a new instance of the class. - - The ConstraintBuilder using this stack. - - - - Pushes the specified operator onto the stack. - - The operator to put onto the stack. - - - - Pops the topmost operator from the stack. - - The topmost operator on the stack - - - - Gets a value indicating whether this is empty. - - true if empty; otherwise, false. - - - - Gets the topmost operator without modifying the stack. - - - - - ConstraintStack is a type-safe stack for holding Constraints - - - - - Initializes a new instance of the class. - - The ConstraintBuilder using this stack. - - - - Pushes the specified constraint. As a side effect, - the constraint's Builder field is set to the - ConstraintBuilder owning this stack. - - The constraint to put onto the stack - - - - Pops this topmost constraint from the stack. - As a side effect, the constraint's Builder - field is set to null. - - The topmost contraint on the stack - - - - Gets a value indicating whether this is empty. - - true if empty; otherwise, false. - - - - ConstraintExpression represents a compound constraint in the - process of being constructed from a series of syntactic elements. - - Individual elements are appended to the expression as they are - reorganized. When a constraint is appended, it is returned as the - value of the operation so that modifiers may be applied. However, - any partially built expression is attached to the constraint for - later resolution. When an operator is appended, the partial - expression is returned. If it's a self-resolving operator, then - a ResolvableConstraintExpression is returned. - - - - - The ConstraintBuilder holding the elements recognized so far - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class passing in a ConstraintBuilder, which may be pre-populated. - - The builder. - - - - Returns a string representation of the expression as it - currently stands. This should only be used for testing, - since it has the side-effect of resolving the expression. - - - - - - Appends an operator to the expression and returns the - resulting expression itself. - - - - - Appends a self-resolving operator to the expression and - returns a new ResolvableConstraintExpression. - - - - - Appends a constraint to the expression and returns that - constraint, which is associated with the current state - of the expression being built. Note that the constraint - is not reduced at this time. For example, if there - is a NotOperator on the stack we don't reduce and - return a NotConstraint. The original constraint must - be returned because it may support modifiers that - are yet to be applied. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns the constraint provided as an argument - used to allow custom - custom constraints to easily participate in the syntax. - - - - - Returns the constraint provided as an argument - used to allow custom - custom constraints to easily participate in the syntax. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that tests whether the path provided - is the same as an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the a subpath of the expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - With is currently a NOP - reserved for future use. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether an object graph is serializable in binary format. - - - - - Returns a constraint that tests whether an object graph is serializable in xml format. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - Returns a constraint that succeeds if the value - is a file or directory and it exists. - - - - - ConstraintStatus represents the status of a ConstraintResult - returned by a Constraint being applied to an actual value. - - - - - The status has not yet been set - - - - - The constraint succeeded - - - - - The constraint failed - - - - - An error occured in applying the constraint (reserved for future use) - - - - - ContainsConstraint tests a whether a string contains a substring - or a collection contains an object. It postpones the decision of - which test to use until the type of the actual argument is known. - This allows testing whether a string is contained in a collection - or as a substring of another string using the same syntax. - - - - - Initializes a new instance of the class. - - The _expected. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Flag the constraint to ignore case and return self. - - - - - Applies a delay to the match so that a match can be evaluated in the future. - - - - - Creates a new DelayedConstraint - - The inner constraint to decorate - The time interval after which the match is performed - If the value of is less than 0 - - - - Creates a new DelayedConstraint - - The inner constraint to decorate - The time interval after which the match is performed, in milliseconds - The time interval used for polling, in milliseconds - If the value of is less than 0 - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for if the base constraint fails, false if it succeeds - - - - Test whether the constraint is satisfied by a delegate - - The delegate whose value is to be tested - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - Overridden to wait for the specified delay period before - calling the base constraint with the dereferenced value. - - A reference to the value to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - Adjusts a Timestamp by a given TimeSpan - - - - - - - - Returns the difference between two Timestamps as a TimeSpan - - - - - - - - Gets text describing a constraint - - - - - DictionaryContainsKeyConstraint is used to test whether a dictionary - contains an expected object as a key. - - - - - Construct a DictionaryContainsKeyConstraint - - - - - - Test whether the expected key is contained in the dictionary - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - DictionaryContainsValueConstraint is used to test whether a dictionary - contains an expected object as a value. - - - - - Construct a DictionaryContainsValueConstraint - - - - - - Test whether the expected value is contained in the dictionary - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyCollectionConstraint tests whether a collection is empty. - - - - - Check that the collection is empty - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyConstraint tests a whether a string or collection is empty, - postponing the decision about which test is applied until the - type of the actual argument is known. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyDirectoryConstraint is used to test that a directory is empty - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyStringConstraint tests whether a string is empty. - - - - - StringConstraint is the abstract base for constraints - that operate on strings. It supports the IgnoreCase - modifier for string operations. - - - - - The expected value - - - - - Indicates whether tests should be case-insensitive - - - - - Description of this constraint - - - - - Constructs a StringConstraint without an expected value - - - - - Constructs a StringConstraint given an expected value - - The expected value - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Test whether the constraint is satisfied by a given string - - The string to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Modify the constraint to ignore case in matching. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EndsWithConstraint can test whether a string ends - with an expected substring. - - - - - Initializes a new instance of the class. - - The expected string - - - - Test whether the constraint is matched by the actual value. - This is a template method, which calls the IsMatch method - of the derived class. - - - - - - - EqualConstraint is able to compare an actual value with the - expected value provided in its constructor. Two objects are - considered equal if both are null, or if both have the same - value. NUnit has special semantics for some object types. - - - - - NUnitEqualityComparer used to test equality. - - - - - Initializes a new instance of the class. - - The expected value. - - - - Flag the constraint to use a tolerance when determining equality. - - Tolerance value to be used - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Gets the tolerance for this comparison. - - - The tolerance. - - - - - Gets a value indicating whether to compare case insensitive. - - - true if comparing case insensitive; otherwise, false. - - - - - Gets a value indicating whether or not to clip strings. - - - true if set to clip strings otherwise, false. - - - - - Gets the failure points. - - - The failure points. - - - - - Flag the constraint to ignore case and return self. - - - - - Flag the constraint to suppress string clipping - and return self. - - - - - Flag the constraint to compare arrays as collections - and return self. - - - - - Flags the constraint to include - property in comparison of two values. - - - Using this modifier does not allow to use the - constraint modifier. - - - - - Switches the .Within() modifier to interpret its tolerance as - a distance in representable _values (see remarks). - - Self. - - Ulp stands for "unit in the last place" and describes the minimum - amount a given value can change. For any integers, an ulp is 1 whole - digit. For floating point _values, the accuracy of which is better - for smaller numbers and worse for larger numbers, an ulp depends - on the size of the number. Using ulps for comparison of floating - point results instead of fixed tolerances is safer because it will - automatically compensate for the added inaccuracy of larger numbers. - - - - - Switches the .Within() modifier to interpret its tolerance as - a percentage that the actual _values is allowed to deviate from - the expected value. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in days. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in hours. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in minutes. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in seconds. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in milliseconds. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in clock ticks. - - Self - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EqualityAdapter class handles all equality comparisons - that use an , - or a . - - - - - Compares two objects, returning true if they are equal - - - - - Returns true if the two objects can be compared by this adapter. - The base adapter cannot handle IEnumerables except for strings. - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps a . - - - - - that wraps an . - - - - - Returns true if the two objects can be compared by this adapter. - Generic adapter requires objects of the specified type. - - - - - that wraps an . - - - - - ExactTypeConstraint is used to test that an object - is of the exact type provided in the constructor - - - - - Construct an ExactTypeConstraint for a given Type - - The expected Type. - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - FalseConstraint tests that the actual value is false - - - - - Initializes a new instance of the class. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - FileExistsConstraint is used to determine if a file exists - - - - - FileOrDirectoryExistsConstraint is used to determine if a file or directory exists - - - - - Initializes a new instance of the class that - will check files and directories. - - - - - Initializes a new instance of the class that - will only check files if ignoreDirectories is true. - - if set to true [ignore directories]. - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - If true, the constraint will only check if files exist, not directories - - - - - If true, the constraint will only check if directories exist, not files - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Initializes a new instance of the class. - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - Helper routines for working with floating point numbers - - - The floating point comparison code is based on this excellent article: - http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm - - - "ULP" means Unit in the Last Place and in the context of this library refers to - the distance between two adjacent floating point numbers. IEEE floating point - numbers can only represent a finite subset of natural numbers, with greater - accuracy for smaller numbers and lower accuracy for very large numbers. - - - If a comparison is allowed "2 ulps" of deviation, that means the _values are - allowed to deviate by up to 2 adjacent floating point _values, which might be - as low as 0.0000001 for small numbers or as high as 10.0 for large numbers. - - - - - Compares two floating point _values for equality - First floating point value to be compared - Second floating point value t be compared - - Maximum number of representable floating point _values that are allowed to - be between the left and the right floating point _values - - True if both numbers are equal or close to being equal - - - Floating point _values can only represent a finite subset of natural numbers. - For example, the _values 2.00000000 and 2.00000024 can be stored in a float, - but nothing inbetween them. - - - This comparison will count how many possible floating point _values are between - the left and the right number. If the number of possible _values between both - numbers is less than or equal to maxUlps, then the numbers are considered as - being equal. - - - Implementation partially follows the code outlined here: - http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ - - - - - Compares two double precision floating point _values for equality - First double precision floating point value to be compared - Second double precision floating point value t be compared - - Maximum number of representable double precision floating point _values that are - allowed to be between the left and the right double precision floating point _values - - True if both numbers are equal or close to being equal - - - Double precision floating point _values can only represent a limited series of - natural numbers. For example, the _values 2.0000000000000000 and 2.0000000000000004 - can be stored in a double, but nothing inbetween them. - - - This comparison will count how many possible double precision floating point - _values are between the left and the right number. If the number of possible - _values between both numbers is less than or equal to maxUlps, then the numbers - are considered as being equal. - - - Implementation partially follows the code outlined here: - http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ - - - - - - Reinterprets the memory contents of a floating point value as an integer value - - - Floating point value whose memory contents to reinterpret - - - The memory contents of the floating point value interpreted as an integer - - - - - Reinterprets the memory contents of a double precision floating point - value as an integer value - - - Double precision floating point value whose memory contents to reinterpret - - - The memory contents of the double precision floating point value - interpreted as an integer - - - - - Reinterprets the memory contents of an integer as a floating point value - - Integer value whose memory contents to reinterpret - - The memory contents of the integer value interpreted as a floating point value - - - - - Reinterprets the memory contents of an integer value as a double precision - floating point value - - Integer whose memory contents to reinterpret - - The memory contents of the integer interpreted as a double precision - floating point value - - - - Union of a floating point variable and an integer - - - The union's value as a floating point variable - - - The union's value as an integer - - - The union's value as an unsigned integer - - - Union of a double precision floating point variable and a long - - - The union's value as a double precision floating point variable - - - The union's value as a long - - - The union's value as an unsigned long - - - - Tests whether a value is greater than the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - Tests whether a value is greater than or equal to the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - InstanceOfTypeConstraint is used to test that an object - is of the same type provided or derived from it. - - - - - Construct an InstanceOfTypeConstraint for the type provided - - The expected Type - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - Tests whether a value is less than the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - Tests whether a value is less than or equal to the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - MessageWriter is the abstract base for classes that write - constraint descriptions and messages in some form. The - class has separate methods for writing various components - of a message, allowing implementations to tailor the - presentation as needed. - - - - - Construct a MessageWriter given a culture - - - - - Method to write single line message with optional args, usually - written to precede the general failure message. - - The message to be written - Any arguments used in formatting the message - - - - Method to write single line message with optional args, usually - written to precede the general failure message, at a givel - indentation level. - - The indentation level of the message - The message to be written - Any arguments used in formatting the message - - - - Display Expected and Actual lines for a constraint. This - is called by MessageWriter's default implementation of - WriteMessageTo and provides the generic two-line display. - - The failing constraint result - - - - Display Expected and Actual lines for given _values. This - method may be called by constraints that need more control over - the display of actual and expected _values than is provided - by the default implementation. - - The expected value - The actual value causing the failure - - - - Display Expected and Actual lines for given _values, including - a tolerance value on the Expected line. - - The expected value - The actual value causing the failure - The tolerance within which the test was made - - - - Display the expected and actual string _values on separate lines. - If the mismatch parameter is >=0, an additional line is displayed - line containing a caret that points to the mismatch point. - - The expected string value - The actual string value - The point at which the strings don't match or -1 - If true, case is ignored in locating the point where the strings differ - If true, the strings should be clipped to fit the line - - - - Writes the text for an actual value. - - The actual value. - - - - Writes the text for a generalized value. - - The value. - - - - Writes the text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Abstract method to get the max line length - - - - - Static methods used in creating messages - - - - - Static string used when strings are clipped - - - - - Formatting strings used for expected and actual _values - - - - - Formats text to represent a generalized value. - - The value - The formatted text - - - - Formats text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Returns the representation of a type as used in NUnitLite. - This is the same as Type.ToString() except for arrays, - which are displayed with their declared sizes. - - - - - - - Converts any control characters in a string - to their escaped representation. - - The string to be converted - The converted string - - - - Return the a string representation for a set of indices into an array - - Array of indices for which a string is needed - - - - Get an array of indices representing the point in a collection or - array corresponding to a single int index into the collection. - - The collection to which the indices apply - Index in the collection - Array of indices - - - - Clip a string to a given length, starting at a particular offset, returning the clipped - string with ellipses representing the removed parts - - The string to be clipped - The maximum permitted length of the result string - The point at which to start clipping - The clipped string - - - - Clip the expected and actual strings in a coordinated fashion, - so that they may be displayed together. - - - - - - - - - Shows the position two strings start to differ. Comparison - starts at the start index. - - The expected string - The actual string - The index in the strings at which comparison should start - Boolean indicating whether case should be ignored - -1 if no mismatch found, or the index where mismatch found - - - - NaNConstraint tests that the actual value is a double or float NaN - - - - - Test that the actual value is an NaN - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - NoItemConstraint applies another constraint to each - item in a collection, failing if any of them succeeds. - - - - - Construct a SomeItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - failing if any item fails. - - - - - - - NotConstraint negates the effect of some other constraint - - - - - Initializes a new instance of the class. - - The base constraint to be negated. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for if the base constraint fails, false if it succeeds - - - - NullConstraint tests that the actual value is null - - - - - Initializes a new instance of the class. - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - The Numerics class contains common operations on numeric _values. - - - - - Checks the type of the object, returning true if - the object is a numeric type. - - The object to check - true if the object is a numeric type - - - - Checks the type of the object, returning true if - the object is a floating point numeric type. - - The object to check - true if the object is a floating point numeric type - - - - Checks the type of the object, returning true if - the object is a fixed point numeric type. - - The object to check - true if the object is a fixed point numeric type - - - - Test two numeric _values for equality, performing the usual numeric - conversions and using a provided or default tolerance. If the tolerance - provided is Empty, this method may set it to a default tolerance. - - The expected value - The actual value - A reference to the tolerance in effect - True if the _values are equal - - - - Compare two numeric _values, performing the usual numeric conversions. - - The expected value - The actual value - The relationship of the _values to each other - - - - NUnitComparer encapsulates NUnit's default behavior - in comparing two objects. - - - - - Compares two objects - - - - - - - - Returns the default NUnitComparer. - - - - - NUnitEqualityComparer encapsulates NUnit's handling of - equality tests between objects. - - - - - If true, all string comparisons will ignore case - - - - - If true, arrays will be treated as collections, allowing - those of different dimensions to be compared - - - - - Comparison objects used in comparisons for some constraints. - - - - - List of points at which a failure occurred. - - - - - Compares two objects for equality within a tolerance. - - - - - Helper method to compare two arrays - - - - - Method to compare two DirectoryInfo objects - - first directory to compare - second directory to compare - true if equivalent, false if not - - - - Returns the default NUnitEqualityComparer - - - - - Gets and sets a flag indicating whether case should - be ignored in determining equality. - - - - - Gets and sets a flag indicating that arrays should be - compared as collections, without regard to their shape. - - - - - Gets the list of external comparers to be used to - test for equality. They are applied to members of - collections, in place of NUnit's own logic. - - - - - Gets the list of failure points for the last Match performed. - The list consists of objects to be interpreted by the caller. - This generally means that the caller may only make use of - objects it has placed on the list at a particular depthy. - - - - - Flags the comparer to include - property in comparison of two values. - - - Using this modifier does not allow to use the - modifier. - - - - - FailurePoint class represents one point of failure - in an equality test. - - - - - The location of the failure - - - - - The expected value - - - - - The actual value - - - - - Indicates whether the expected value is valid - - - - - Indicates whether the actual value is valid - - - - - Represents a constraint that succeeds if all the - members of a collection match a base constraint. - - - - - Abstract base for operators that indicate how to - apply a constraint to items in a collection. - - - - - PrefixOperator takes a single constraint and modifies - it's action in some way. - - - - - The ConstraintOperator class is used internally by a - ConstraintBuilder to represent an operator that - modifies or combines constraints. - - Constraint operators use left and right precedence - _values to determine whether the top operator on the - stack should be reduced before pushing a new operator. - - - - - The precedence value used when the operator - is about to be pushed to the stack. - - - - - The precedence value used when the operator - is on the top of the stack. - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - The syntax element preceding this operator - - - - - The syntax element following this operator - - - - - The precedence value used when the operator - is about to be pushed to the stack. - - - - - The precedence value used when the operator - is on the top of the stack. - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Returns the constraint created by applying this - prefix to another constraint. - - - - - - - Constructs a CollectionOperator - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - they all succeed. - - - - - Operator that requires both it's arguments to succeed - - - - - Abstract base class for all binary operators - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Abstract method that produces a constraint by applying - the operator to its left and right constraint arguments. - - - - - Gets the left precedence of the operator - - - - - Gets the right precedence of the operator - - - - - Construct an AndOperator - - - - - Apply the operator to produce an AndConstraint - - - - - Operator that tests for the presence of a particular attribute - on a type and optionally applies further tests to the attribute. - - - - - Abstract base class for operators that are able to reduce to a - constraint whether or not another syntactic element follows. - - - - - Construct an AttributeOperator for a particular Type - - The Type of attribute tested - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - Represents a constraint that succeeds if none of the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - none of them succeed. - - - - - Negates the test of the constraint it wraps. - - - - - Constructs a new NotOperator - - - - - Returns a NotConstraint applied to its argument. - - - - - Operator that requires at least one of it's arguments to succeed - - - - - Construct an OrOperator - - - - - Apply the operator to produce an OrConstraint - - - - - Operator used to test for the presence of a named Property - on an object and optionally apply further tests to the - value of that property. - - - - - Constructs a PropOperator for a particular named property - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Gets the name of the property to which the operator applies - - - - - Represents a constraint that succeeds if any of the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - any of them succeed. - - - - - Operator that tests that an exception is thrown and - optionally applies further tests to the exception. - - - - - Construct a ThrowsOperator - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - Represents a constraint that simply wraps the - constraint provided as an argument, without any - further functionality, but which modifies the - order of evaluation because of its precedence. - - - - - Constructor for the WithOperator - - - - - Returns a constraint that wraps its argument - - - - - OrConstraint succeeds if either member succeeds - - - - - Create an OrConstraint from two other constraints - - The first constraint - The second constraint - - - - Apply the member constraints to an actual value, succeeding - succeeding as soon as one of them succeeds. - - The actual value - True if either constraint succeeded - - - - Gets text describing a constraint - - - - - PathConstraint serves as the abstract base of constraints - that operate on paths and provides several helper methods. - - - - - Construct a PathConstraint for a give expected path - - The expected path - - - - Returns the string representation of this constraint - - - - - Canonicalize the provided path - - - The path in standardized form - - - - Test whether one path in canonical form is a subpath of another path - - The first path - supposed to be the parent path - The second path - supposed to be the child path - - - - - Modifies the current instance to be case-sensitive - and returns it. - - - - - Predicate constraint wraps a Predicate in a constraint, - returning success if the predicate is true. - - - - - Construct a PredicateConstraint from a predicate - - - - - Determines whether the predicate succeeds when applied - to the actual value. - - - - - Gets text describing a constraint - - - - - PropertyConstraint extracts a named property and uses - its value as the actual value for a chained constraint. - - - - - Initializes a new instance of the class. - - The name. - The constraint to apply to the property. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - - PropertyExistsConstraint tests that a named property - exists on the object provided through Match. - - Originally, PropertyConstraint provided this feature - in addition to making optional tests on the value - of the property. The two constraints are now separate. - - - - - Initializes a new instance of the class. - - The name of the property. - - - - Test whether the property exists for a given object - - The object to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - RangeConstraint tests whether two _values are within a - specified range. - - - - - Initializes a new instance of the class. - - from must be less than or equal to true - Inclusive beginning of the range. Must be less than or equal to to. - Inclusive end of the range. Must be greater than or equal to from. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use a and returns self. - - - - - Gets text describing a constraint - - - - - RegexConstraint can test whether a string matches - the pattern provided. - - - - - Initializes a new instance of the class. - - The pattern. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - ResolvableConstraintExpression is used to represent a compound - constraint being constructed at a point where the last operator - may either terminate the expression or may have additional - qualifying constraints added to it. - - It is used, for example, for a Property element or for - an Exception element, either of which may be optionally - followed by constraints that apply to the property or - exception. - - - - - Create a new instance of ResolvableConstraintExpression - - - - - Create a new instance of ResolvableConstraintExpression, - passing in a pre-populated ConstraintBuilder. - - - - - Resolve the current expression to a Constraint - - - - - Appends an And Operator to the expression - - - - - Appends an Or operator to the expression. - - - - - ReusableConstraint wraps a constraint expression after - resolving it so that it can be reused consistently. - - - - - Construct a ReusableConstraint from a constraint expression - - The expression to be resolved and reused - - - - Converts a constraint to a ReusableConstraint - - The constraint to be converted - A ReusableConstraint - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Return the top-level constraint for this expression - - - - - - SameAsConstraint tests whether an object is identical to - the object passed to its constructor - - - - - Initializes a new instance of the class. - - The expected object. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Summary description for SamePathConstraint. - - - - - Initializes a new instance of the class. - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - SamePathOrUnderConstraint tests that one path is under another - - - - - Initializes a new instance of the class. - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - SomeItemsConstraint applies another constraint to each - item in a collection, succeeding if any of them succeeds. - - - - - Construct a SomeItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - succeeding if any item succeeds. - - - - - - - The EqualConstraintResult class is tailored for formatting - and displaying the result of an EqualConstraint. - - - - - Construct an EqualConstraintResult - - - - - Write a failure message. Overridden to provide custom - failure messages for EqualConstraint. - - The MessageWriter to write to - - - - Display the failure information for two collections that did not match. - - The MessageWriter on which to display - The expected collection. - The actual collection - The depth of this failure in a set of nested collections - - - - Displays a single line showing the types and sizes of the expected - and actual collections or arrays. If both are identical, the value is - only shown once. - - The MessageWriter on which to display - The expected collection or array - The actual collection or array - The indentation level for the message line - - - - Displays a single line showing the point in the expected and actual - arrays at which the comparison failed. If the arrays have different - structures or dimensions, both _values are shown. - - The MessageWriter on which to display - The expected array - The actual array - Index of the failure point in the underlying collections - The indentation level for the message line - - - - Display the failure information for two IEnumerables that did not match. - - The MessageWriter on which to display - The expected enumeration. - The actual enumeration - The depth of this failure in a set of nested collections - - - - StartsWithConstraint can test whether a string starts - with an expected substring. - - - - - Initializes a new instance of the class. - - The expected string - - - - Test whether the constraint is matched by the actual value. - This is a template method, which calls the IsMatch method - of the derived class. - - - - - - - SubPathConstraint tests that the actual path is under the expected path - - - - - Initializes a new instance of the class. - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - SubstringConstraint can test whether a string contains - the expected substring. - - - - - Initializes a new instance of the class. - - The expected. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - ThrowsConstraint is used to test the exception thrown by - a delegate by applying a constraint to it. - - - - - Initializes a new instance of the class, - using a constraint to be applied to the exception. - - A constraint to apply to the caught exception. - - - - Executes the code of the delegate and captures any exception. - If a non-null base constraint was provided, it applies that - constraint to the exception. - - A delegate representing the code to be tested - True if an exception is thrown and the constraint succeeds, otherwise false - - - - Converts an ActualValueDelegate to a TestDelegate - before calling the primary overload. - - - - - - - Get the actual exception thrown - used by Assert.Throws. - - - - - Gets text describing a constraint - - - - - Write the actual value for a failing constraint test to a - MessageWriter. This override only handles the special message - used when an exception is expected but none is thrown. - - The writer on which the actual value is displayed - - - - ThrowsExceptionConstraint tests that an exception has - been thrown, without any further tests. - - - - - Executes the code and returns success if an exception is thrown. - - A delegate representing the code to be tested - True if an exception is thrown, otherwise false - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - ThrowsNothingConstraint tests that a delegate does not - throw an exception. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True if no exception is thrown, otherwise false - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Gets text describing a constraint - - - - - The Tolerance class generalizes the notion of a tolerance - within which an equality test succeeds. Normally, it is - used with numeric types, but it can be used with any - type that supports taking a difference between two - objects and comparing that difference to a value. - - - - - Constructs a linear tolerance of a specified amount - - - - - Constructs a tolerance given an amount and - - - - - Tests that the current Tolerance is linear with a - numeric value, throwing an exception if it is not. - - - - - Returns a default Tolerance object, equivalent to - specifying an exact match unless - is set, in which case, the - will be used. - - - - - Returns an empty Tolerance object, equivalent to - specifying an exact match even if - is set. - - - - - Gets the for the current Tolerance - - - - - Gets the value of the current Tolerance instance. - - - - - Returns a new tolerance, using the current amount as a percentage. - - - - - Returns a new tolerance, using the current amount in Ulps - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of days. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of hours. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of minutes. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of seconds. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of milliseconds. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of clock ticks. - - - - - Returns true if the current tolerance has not been set or is using the . - - - - - Modes in which the tolerance value for a comparison can be interpreted. - - - - - The tolerance was created with a value, without specifying - how the value would be used. This is used to prevent setting - the mode more than once and is generally changed to Linear - upon execution of the test. - - - - - The tolerance is used as a numeric range within which - two compared _values are considered to be equal. - - - - - Interprets the tolerance as the percentage by which - the two compared _values my deviate from each other. - - - - - Compares two _values based in their distance in - representable numbers. - - - - - TrueConstraint tests that the actual value is true - - - - - Initializes a new instance of the class. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - UniqueItemsConstraint tests whether all the items in a - collection are unique. - - - - - Check that all items are unique. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - XmlSerializableConstraint tests whether - an object is serializable in xml format. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Returns the string representation of this constraint - - - - - Gets text describing a constraint - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new DictionaryContainsKeyConstraint checking for the - presence of a particular key in the dictionary. - - - - - Returns a new DictionaryContainsValueConstraint checking for the - presence of a particular value in the dictionary. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Asserts on Directories - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Verifies that two directories are equal. Two directories are considered - equal if both are null, or if both point to the same directory. - If they are not equal an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - The message to display if the directories are not equal - Arguments to be used in formatting the message - - - - Verifies that two directories are equal. Two directories are considered - equal if both are null, or if both point to the same directory. - If they are not equal an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - - - - Asserts that two directories are not equal. If they are equal - an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that two directories are not equal. If they are equal - an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - - - - Asserts that the directory exists. If it does not exist - an is thrown. - - A directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory exists. If it does not exist - an is thrown. - - A directory containing the actual value - - - - Asserts that the directory exists. If it does not exist - an is thrown. - - The path to a directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory exists. If it does not exist - an is thrown. - - The path to a directory containing the actual value - - - - Asserts that the directory does not exist. If it does exist - an is thrown. - - A directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory does not exist. If it does exist - an is thrown. - - A directory containing the actual value - - - - Asserts that the directory does not exist. If it does exist - an is thrown. - - The path to a directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory does not exist. If it does exist - an is thrown. - - The path to a directory containing the actual value - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a constraint that succeeds if the value - is a file or directory and it exists. - - - - - Thrown when an assertion failed. - - - - - Abstract base for Exceptions that terminate a test and provide a ResultState. - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - Thrown when an assertion failed. - - - - - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - Thrown when a test executes inconclusively. - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - Thrown when an assertion failed. - - - - - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - CombiningStrategy is the abstract base for classes that - know how to combine values provided for individual test - parameters to create a set of test cases. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - Objects implementing this interface are used to wrap - the TestMethodCommand itself. They apply after SetUp - has been run and before TearDown. - - - - - Any ITest that implements this interface is at a level that the implementing - class should be disposed at the end of the test run - - - - - The IMethodInfo class is used to encapsulate information - about a method in a platform-independent manner. - - - - - The IReflectionInfo interface is implemented by NUnit wrapper objects that perform reflection. - - - - - Returns an array of custom attributes of the specified type applied to this object - - - - - Returns a value indicating whether an attribute of the specified type is defined on this object. - - - - - Gets the parameters of the method. - - - - - - Returns the Type arguments of a generic method or the Type parameters of a generic method definition. - - - - - Replaces the type parameters of the method with the array of types provided and returns a new IMethodInfo. - - The type arguments to be used - A new IMethodInfo with the type arguments replaced - - - - Invokes the method, converting any TargetInvocationException to an NUnitException. - - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - Gets the Type from which this method was reflected. - - - - - Gets the MethodInfo for this method. - - - - - Gets the name of the method. - - - - - Gets a value indicating whether the method is abstract. - - - - - Gets a value indicating whether the method is public. - - - - - Gets a value indicating whether the method contains unassigned generic type parameters. - - - - - Gets a value indicating whether the method is a generic method. - - - - - Gets a value indicating whether the MethodInfo represents the definition of a generic method. - - - - - Gets the return Type of the method. - - - - - The IDataPointProvider interface is used by extensions - that provide data for a single test parameter. - - - - - Determine whether any data is available for a parameter. - - An IParameterInfo representing one - argument to a parameterized test - True if any data is available, otherwise false. - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - An IParameterInfo representing one - argument to a parameterized test - An IEnumerable providing the required data - - - - Asserts on Files - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Verifies that two Streams are equal. Two Streams are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The expected Stream - The actual Stream - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Verifies that two Streams are equal. Two Streams are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The expected Stream - The actual Stream - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A file containing the value that is expected - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A file containing the value that is expected - A file containing the actual value - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - - - - Asserts that two Streams are not equal. If they are equal - an is thrown. - - The expected Stream - The actual Stream - The message to be displayed when the two Stream are the same. - Arguments to be used in formatting the message - - - - Asserts that two Streams are not equal. If they are equal - an is thrown. - - The expected Stream - The actual Stream - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - A file containing the value that is expected - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - A file containing the value that is expected - A file containing the actual value - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - - - - Asserts that the file exists. If it does not exist - an is thrown. - - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that the file exists. If it does not exist - an is thrown. - - A file containing the actual value - - - - Asserts that the file exists. If it does not exist - an is thrown. - - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that the file exists. If it does not exist - an is thrown. - - The path to a file containing the actual value - - - - Asserts that the file does not exist. If it does exist - an is thrown. - - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that the file does not exist. If it does exist - an is thrown. - - A file containing the actual value - - - - Asserts that the file does not exist. If it does exist - an is thrown. - - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that the file does not exist. If it does exist - an is thrown. - - The path to a file containing the actual value - - - - GlobalSettings is a place for setting default _values used - by the framework in performing asserts. - - - - - Default tolerance for floating point equality - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - The IParameterInfo interface is an abstraction of a .NET parameter. - - - - - Gets a value indicating whether the parameter is optional - - - - - Gets an IMethodInfo representing the method for which this is a parameter - - - - - Gets the underlying .NET ParameterInfo - - - - - Gets the Type of the parameter - - - - - A PropertyBag represents a collection of name/value pairs - that allows duplicate entries with the same key. Methods - are provided for adding a new pair as well as for setting - a key to a single value. All keys are strings but _values - may be of any type. Null _values are not permitted, since - a null entry represents the absence of the key. - - The entries in a PropertyBag are of two kinds: those that - take a single value and those that take multiple _values. - However, the PropertyBag has no knowledge of which entries - fall into each category and the distinction is entirely - up to the code using the PropertyBag. - - When working with multi-valued properties, client code - should use the Add method to add name/value pairs and - indexing to retrieve a list of all _values for a given - key. For example: - - bag.Add("Tag", "one"); - bag.Add("Tag", "two"); - Assert.That(bag["Tag"], - Is.EqualTo(new string[] { "one", "two" })); - - When working with single-valued propeties, client code - should use the Set method to set the value and Get to - retrieve the value. The GetSetting methods may also be - used to retrieve the value in a type-safe manner while - also providing default. For example: - - bag.Set("Priority", "low"); - bag.Set("Priority", "high"); // replaces value - Assert.That(bag.Get("Priority"), - Is.EqualTo("high")); - Assert.That(bag.GetSetting("Priority", "low"), - Is.EqualTo("high")); - - - - - An object implementing IXmlNodeBuilder is able to build - an XML representation of itself and any children. - - - - - Returns a TNode representing the current object. - - If true, children are included where applicable - A TNode representing the result - - - - Returns a TNode representing the current object after - adding it as a child of the supplied parent node. - - The parent node. - If true, children are included, where applicable - - - - - Adds a key/value pair to the property bag - - The key - The value - - - - Sets the value for a key, removing any other - _values that are already in the property set. - - - - - - - Gets a single value for a key, using the first - one if multiple _values are present and returning - null if the value is not found. - - - - - Gets a flag indicating whether the specified key has - any entries in the property set. - - The key to be checked - True if their are _values present, otherwise false - - - - Gets or sets the list of _values for a particular key - - The key for which the _values are to be retrieved or set - - - - Gets a collection containing all the keys in the property set - - - - - The ISuiteBuilder interface is exposed by a class that knows how to - build a suite from one or more Types. - - - - - Examine the type and determine if it is suitable for - this builder to use in building a TestSuite. - - Note that returning false will cause the type to be ignored - in loading the tests. If it is desired to load the suite - but label it as non-runnable, ignored, etc., then this - method must return true. - - The type of the fixture to be used - True if the type can be used to build a TestSuite - - - - Build a TestSuite from type provided. - - The type of the fixture to be used - A TestSuite - - - - Common interface supported by all representations - of a test. Only includes informational fields. - The Run method is specifically excluded to allow - for data-only representations of a test. - - - - - Gets the id of the test - - - - - Gets the name of the test - - - - - Gets the fully qualified name of the test - - - - - Gets the name of the class containing this test. Returns - null if the test is not associated with a class. - - - - - Gets the name of the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the Type of the test fixture, if applicable, or - null if no fixture type is associated with this test. - - - - - Gets an IMethod for the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the RunState of the test, indicating whether it can be run. - - - - - Count of the test cases ( 1 if this is a test case ) - - - - - Gets the properties of the test - - - - - Gets the parent test, if any. - - The parent test or null if none exists. - - - - Returns true if this is a test suite - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets a fixture object for running this test. - - - - - The ITestCaseBuilder interface is exposed by a class that knows how to - build a test case from certain methods. - - - This interface is not the same as the ITestCaseBuilder interface in NUnit 2.x. - We have reused the name because the two products don't interoperate at all. - - - - - Examine the method and determine if it is suitable for - this builder to use in building a TestCase to be - included in the suite being populated. - - Note that returning false will cause the method to be ignored - in loading the tests. If it is desired to load the method - but label it as non-runnable, ignored, etc., then this - method must return true. - - The test method to examine - The suite being populated - True is the builder can use this method - - - - Build a TestCase from the provided MethodInfo for - inclusion in the suite being constructed. - - The method to be used as a test case - The test suite being populated, or null - A TestCase or null - - - - Interface to be implemented by filters applied to tests. - The filter applies when running the test, after it has been - loaded, since this is the only time an ITest exists. - - - - - Determine if a particular test passes the filter criteria. Pass - may examine the parents and/or descendants of a test, depending - on the semantics of the particular filter - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - The ITestListener interface is used internally to receive - notifications of significant events while a test is being - run. The events are propagated to clients by means of an - AsyncCallback. NUnit extensions may also monitor these events. - - - - - Called when a test has just started - - The test that is starting - - - - Called when a test has finished - - The result of the test - - - - The ITestResult interface represents the result of a test. - - - - - Gets the ResultState of the test result, which - indicates the success or failure of the test. - - - - - Gets the name of the test result - - - - - Gets the full name of the test result - - - - - Gets the elapsed time for running the test in seconds - - - - - Gets or sets the time the test started running. - - - - - Gets or sets the time the test finished running. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. Not available in - the Compact Framework 1.0. - - - - - Gets the number of asserts executed - when running the test and all its children. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Indicates whether this result has any child results. - Accessing HasChildren should not force creation of the - Children collection in classes implementing this interface. - - - - - Gets the the collection of child results. - - - - - Gets the Test to which this result applies. - - - - - Gets any text output written to this result. - - - - - The ITypeInfo interface is an abstraction of a .NET Type - - - - - Returns true if the Type wrapped is equal to the argument - - - - - Get the display name for this typeInfo. - - - - - Get the display name for an oject of this type, constructed with specific arguments - - - - - Returns a Type representing a generic type definition from which this Type can be constructed. - - - - - Returns a new ITypeInfo representing an instance of this generic Type using the supplied Type arguments - - - - - Returns a value indicating whether this type has a method with a specified public attribute - - - - - Returns an array of IMethodInfos for methods of this Type - that match the specified flags. - - - - - Returns a value indicating whether this Type has a public constructor taking the specified argument Types. - - - - - Construct an object of this Type, using the specified arguments. - - - - - Gets the underlying Type on which this ITypeInfo is based - - - - - Gets the base type of this type as an ITypeInfo - - - - - Gets the Name of the Type - - - - - Gets the FullName of the Type - - - - - Gets the assembly in which the type is declared - - - - - Gets the Namespace of the Type - - - - - Gets a value indicating whether the type is abstract. - - - - - Gets a value indicating whether the Type is a generic Type - - - - - Gets a value indicating whether the Type has generic parameters that have not been replaced by specific Types. - - - - - Gets a value indicating whether the Type is a generic Type definition - - - - - Gets a value indicating whether the type is sealed. - - - - - Gets a value indicating whether this type is a static class. - - - - - The ResultState class represents the outcome of running a test. - It contains two pieces of information. The Status of the test - is an enum indicating whether the test passed, failed, was - skipped or was inconclusive. The Label provides a more - detailed breakdown for use by client runners. - - - - - Initializes a new instance of the class. - - The TestStatus. - - - - Initializes a new instance of the class. - - The TestStatus. - The label. - - - - Initializes a new instance of the class. - - The TestStatus. - The stage at which the result was produced - - - - Initializes a new instance of the class. - - The TestStatus. - The label. - The stage at which the result was produced - - - - The result is inconclusive - - - - - The test has been skipped. - - - - - The test has been ignored. - - - - - The test was skipped because it is explicit - - - - - The test succeeded - - - - - The test failed - - - - - The test encountered an unexpected exception - - - - - The test was cancelled by the user - - - - - The test was not runnable. - - - - - A suite failed because one or more child tests failed or had errors - - - - - A suite failed in its OneTimeSetUp - - - - - A suite had an unexpected exception in its OneTimeSetUp - - - - - A suite had an unexpected exception in its OneTimeDown - - - - - Get a new ResultState, which is the same as the current - one but with the FailureSite set to the specified value. - - The FailureSite to use - A new ResultState - - - - Determines whether the specified , is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Returns a hash code for this instance. - - - A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Gets the TestStatus for the test. - - The status. - - - - Gets the label under which this test result is - categorized, if any. - - - - - Gets the stage of test execution in which - the failure or other result took place. - - - - - The FailureSite enum indicates the stage of a test - in which an error or failure occurred. - - - - - Failure in the test itself - - - - - Failure in the SetUp method - - - - - Failure in the TearDown method - - - - - Failure of a parent test - - - - - Failure of a child test - - - - - The RunState enum indicates whether a test can be executed. - - - - - The test is not runnable. - - - - - The test is runnable. - - - - - The test can only be run explicitly - - - - - The test has been skipped. This value may - appear on a Test when certain attributes - are used to skip the test. - - - - - The test has been ignored. May appear on - a Test, when the IgnoreAttribute is used. - - - - - The TestStatus enum indicates the result of running a test - - - - - The test was inconclusive - - - - - The test has skipped - - - - - The test succeeded - - - - - The test failed - - - - - TNode represents a single node in the XML representation - of a Test or TestResult. It replaces System.Xml.XmlNode and - System.Xml.Linq.XElement, providing a minimal set of methods - for operating on the XML in a platform-independent manner. - - - - - Constructs a new instance of TNode - - The name of the node - - - - Constructs a new instance of TNode with a value - - The name of the node - The text content of the node - - - - Constructs a new instance of TNode with a value - - The name of the node - The text content of the node - Flag indicating whether to use CDATA when writing the text - - - - Create a TNode from it's XML text representation - - The XML text to be parsed - A TNode - - - - Adds a new element as a child of the current node and returns it. - - The element name. - The newly created child element - - - - Adds a new element with a value as a child of the current node and returns it. - - The element name - The text content of the new element - The newly created child element - - - - Adds a new element with a value as a child of the current node and returns it. - The value will be output using a CDATA section. - - The element name - The text content of the new element - The newly created child element - - - - Adds an attribute with a specified name and value to the XmlNode. - - The name of the attribute. - The value of the attribute. - - - - Finds a single descendant of this node matching an xpath - specification. The format of the specification is - limited to what is needed by NUnit and its tests. - - - - - - - Finds all descendants of this node matching an xpath - specification. The format of the specification is - limited to what is needed by NUnit and its tests. - - - - - Writes the XML representation of the node to an XmlWriter - - - - - - Gets the name of the node - - - - - Gets the value of the node - - - - - Gets a flag indicating whether the value should be output using CDATA. - - - - - Gets the dictionary of attributes - - - - - Gets a list of child nodes - - - - - Gets the first ChildNode - - - - - Gets the XML representation of this node. - - - - - Class used to represent a list of XmlResults - - - - - Class used to represent the attributes of a node - - - - - Gets or sets the value associated with the specified key. - Overridden to return null if attribute is not found. - - The key. - Value of the attribute or null - - - - Waits for pending asynchronous operations to complete, if appropriate, - and returns a proper result of the invocation by unwrapping task results - - The raw result of the method invocation - The unwrapped result, if necessary - - - - CombinatorialStrategy creates test cases by using all possible - combinations of the parameter data. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - Provides data from fields marked with the DatapointAttribute or the - DatapointsAttribute. - - - - - Determine whether any data is available for a parameter. - - A ParameterInfo representing one - argument to a parameterized test - - True if any data is available, otherwise false. - - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - A ParameterInfo representing one - argument to a parameterized test - - An IEnumerable providing the required data - - - - - Class to build ether a parameterized or a normal NUnitTestMethod. - There are four cases that the builder must deal with: - 1. The method needs no params and none are provided - 2. The method needs params and they are provided - 3. The method needs no params but they are provided in error - 4. The method needs params but they are not provided - This could have been done using two different builders, but it - turned out to be simpler to have just one. The BuildFrom method - takes a different branch depending on whether any parameters are - provided, but all four cases are dealt with in lower-level methods - - - - - Determines if the method can be used to build an NUnit test - test method of some kind. The method must normally be marked - with an identifying attribute for this to be true. - - Note that this method does not check that the signature - of the method for validity. If we did that here, any - test methods with invalid signatures would be passed - over in silence in the test run. Since we want such - methods to be reported, the check for validity is made - in BuildFrom rather than here. - - An IMethodInfo for the method being used as a test method - True if the builder can create a test case from this method - - - - Build a Test from the provided MethodInfo. Depending on - whether the method takes arguments and on the availability - of test case data, this method may return a single test - or a group of tests contained in a ParameterizedMethodSuite. - - The method for which a test is to be built - A Test representing one or more method invocations - - - - Determines if the method can be used to build an NUnit test - test method of some kind. The method must normally be marked - with an identifying attribute for this to be true. - - Note that this method does not check that the signature - of the method for validity. If we did that here, any - test methods with invalid signatures would be passed - over in silence in the test run. Since we want such - methods to be reported, the check for validity is made - in BuildFrom rather than here. - - An IMethodInfo for the method being used as a test method - The test suite being built, to which the new test would be added - True if the builder can create a test case from this method - - - - Build a Test from the provided MethodInfo. Depending on - whether the method takes arguments and on the availability - of test case data, this method may return a single test - or a group of tests contained in a ParameterizedMethodSuite. - - The method for which a test is to be built - The test fixture being populated, or null - A Test representing one or more method invocations - - - - Builds a ParameterizedMethodSuite containing individual test cases. - - The method for which a test is to be built. - The list of test cases to include. - A ParameterizedMethodSuite populated with test cases - - - - Build a simple, non-parameterized TestMethod for this method. - - The MethodInfo for which a test is to be built - The test suite for which the method is being built - A TestMethod. - - - - Class that can build a tree of automatic namespace - suites from a group of fixtures. - - - - - NamespaceDictionary of all test suites we have created to represent - namespaces. Used to locate namespace parent suites for fixtures. - - - - - The root of the test suite being created by this builder. - - - - - Initializes a new instance of the class. - - The root suite. - - - - Adds the specified fixtures to the tree. - - The fixtures to be added. - - - - Adds the specified fixture to the tree. - - The fixture to be added. - - - - Gets the root entry in the tree created by the NamespaceTreeBuilder. - - The root suite. - - - - NUnitTestCaseBuilder is a utility class used by attributes - that build test cases. - - - - - Constructs an - - - - - Builds a single NUnitTestMethod, either as a child of the fixture - or as one of a set of test cases under a ParameterizedTestMethodSuite. - - The MethodInfo from which to construct the TestMethod - The suite or fixture to which the new test will be added - The ParameterSet to be used, or null - - - - - Helper method that checks the signature of a TestMethod and - any supplied parameters to determine if the test is valid. - - Currently, NUnitTestMethods are required to be public, - non-abstract methods, either static or instance, - returning void. They may take arguments but the _values must - be provided or the TestMethod is not considered runnable. - - Methods not meeting these criteria will be marked as - non-runnable and the method will return false in that case. - - The TestMethod to be checked. If it - is found to be non-runnable, it will be modified. - Parameters to be used for this test, or null - True if the method signature is valid, false if not - - The return value is no longer used internally, but is retained - for testing purposes. - - - - - NUnitTestFixtureBuilder is able to build a fixture given - a class marked with a TestFixtureAttribute or an unmarked - class containing test methods. In the first case, it is - called by the attribute and in the second directly by - NUnitSuiteBuilder. - - - - - Build a TestFixture from type provided. A non-null TestSuite - must always be returned, since the method is generally called - because the user has marked the target class as a fixture. - If something prevents the fixture from being used, it should - be returned nonetheless, labelled as non-runnable. - - An ITypeInfo for the fixture to be used. - A TestSuite object or one derived from TestSuite. - - - - Overload of BuildFrom called by tests that have arguments. - Builds a fixture using the provided type and information - in the ITestFixtureData object. - - The TypeInfo for which to construct a fixture. - An object implementing ITestFixtureData or null. - - - - - Method to add test cases to the newly constructed fixture. - - The fixture to which cases should be added - - - - Method to create a test case from a MethodInfo and add - it to the fixture being built. It first checks to see if - any global TestCaseBuilder addin wants to build the - test case. If not, it uses the internal builder - collection maintained by this fixture builder. - - The default implementation has no test case builders. - Derived classes should add builders to the collection - in their constructor. - - The method for which a test is to be created - The test suite being built. - A newly constructed Test - - - - Built-in SuiteBuilder for all types of test classes. - - - - - Checks to see if the provided Type is a fixture. - To be considered a fixture, it must be a non-abstract - class with one or more attributes implementing the - IFixtureBuilder interface or one or more methods - marked as tests. - - The fixture type to check - True if the fixture can be built, false if not - - - - Build a TestSuite from TypeInfo provided. - - The fixture type to build - A TestSuite built from that type - - - - We look for attributes implementing IFixtureBuilder at one level - of inheritance at a time. Attributes on base classes are not used - unless there are no fixture builder attributes at all on the derived - class. This is by design. - - The type being examined for attributes - A list of the attributes found. - - - - PairwiseStrategy creates test cases by combining the parameter - data so that all possible pairs of data items are used. - - - - The number of test cases that cover all possible pairs of test function - parameters values is significantly less than the number of test cases - that cover all possible combination of test function parameters values. - And because different studies show that most of software failures are - caused by combination of no more than two parameters, pairwise testing - can be an effective ways to test the system when it's impossible to test - all combinations of parameters. - - - The PairwiseStrategy code is based on "jenny" tool by Bob Jenkins: - http://burtleburtle.net/bob/math/jenny.html - - - - - - Gets the test cases generated by this strategy instance. - - A set of test cases. - - - - FleaRand is a pseudo-random number generator developed by Bob Jenkins: - http://burtleburtle.net/bob/rand/talksmall.html#flea - - - - - Initializes a new instance of the FleaRand class. - - The seed. - - - - FeatureInfo represents coverage of a single value of test function - parameter, represented as a pair of indices, Dimension and Feature. In - terms of unit testing, Dimension is the index of the test parameter and - Feature is the index of the supplied value in that parameter's list of - sources. - - - - - Initializes a new instance of FeatureInfo class. - - Index of a dimension. - Index of a feature. - - - - A FeatureTuple represents a combination of features, one per test - parameter, which should be covered by a test case. In the - PairwiseStrategy, we are only trying to cover pairs of features, so the - tuples actually may contain only single feature or pair of features, but - the algorithm itself works with triplets, quadruples and so on. - - - - - Initializes a new instance of FeatureTuple class for a single feature. - - Single feature. - - - - Initializes a new instance of FeatureTuple class for a pair of features. - - First feature. - Second feature. - - - - TestCase represents a single test case covering a list of features. - - - - - Initializes a new instance of TestCaseInfo class. - - A number of features in the test case. - - - - PairwiseTestCaseGenerator class implements an algorithm which generates - a set of test cases which covers all pairs of possible values of test - function. - - - - The algorithm starts with creating a set of all feature tuples which we - will try to cover (see method). This set - includes every single feature and all possible pairs of features. We - store feature tuples in the 3-D collection (where axes are "dimension", - "feature", and "all combinations which includes this feature"), and for - every two feature (e.g. "A" and "B") we generate both ("A", "B") and - ("B", "A") pairs. This data structure extremely reduces the amount of - time needed to calculate coverage for a single test case (this - calculation is the most time-consuming part of the algorithm). - - - Then the algorithm picks one tuple from the uncovered tuple, creates a - test case that covers this tuple, and then removes this tuple and all - other tuples covered by this test case from the collection of uncovered - tuples. - - - Picking a tuple to cover - - - There are no any special rules defined for picking tuples to cover. We - just pick them one by one, in the order they were generated. - - - Test generation - - - Test generation starts from creating a completely random test case which - covers, nevertheless, previously selected tuple. Then the algorithm - tries to maximize number of tuples which this test covers. - - - Test generation and maximization process repeats seven times for every - selected tuple and then the algorithm picks the best test case ("seven" - is a magic number which provides good results in acceptable time). - - Maximizing test coverage - - To maximize tests coverage, the algorithm walks thru the list of mutable - dimensions (mutable dimension is a dimension that are not included in - the previously selected tuple). Then for every dimension, the algorithm - walks thru the list of features and checks if this feature provides - better coverage than randomly selected feature, and if yes keeps this - feature. - - - This process repeats while it shows progress. If the last iteration - doesn't improve coverage, the process ends. - - - In addition, for better results, before start every iteration, the - algorithm "scrambles" dimensions - so for every iteration dimension - probes in a different order. - - - - - - Creates a set of test cases for specified dimensions. - - - An array which contains information about dimensions. Each element of - this array represents a number of features in the specific dimension. - - - A set of test cases. - - - - - ParameterDataProvider supplies individual argument _values for - single parameters using attributes derived from DataAttribute. - - - - - Determine whether any data is available for a parameter. - - A ParameterInfo representing one - argument to a parameterized test - - True if any data is available, otherwise false. - - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - An IParameterInfo representing one - argument to a parameterized test - - An IEnumerable providing the required data - - - - - SequentialStrategy creates test cases by using all of the - parameter data sources in parallel, substituting null - when any of them run out of data. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - ContextSettingsCommand applies specified changes to the - TestExecutionContext prior to running a test. No special - action is needed after the test runs, since the prior - context will be restored automatically. - - - - - The CommandStage enumeration represents the defined stages - of execution for a series of TestCommands. The int _values - of the enum are used to apply decorators in the proper - order. Lower _values are applied first and are therefore - "closer" to the actual test execution. - - - No CommandStage is defined for actual invocation of the test or - for creation of the context. Execution may be imagined as - proceeding from the bottom of the list upwards, with cleanup - after the test running in the opposite order. - - - - - Use an application-defined default value. - - - - - Make adjustments needed before and after running - the raw test - that is, after any SetUp has run - and before TearDown. - - - - - Run SetUp and TearDown for the test. This stage is used - internally by NUnit and should not normally appear - in user-defined decorators. - - - - - Make adjustments needed before and after running - the entire test - including SetUp and TearDown. - - - - - TODO: Documentation needed for class - - - - - Initializes a new instance of the class. - - The inner command. - The max time allowed in milliseconds - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext - - The context in which the test should run. - A TestResult - - - - OneTimeSetUpCommand runs any one-time setup methods for a suite, - constructing the user test object if necessary. - - - - - Constructs a OneTimeSetUpCommand for a suite - - The suite to which the command applies - A SetUpTearDownList for use by the command - A List of TestActionItems to be run after Setup - - - - Overridden to run the one-time setup for a suite. - - The TestExecutionContext to be used. - A TestResult - - - - OneTimeTearDownCommand performs any teardown actions - specified for a suite and calls Dispose on the user - test object, if any. - - - - - Construct a OneTimeTearDownCommand - - The test suite to which the command applies - A SetUpTearDownList for use by the command - A List of TestActionItems to be run before teardown. - - - - Overridden to run the teardown methods specified on the test. - - The TestExecutionContext to be used. - A TestResult - - - - SetUpTearDownCommand runs any SetUp methods for a suite, - runs the test and then runs any TearDown methods. - - - - - Initializes a new instance of the class. - - The inner command. - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - SetUpTearDownItem holds the setup and teardown methods - for a single level of the inheritance hierarchy. - - - - - Construct a SetUpTearDownNode - - A list of setup methods for this level - A list teardown methods for this level - - - - Run SetUp on this level. - - The execution context to use for running. - - - - Run TearDown for this level. - - - - - - Returns true if this level has any methods at all. - This flag is used to discard levels that do nothing. - - - - - TODO: Documentation needed for class - - - - - Initializes a new instance of the class. - - The test being skipped. - - - - Overridden to simply set the CurrentResult to the - appropriate Skipped state. - - The execution context for the test - A TestResult - - - - TestActionCommand runs the BeforeTest actions for a test, - then runs the test and finally runs the AfterTestActions. - - - - - Initializes a new instance of the class. - - The inner command. - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - TestActionItem represents a single execution of an - ITestAction. It is used to track whether the BeforeTest - method has been called and suppress calling the - AfterTest method if it has not. - - - - - Construct a TestActionItem - - The ITestAction to be included - - - - Run the BeforeTest method of the action and remember that it has been run. - - The test to which the action applies - - - - Run the AfterTest action, but only if the BeforeTest - action was actually run. - - The test to which the action applies - - - - TestMethodCommand is the lowest level concrete command - used to run actual test cases. - - - - - Initializes a new instance of the class. - - The test. - - - - Runs the test, saving a TestResult in the execution context, as - well as returning it. If the test has an expected result, it - is asserts on that value. Since failed tests and errors throw - an exception, this command must be wrapped in an outer command, - will handle that exception and records the failure. This role - is usually played by the SetUpTearDown command. - - The execution context - - - - TheoryResultCommand adjusts the result of a Theory so that - it fails if all the results were inconclusive. - - - - - Constructs a TheoryResultCommand - - The command to be wrapped by this one - - - - Overridden to call the inner command and adjust the result - in case all chlid results were inconclusive. - - - - - - - CultureDetector is a helper class used by NUnit to determine - whether a test should be run based on the current culture. - - - - - Default constructor uses the current culture. - - - - - Construct a CultureDetector for a particular culture for testing. - - The culture to be used - - - - Test to determine if one of a collection of cultures - is being used currently. - - - - - - - Tests to determine if the current culture is supported - based on a culture attribute. - - The attribute to examine - - - - - Test to determine if the a particular culture or comma- - delimited set of cultures is in use. - - Name of the culture or comma-separated list of culture ids - True if the culture is in use on the system - - - - Return the last failure reason. Results are not - defined if called before IsSupported( Attribute ) - is called. - - - - - ExceptionHelper provides static methods for working with exceptions - - - - - Builds up a message, using the Message field of the specified exception - as well as any InnerExceptions. - - The exception. - A combined message string. - - - - Builds up a message, using the Message field of the specified exception - as well as any InnerExceptions. - - The exception. - A combined stack trace. - - - - Gets the stack trace of the exception. - - The exception. - A string representation of the stack trace. - - - - A utility class to create TestCommands - - - - - Gets the command to be executed before any of - the child tests are run. - - A TestCommand - - - - Gets the command to be executed after all of the - child tests are run. - - A TestCommand - - - - Creates a test command for use in running this test. - - - - - - Creates a command for skipping a test. The result returned will - depend on the test RunState. - - - - - Builds the set up tear down list. - - Type of the fixture. - Type of the set up attribute. - Type of the tear down attribute. - A list of SetUpTearDownItems - - - - A CompositeWorkItem represents a test suite and - encapsulates the execution of the suite as well - as all its child tests. - - - - - A WorkItem may be an individual test case, a fixture or - a higher level grouping of tests. All WorkItems inherit - from the abstract WorkItem class, which uses the template - pattern to allow derived classes to perform work in - whatever way is needed. - - A WorkItem is created with a particular TestExecutionContext - and is responsible for re-establishing that context in the - current thread before it begins or resumes execution. - - - - - Creates a work item. - - The test for which this WorkItem is being created. - The filter to be used in selecting any child Tests. - - - - - Construct a WorkItem for a particular test. - - The test that the WorkItem will run - - - - Initialize the TestExecutionContext. This must be done - before executing the WorkItem. - - - Originally, the context was provided in the constructor - but delaying initialization of the context until the item - is about to be dispatched allows changes in the parent - context during OneTimeSetUp to be reflected in the child. - - The TestExecutionContext to use - - - - Execute the current work item, including any - child work items. - - - - - Method that performs actually performs the work. It should - set the State to WorkItemState.Complete when done. - - - - - Method called by the derived class when all work is complete - - - - - Event triggered when the item is complete - - - - - Gets the current state of the WorkItem - - - - - The test being executed by the work item - - - - - The execution context - - - - - The test actions to be performed before and after this test - - - - - Indicates whether this WorkItem may be run in parallel - - - - - The test result - - - - - Construct a CompositeWorkItem for executing a test suite - using a filter to select child tests. - - The TestSuite to be executed - A filter used to select child tests - - - - Method that actually performs the work. Overridden - in CompositeWorkItem to do setup, run all child - items and then do teardown. - - - - - The EventPumpState enum represents the state of an - EventPump. - - - - - The pump is stopped - - - - - The pump is pumping events with no stop requested - - - - - The pump is pumping events but a stop has been requested - - - - - EventPump pulls events out of an EventQueue and sends - them to a listener. It is used to send events back to - the client without using the CallContext of the test - runner thread. - - - - - The handle on which a thread enqueuing an event with == true - waits, until the EventPump has sent the event to its listeners. - - - - - The downstream listener to which we send events - - - - - The queue that holds our events - - - - - Thread to do the pumping - - - - - The current state of the eventpump - - - - - Constructor - - The EventListener to receive events - The event queue to pull events from - - - - Dispose stops the pump - Disposes the used WaitHandle, too. - - - - - Start the pump - - - - - Tell the pump to stop after emptying the queue. - - - - - Our thread proc for removing items from the event - queue and sending them on. Note that this would - need to do more locking if any other thread were - removing events from the queue. - - - - - Gets or sets the current state of the pump - - - On volatile and , see - "http://www.albahari.com/threading/part4.aspx". - - - - - Gets or sets the name of this EventPump - (used only internally and for testing). - - - - - NUnit.Core.Event is the abstract base for all stored events. - An Event is the stored representation of a call to the - ITestListener interface and is used to record such calls - or to queue them for forwarding on another thread or at - a later time. - - - - - The Send method is implemented by derived classes to send the event to the specified listener. - - The listener. - - - - Gets a value indicating whether this event is delivered synchronously by the NUnit . - - If true, and if has been used to - set a WaitHandle, blocks its calling thread until the - thread has delivered the event and sets the WaitHandle. - - - - - - TestStartedEvent holds information needed to call the TestStarted method. - - - - - Initializes a new instance of the class. - - The test. - - - - Calls TestStarted on the specified listener. - - The listener. - - - - TestFinishedEvent holds information needed to call the TestFinished method. - - - - - Initializes a new instance of the class. - - The result. - - - - Calls TestFinished on the specified listener. - - The listener. - - - - Implements a queue of work items each of which - is queued as a WaitCallback. - - - - - Construct a new EventQueue - - - - - WaitHandle for synchronous event delivery in . - - Having just one handle for the whole implies that - there may be only one producer (the test thread) for synchronous events. - If there can be multiple producers for synchronous events, one would have - to introduce one WaitHandle per event. - - - - - - Sets a handle on which to wait, when is called - for an with == true. - - - The wait handle on which to wait, when is called - for an with == true. - The caller is responsible for disposing this wait handle. - - - - - Enqueues the specified event - - The event to enqueue. - - - - Removes the first element from the queue and returns it (or null). - - - If true and the queue is empty, the calling thread is blocked until - either an element is enqueued, or is called. - - - - - If the queue not empty - the first element. - - - otherwise, if ==false - or has been called - null. - - - - - - - Stop processing of the queue - - - - - Gets the count of items in the queue. - - - - - An IWorkItemDispatcher handles execution of work items. - - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and used when stopping the run. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - ParallelWorkItemDispatcher handles execution of work items by - queuing them for worker threads to process. - - - - - Construct a ParallelWorkItemDispatcher - - Number of workers to use - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and used when stopping the run. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - Enumerates all the shifts supported by the dispatcher - - - - - QueuingEventListener uses an EventQueue to store any - events received on its EventListener interface. - - - - - A test has started - - The test that is starting - - - - A test case finished - - Result of the test case - - - - The EvenQueue created and filled by this listener - - - - - A SimpleWorkItem represents a single test case and is - marked as completed immediately upon execution. This - class is also used for skipped or ignored test suites. - - - - - Construct a simple work item for a test. - - The test to be executed - The filter used to select this test - - - - Method that performs actually performs the work. - - - - - SimpleWorkItemDispatcher handles execution of WorkItems by - directly executing them. It is provided so that a dispatcher - is always available in the context, thereby simplifying the - code needed to run child tests. - - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and a thread is created on which to - run it. Subsequent calls come from the top level - item or its descendants on the proper thread. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - A TestWorker pulls work items from a queue - and executes them. - - - - - Construct a new TestWorker. - - The queue from which to pull work items - The name of this worker - The apartment state to use for running tests - - - - Our ThreadProc, which pulls and runs tests in a loop - - - - - Start processing work items. - - - - - Stop the thread, either immediately or after finishing the current WorkItem - - - - - Event signaled immediately before executing a WorkItem - - - - - Event signaled immediately after executing a WorkItem - - - - - The name of this worker - also used for the thread - - - - - Indicates whether the worker thread is running - - - - - The TextCapture class intercepts console output and writes it - to the current execution context, if one is present on the thread. - If no execution context is found, the output is written to a - default destination, normally the original destination of the - intercepted output. - - - - - Construct a TextCapture object - - The default destination for non-intercepted output - - - - Writes a single character - - The char to write - - - - Writes a string - - The string to write - - - - Writes a string followed by a line terminator - - The string to write - - - - Gets the Encoding in use by this TextWriter - - - - - TextMessageWriter writes constraint descriptions and messages - in displayable form as a text stream. It tailors the display - of individual message components to form the standard message - format of NUnit assertion failure messages. - - - - - Prefix used for the expected value line of a message - - - - - Prefix used for the actual value line of a message - - - - - Length of a message prefix - - - - - Construct a TextMessageWriter - - - - - Construct a TextMessageWriter, specifying a user message - and optional formatting arguments. - - - - - - - Method to write single line message with optional args, usually - written to precede the general failure message, at a given - indentation level. - - The indentation level of the message - The message to be written - Any arguments used in formatting the message - - - - Display Expected and Actual lines for a constraint. This - is called by MessageWriter's default implementation of - WriteMessageTo and provides the generic two-line display. - - The result of the constraint that failed - - - - Display Expected and Actual lines for given _values. This - method may be called by constraints that need more control over - the display of actual and expected _values than is provided - by the default implementation. - - The expected value - The actual value causing the failure - - - - Display Expected and Actual lines for given _values, including - a tolerance value on the expected line. - - The expected value - The actual value causing the failure - The tolerance within which the test was made - - - - Display the expected and actual string _values on separate lines. - If the mismatch parameter is >=0, an additional line is displayed - line containing a caret that points to the mismatch point. - - The expected string value - The actual string value - The point at which the strings don't match or -1 - If true, case is ignored in string comparisons - If true, clip the strings to fit the max line length - - - - Writes the text for an actual value. - - The actual value. - - - - Writes the text for a generalized value. - - The value. - - - - Writes the text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Write the generic 'Expected' line for a constraint - - The constraint that failed - - - - Write the generic 'Expected' line for a given value - - The expected value - - - - Write the generic 'Expected' line for a given value - and tolerance. - - The expected value - The tolerance within which the test was made - - - - Write the generic 'Actual' line for a constraint - - The ConstraintResult for which the actual value is to be written - - - - Write the generic 'Actual' line for a given value - - The actual value causing a failure - - - - Gets or sets the maximum line length for this writer - - - - - WorkItemQueueState indicates the current state of a WorkItemQueue - - - - - The queue is paused - - - - - The queue is running - - - - - The queue is stopped - - - - - A WorkItemQueue holds work items that are ready to - be run, either initially or after some dependency - has been satisfied. - - - - - Initializes a new instance of the class. - - The name of the queue. - - - - Enqueue a WorkItem to be processed - - The WorkItem to process - - - - Dequeue a WorkItem for processing - - A WorkItem or null if the queue has stopped - - - - Start or restart processing of items from the queue - - - - - Signal the queue to stop - - - - - Pause the queue for restarting later - - - - - Gets the name of the work item queue. - - - - - Gets the total number of items processed so far - - - - - Gets the maximum number of work items. - - - - - Gets the current state of the queue - - - - - Get a bool indicating whether the queue is empty. - - - - - The current state of a work item - - - - - Ready to run or continue - - - - - Work Item is executing - - - - - Complete - - - - - The dispatcher needs to do different things at different, - non-overlapped times. For example, non-parallel tests may - not be run at the same time as parallel tests. We model - this using the metaphor of a working shift. The WorkShift - class associates one or more WorkItemQueues with one or - more TestWorkers. - - Work in the queues is processed until all queues are empty - and all workers are idle. Both tests are needed because a - worker that is busy may end up adding more work to one of - the queues. At that point, the shift is over and another - shift may begin. This cycle continues until all the tests - have been run. - - - - - Construct a WorkShift - - - - - Add a WorkItemQueue to the shift, starting it if the - shift is currently active. - - - - - Assign a worker to the shift. - - - - - - Start or restart processing for the shift - - - - - End the shift, pausing all queues and raising - the EndOfShift event. - - - - - Shut down the shift. - - - - - Cancel the shift without completing all work - - - - - Event that fires when the shift has ended - - - - - Gets a flag indicating whether the shift is currently active - - - - - Gets a list of the queues associated with this shift. - - Used for testing - - - - Gets the list of workers associated with this shift. - - - - - Gets a bool indicating whether this shift has any work to do - - - - - Combines multiple filters so that a test must pass all - of them in order to pass this filter. - - - - - A base class for multi-part filters - - - - - Interface to be implemented by filters applied to tests. - The filter applies when running the test, after it has been - loaded, since this is the only time an ITest exists. - - - - - Unique Empty filter. - - - - - Determine if a particular test passes the filter criteria. The default - implementation checks the test itself, its parents and any descendants. - - Derived classes may override this method or any of the Match methods - to change the behavior of the filter. - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - Determine whether the test itself matches the filter criteria, without - examining either parents or descendants. This is overridden by each - different type of filter to perform the necessary tests. - - The test to which the filter is applied - True if the filter matches the any parent of the test - - - - Determine whether any ancestor of the test matches the filter criteria - - The test to which the filter is applied - True if the filter matches the an ancestor of the test - - - - Determine whether any descendant of the test matches the filter criteria. - - The test to be matched - True if at least one descendant matches the filter criteria - - - - Create a TestFilter instance from an xml representation. - - - - - Create a TestFilter from it's TNode representation - - - - - Adds an XML node - - True if recursive - The added XML node - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Indicates whether this is the EmptyFilter - - - - - Indicates whether this is a top-level filter, - not contained in any other filter. - - - - - Nested class provides an empty filter - one that always - returns true when called. It never matches explicitly. - - - - - Constructs an empty CompositeFilter - - - - - Constructs a CompositeFilter from an array of filters - - - - - - Adds a filter to the list of filters - - The filter to be added - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Return a list of the composing filters. - - - - - Gets the element name - - Element name - - - - Constructs an empty AndFilter - - - - - Constructs an AndFilter from an array of filters - - - - - - Checks whether the AndFilter is matched by a test - - The test to be matched - True if all the component filters pass, otherwise false - - - - Checks whether the AndFilter is matched by a test - - The test to be matched - True if all the component filters match, otherwise false - - - - Gets the element name - - Element name - - - - CategoryFilter is able to select or exclude tests - based on their categories. - - - - - - ValueMatchFilter selects tests based on some value, which - is expected to be contained in the test. - - - - - Construct a ValueMatchFilter for a single value. - - The value to be included. - - - - Match the input provided by the derived class - - The value to be matchedT - True for a match, false otherwise. - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Returns the value matched by the filter - used for testing - - - - - Indicates whether the value is a regular expression - - - - - Gets the element name - - Element name - - - - Construct a CategoryFilter using a single category name - - A category name - - - - Check whether the filter matches a test - - The test to be matched - - - - - Gets the element name - - Element name - - - - ClassName filter selects tests based on the class FullName - - - - - Construct a FullNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - FullName filter selects tests based on their FullName - - - - - Construct a FullNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - IdFilter selects tests based on their id - - - - - Construct an IdFilter for a single value - - The id the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - FullName filter selects tests based on their FullName - - - - - Construct a MethodNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - NotFilter negates the operation of another filter - - - - - Construct a not filter on another filter - - The filter to be negated - - - - Determine if a particular test passes the filter criteria. The default - implementation checks the test itself, its parents and any descendants. - - Derived classes may override this method or any of the Match methods - to change the behavior of the filter. - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Check whether the filter matches a test - - The test to be matched - True if it matches, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Gets the base filter - - - - - Combines multiple filters so that a test must pass one - of them in order to pass this filter. - - - - - Constructs an empty OrFilter - - - - - Constructs an AndFilter from an array of filters - - - - - - Checks whether the OrFilter is matched by a test - - The test to be matched - True if any of the component filters pass, otherwise false - - - - Checks whether the OrFilter is matched by a test - - The test to be matched - True if any of the component filters match, otherwise false - - - - Gets the element name - - Element name - - - - PropertyFilter is able to select or exclude tests - based on their properties. - - - - - - Construct a PropertyFilter using a property name and expected value - - A property name - The expected value of the property - - - - Check whether the filter matches a test - - The test to be matched - - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Gets the element name - - Element name - - - - TestName filter selects tests based on their Name - - - - - Construct a TestNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - GenericMethodHelper is able to deduce the Type arguments for - a generic method from the actual arguments provided. - - - - - Construct a GenericMethodHelper for a method - - MethodInfo for the method to examine - - - - Return the type argments for the method, deducing them - from the arguments actually provided. - - The arguments to the method - An array of type arguments. - - - - InvalidTestFixtureException is thrown when an appropriate test - fixture constructor using the provided arguments cannot be found. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The message. - - - - Initializes a new instance of the class. - - The message. - The inner. - - - - Serialization Constructor - - - - - InvalidTestFixtureException is thrown when an appropriate test - fixture constructor using the provided arguments cannot be found. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The message. - - - - Initializes a new instance of the class. - - The message. - The inner. - - - - Serialization Constructor - - - - - The MethodWrapper class wraps a MethodInfo so that it may - be used in a platform-independent manner. - - - - - Construct a MethodWrapper for a Type and a MethodInfo. - - - - - Construct a MethodInfo for a given Type and method name. - - - - - Gets the parameters of the method. - - - - - - Returns the Type arguments of a generic method or the Type parameters of a generic method definition. - - - - - Replaces the type parameters of the method with the array of types provided and returns a new IMethodInfo. - - The type arguments to be used - A new IMethodInfo with the type arguments replaced - - - - Returns an array of custom attributes of the specified type applied to this method - - - - - Gets a value indicating whether one or more attributes of the spcified type are defined on the method. - - - - - Invokes the method, converting any TargetInvocationException to an NUnitException. - - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - Override ToString() so that error messages in NUnit's own tests make sense - - - - - Gets the Type from which this method was reflected. - - - - - Gets the MethodInfo for this method. - - - - - Gets the name of the method. - - - - - Gets a value indicating whether the method is abstract. - - - - - Gets a value indicating whether the method is public. - - - - - Gets a value indicating whether the method contains unassigned generic type parameters. - - - - - Gets a value indicating whether the method is a generic method. - - - - - Gets a value indicating whether the MethodInfo represents the definition of a generic method. - - - - - Gets the return Type of the method. - - - - - Thrown when an assertion failed. Here to preserve the inner - exception and hence its stack trace. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The error message that explains - the reason for the exception - - - - Initializes a new instance of the class. - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - OSPlatform represents a particular operating system platform - - - - - Platform ID for Unix as defined by Microsoft .NET 2.0 and greater - - - - - Platform ID for Unix as defined by Mono - - - - - Platform ID for XBox as defined by .NET and Mono, but not CF - - - - - Platform ID for MacOSX as defined by .NET and Mono, but not CF - - - - - Gets the actual OS Version, not the incorrect value that might be - returned for Win 8.1 and Win 10 - - - If an application is not manifested as Windows 8.1 or Windows 10, - the version returned from Environment.OSVersion will not be 6.3 and 10.0 - respectively, but will be 6.2 and 6.3. The correct value can be found in - the registry. - - The original version - The correct OS version - - - - Construct from a platform ID and version - - - - - Construct from a platform ID, version and product type - - - - - Get the OSPlatform under which we are currently running - - - - - Get the platform ID of this instance - - - - - Get the Version of this instance - - - - - Get the Product Type of this instance - - - - - Return true if this is a windows platform - - - - - Return true if this is a Unix or Linux platform - - - - - Return true if the platform is Win32S - - - - - Return true if the platform is Win32Windows - - - - - Return true if the platform is Win32NT - - - - - Return true if the platform is Windows CE - - - - - Return true if the platform is Xbox - - - - - Return true if the platform is MacOSX - - - - - Return true if the platform is Windows 95 - - - - - Return true if the platform is Windows 98 - - - - - Return true if the platform is Windows ME - - - - - Return true if the platform is NT 3 - - - - - Return true if the platform is NT 4 - - - - - Return true if the platform is NT 5 - - - - - Return true if the platform is Windows 2000 - - - - - Return true if the platform is Windows XP - - - - - Return true if the platform is Windows 2003 Server - - - - - Return true if the platform is NT 6 - - - - - Return true if the platform is NT 6.0 - - - - - Return true if the platform is NT 6.1 - - - - - Return true if the platform is NT 6.2 - - - - - Return true if the platform is NT 6.3 - - - - - Return true if the platform is Vista - - - - - Return true if the platform is Windows 2008 Server (original or R2) - - - - - Return true if the platform is Windows 2008 Server (original) - - - - - Return true if the platform is Windows 2008 Server R2 - - - - - Return true if the platform is Windows 2012 Server (original or R2) - - - - - Return true if the platform is Windows 2012 Server (original) - - - - - Return true if the platform is Windows 2012 Server R2 - - - - - Return true if the platform is Windows 7 - - - - - Return true if the platform is Windows 8 - - - - - Return true if the platform is Windows 8.1 - - - - - Return true if the platform is Windows 10 - - - - - Return true if the platform is Windows Server. This is named Windows - Server 10 to distinguish it from previous versions of Windows Server. - - - - - Product Type Enumeration used for Windows - - - - - Product type is unknown or unspecified - - - - - Product type is Workstation - - - - - Product type is Domain Controller - - - - - Product type is Server - - - - - The ParameterWrapper class wraps a ParameterInfo so that it may - be used in a platform-independent manner. - - - - - Construct a ParameterWrapper for a given method and parameter - - - - - - - Returns an array of custom attributes of the specified type applied to this method - - - - - Gets a value indicating whether one or more attributes of the specified type are defined on the parameter. - - - - - Gets a value indicating whether the parameter is optional - - - - - Gets an IMethodInfo representing the method for which this is a parameter. - - - - - Gets the underlying ParameterInfo - - - - - Gets the Type of the parameter - - - - - PlatformHelper class is used by the PlatformAttribute class to - determine whether a platform is supported. - - - - - Comma-delimited list of all supported OS platform constants - - - - - Comma-delimited list of all supported Runtime platform constants - - - - - Default constructor uses the operating system and - common language runtime of the system. - - - - - Construct a PlatformHelper for a particular operating - system and common language runtime. Used in testing. - - OperatingSystem to be used - RuntimeFramework to be used - - - - Test to determine if one of a collection of platforms - is being used currently. - - - - - - - Tests to determine if the current platform is supported - based on a platform attribute. - - The attribute to examine - - - - - Tests to determine if the current platform is supported - based on a platform attribute. - - The attribute to examine - - - - - Test to determine if the a particular platform or comma- - delimited set of platforms is in use. - - Name of the platform or comma-separated list of platform ids - True if the platform is in use on the system - - - - Return the last failure reason. Results are not - defined if called before IsSupported( Attribute ) - is called. - - - - - A PropertyBag represents a collection of name value pairs - that allows duplicate entries with the same key. Methods - are provided for adding a new pair as well as for setting - a key to a single value. All keys are strings but _values - may be of any type. Null _values are not permitted, since - a null entry represents the absence of the key. - - - - - Adds a key/value pair to the property set - - The key - The value - - - - Sets the value for a key, removing any other - _values that are already in the property set. - - - - - - - Gets a single value for a key, using the first - one if multiple _values are present and returning - null if the value is not found. - - - - - - - Gets a flag indicating whether the specified key has - any entries in the property set. - - The key to be checked - - True if their are _values present, otherwise false - - - - - Returns an XmlNode representating the current PropertyBag. - - Not used - An XmlNode representing the PropertyBag - - - - Returns an XmlNode representing the PropertyBag after - adding it as a child of the supplied parent node. - - The parent node. - Not used - - - - - Gets a collection containing all the keys in the property set - - - - - - Gets or sets the list of _values for a particular key - - - - - The PropertyNames class provides static constants for the - standard property ids that NUnit uses on tests. - - - - - The FriendlyName of the AppDomain in which the assembly is running - - - - - The selected strategy for joining parameter data into test cases - - - - - The process ID of the executing assembly - - - - - The stack trace from any data provider that threw - an exception. - - - - - The reason a test was not run - - - - - The author of the tests - - - - - The ApartmentState required for running the test - - - - - The categories applying to a test - - - - - The Description of a test - - - - - The number of threads to be used in running tests - - - - - The maximum time in ms, above which the test is considered to have failed - - - - - The ParallelScope associated with a test - - - - - The number of times the test should be repeated - - - - - Indicates that the test should be run on a separate thread - - - - - The culture to be set for a test - - - - - The UI culture to be set for a test - - - - - The type that is under test - - - - - The timeout value for the test - - - - - The test will be ignored until the given date - - - - - Randomizer returns a set of random _values in a repeatable - way, to allow re-running of tests if necessary. It extends - the .NET Random class, providing random values for a much - wider range of types. - - The class is used internally by the framework to generate - test case data and is also exposed for use by users through - the TestContext.Random property. - - - For consistency with the underlying Random Type, methods - returning a single value use the prefix "Next..." Those - without an argument return a non-negative value up to - the full positive range of the Type. Overloads are provided - for specifying a maximum or a range. Methods that return - arrays or strings use the prefix "Get..." to avoid - confusion with the single-value methods. - - - - - Default characters for random functions. - - Default characters are the English alphabet (uppercase & lowercase), arabic numerals, and underscore - - - - Get a Randomizer for a particular member, returning - one that has already been created if it exists. - This ensures that the same _values are generated - each time the tests are reloaded. - - - - - Get a randomizer for a particular parameter, returning - one that has already been created if it exists. - This ensures that the same values are generated - each time the tests are reloaded. - - - - - Create a new Randomizer using the next seed - available to ensure that each randomizer gives - a unique sequence of values. - - - - - - Default constructor - - - - - Construct based on seed value - - - - - - Returns a random unsigned int. - - - - - Returns a random unsigned int less than the specified maximum. - - - - - Returns a random unsigned int within a specified range. - - - - - Returns a non-negative random short. - - - - - Returns a non-negative random short less than the specified maximum. - - - - - Returns a non-negative random short within a specified range. - - - - - Returns a random unsigned short. - - - - - Returns a random unsigned short less than the specified maximum. - - - - - Returns a random unsigned short within a specified range. - - - - - Returns a random long. - - - - - Returns a random long less than the specified maximum. - - - - - Returns a non-negative random long within a specified range. - - - - - Returns a random ulong. - - - - - Returns a random ulong less than the specified maximum. - - - - - Returns a non-negative random long within a specified range. - - - - - Returns a random Byte - - - - - Returns a random Byte less than the specified maximum. - - - - - Returns a random Byte within a specified range - - - - - Returns a random SByte - - - - - Returns a random sbyte less than the specified maximum. - - - - - Returns a random sbyte within a specified range - - - - - Returns a random bool - - - - - Returns a random bool based on the probablility a true result - - - - - Returns a random double between 0.0 and the specified maximum. - - - - - Returns a random double within a specified range. - - - - - Returns a random float. - - - - - Returns a random float between 0.0 and the specified maximum. - - - - - Returns a random float within a specified range. - - - - - Returns a random enum value of the specified Type as an object. - - - - - Returns a random enum value of the specified Type. - - - - - Generate a random string based on the characters from the input string. - - desired length of output string. - string representing the set of characters from which to construct the resulting string - A random string of arbitrary length - - - - Generate a random string based on the characters from the input string. - - desired length of output string. - A random string of arbitrary length - Uses DefaultStringChars as the input character set - - - - Generate a random string based on the characters from the input string. - - A random string of the default length - Uses DefaultStringChars as the input character set - - - - Returns a random decimal. - - - - - Returns a random decimal between positive zero and the specified maximum. - - - - - Returns a random decimal within a specified range, which is not - permitted to exceed decimal.MaxVal in the current implementation. - - - A limitation of this implementation is that the range from min - to max must not exceed decimal.MaxVal. - - - - - Initial seed used to create randomizers for this run - - - - - Helper methods for inspecting a type by reflection. - - Many of these methods take ICustomAttributeProvider as an - argument to avoid duplication, even though certain attributes can - only appear on specific types of members, like MethodInfo or Type. - - In the case where a type is being examined for the presence of - an attribute, interface or named member, the Reflect methods - operate with the full name of the member being sought. This - removes the necessity of the caller having a reference to the - assembly that defines the item being sought and allows the - NUnit core to inspect assemblies that reference an older - version of the NUnit framework. - - - - - Examine a fixture type and return an array of methods having a - particular attribute. The array is order with base methods first. - - The type to examine - The attribute Type to look for - Specifies whether to search the fixture type inheritance chain - The array of methods found - - - - Examine a fixture type and return true if it has a method with - a particular attribute. - - The type to examine - The attribute Type to look for - True if found, otherwise false - - - - Invoke the default constructor on a Type - - The Type to be constructed - An instance of the Type - - - - Invoke a constructor on a Type with arguments - - The Type to be constructed - Arguments to the constructor - An instance of the Type - - - - Returns an array of types from an array of objects. - Used because the compact framework doesn't support - Type.GetTypeArray() - - An array of objects - An array of Types - - - - Invoke a parameterless method returning void on an object. - - A MethodInfo for the method to be invoked - The object on which to invoke the method - - - - Invoke a method, converting any TargetInvocationException to an NUnitException. - - A MethodInfo for the method to be invoked - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - The TestResult class represents the result of a test. - - - - - The minimum duration for tests - - - - - Error message for when child tests have errors - - - - - Error message for when child tests are ignored - - - - - List of child results - - - - - Construct a test result given a Test - - The test to be used - - - - Returns the Xml representation of the result. - - If true, descendant results are included - An XmlNode representing the result - - - - Adds the XML representation of the result as a child of the - supplied parent node.. - - The parent node. - If true, descendant results are included - - - - - Adds a child result to this result, setting this result's - ResultState to Failure if the child result failed. - - The result to be added - - - - Set the result of the test - - The ResultState to use in the result - - - - Set the result of the test - - The ResultState to use in the result - A message associated with the result state - - - - Set the result of the test - - The ResultState to use in the result - A message associated with the result state - Stack trace giving the location of the command - - - - Set the test result based on the type of exception thrown - - The exception that was thrown - - - - Set the test result based on the type of exception thrown - - The exception that was thrown - THe FailureSite to use in the result - - - - RecordTearDownException appends the message and stacktrace - from an exception arising during teardown of the test - to any previously recorded information, so that any - earlier failure information is not lost. Note that - calling Assert.Ignore, Assert.Inconclusive, etc. during - teardown is treated as an error. If the current result - represents a suite, it may show a teardown error even - though all contained tests passed. - - The Exception to be recorded - - - - Adds a reason element to a node and returns it. - - The target node. - The new reason element. - - - - Adds a failure element to a node and returns it. - - The target node. - The new failure element. - - - - Gets the test with which this result is associated. - - - - - Gets the ResultState of the test result, which - indicates the success or failure of the test. - - - - - Gets the name of the test result - - - - - Gets the full name of the test result - - - - - Gets or sets the elapsed time for running the test in seconds - - - - - Gets or sets the time the test started running. - - - - - Gets or sets the time the test finished running. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. - - - - - Gets or sets the count of asserts executed - when running the test. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Indicates whether this result has any child results. - Test HasChildren before accessing Children to avoid - the creation of an empty collection. - - - - - Gets the collection of child results. - - - - - Gets a TextWriter, which will write output to be included in the result. - - - - - Gets any text output written to this result. - - - - - Enumeration identifying a common language - runtime implementation. - - - - Any supported runtime framework - - - Microsoft .NET Framework - - - Microsoft .NET Compact Framework - - - Microsoft Shared Source CLI - - - Mono - - - Silverlight - - - MonoTouch - - - - RuntimeFramework represents a particular version - of a common language runtime implementation. - - - - - DefaultVersion is an empty Version, used to indicate that - NUnit should select the CLR version to use for the test. - - - - - Construct from a runtime type and version. If the version has - two parts, it is taken as a framework version. If it has three - or more, it is taken as a CLR version. In either case, the other - version is deduced based on the runtime type and provided version. - - The runtime type of the framework - The version of the framework - - - - Parses a string representing a RuntimeFramework. - The string may be just a RuntimeType name or just - a Version or a hyphenated RuntimeType-Version or - a Version prefixed by 'versionString'. - - - - - - - Overridden to return the short name of the framework - - - - - - Returns true if the current framework matches the - one supplied as an argument. Two frameworks match - if their runtime types are the same or either one - is RuntimeType.Any and all specified version components - are equal. Negative (i.e. unspecified) version - components are ignored. - - The RuntimeFramework to be matched. - True on match, otherwise false - - - - Static method to return a RuntimeFramework object - for the framework that is currently in use. - - - - - The type of this runtime framework - - - - - The framework version for this runtime framework - - - - - The CLR version for this runtime framework - - - - - Return true if any CLR version may be used in - matching this RuntimeFramework object. - - - - - Returns the Display name for this framework - - - - - StackFilter class is used to remove internal NUnit - entries from a stack trace so that the resulting - trace provides better information about the test. - - - - - Filters a raw stack trace and returns the result. - - The original stack trace - A filtered stack trace - - - - Provides methods to support legacy string comparison methods. - - - - - Compares two strings for equality, ignoring case if requested. - - The first string. - The second string.. - if set to true, the case of the letters in the strings is ignored. - Zero if the strings are equivalent, a negative number if strA is sorted first, a positive number if - strB is sorted first - - - - Compares two strings for equality, ignoring case if requested. - - The first string. - The second string.. - if set to true, the case of the letters in the strings is ignored. - True if the strings are equivalent, false if not. - - - - The TestCaseParameters class encapsulates method arguments and - other selected parameters needed for constructing - a parameterized test case. - - - - - TestParameters is the abstract base class for all classes - that know how to provide data for constructing a test. - - - - - Default Constructor creates an empty parameter set - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a ParameterSet from an object implementing ITestData - - - - - - Applies ParameterSet _values to the test itself. - - A test. - - - - The RunState for this set of parameters. - - - - - The arguments to be used in running the test, - which must match the method signature. - - - - - A name to be used for this test case in lieu - of the standard generated name containing - the argument list. - - - - - Gets the property dictionary for this test - - - - - The original arguments provided by the user, - used for display purposes. - - - - - The expected result to be returned - - - - - Default Constructor creates an empty parameter set - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a ParameterSet from an object implementing ITestCaseData - - - - - - The expected result of the test, which - must match the method return type. - - - - - Gets a value indicating whether an expected result was specified. - - - - - Helper class used to save and restore certain static or - singleton settings in the environment that affect tests - or which might be changed by the user tests. - - An internal class is used to hold settings and a stack - of these objects is pushed and popped as Save and Restore - are called. - - - - - Link to a prior saved context - - - - - Indicates that a stop has been requested - - - - - The event listener currently receiving notifications - - - - - The number of assertions for the current test - - - - - The current culture - - - - - The current UI culture - - - - - The current test result - - - - - The current Principal. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - An existing instance of TestExecutionContext. - - - - The current context, head of the list of saved contexts. - - - - - Get the current context or return null if none is found. - - - - - Clear the current context. This is provided to - prevent "leakage" of the CallContext containing - the current context back to any runners. - - - - - Record any changes in the environment made by - the test code in the execution context so it - will be passed on to lower level tests. - - - - - Set up the execution environment to match a context. - Note that we may be running on the same thread where the - context was initially created or on a different thread. - - - - - Increments the assert count by one. - - - - - Increments the assert count by a specified amount. - - - - - Obtain lifetime service object - - - - - - Gets the current context. - - The current context. - - - - Gets or sets the current test - - - - - The time the current test started execution - - - - - The time the current test started in Ticks - - - - - Gets or sets the current test result - - - - - Gets a TextWriter that will send output to the current test result. - - - - - The current test object - that is the user fixture - object on which tests are being executed. - - - - - Get or set the working directory - - - - - Get or set indicator that run should stop on the first error - - - - - Gets an enum indicating whether a stop has been requested. - - - - - The current test event listener - - - - - The current WorkItemDispatcher - - - - - The ParallelScope to be used by tests running in this context. - For builds with out the parallel feature, it has no effect. - - - - - Gets the RandomGenerator specific to this Test - - - - - Gets the assert count. - - The assert count. - - - - Gets or sets the test case timeout value - - - - - Gets a list of ITestActions set by upstream tests - - - - - Saves or restores the CurrentCulture - - - - - Saves or restores the CurrentUICulture - - - - - Gets or sets the current for the Thread. - - - - - Enumeration indicating whether the tests are - running normally or being cancelled. - - - - - Running normally with no stop requested - - - - - A graceful stop has been requested - - - - - A forced stop has been requested - - - - - The TestCaseParameters class encapsulates method arguments and - other selected parameters needed for constructing - a parameterized test case. - - - - - Default Constructor creates an empty parameter set - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a ParameterSet from an object implementing ITestCaseData - - - - - - Type arguments used to create a generic fixture instance - - - - - TestListener provides an implementation of ITestListener that - does nothing. It is used only through its NULL property. - - - - - Called when a test has just started - - The test that is starting - - - - Called when a test case has finished - - The result of the test - - - - Construct a new TestListener - private so it may not be used. - - - - - Get a listener that does nothing - - - - - TestNameGenerator is able to create test names according to - a coded pattern. - - - - - Construct a TestNameGenerator - - The pattern used by this generator. - - - - Get the display name for a TestMethod and it's arguments - - A TestMethod - The display name - - - - Get the display name for a TestMethod and it's arguments - - A TestMethod - Arguments to be used - The display name - - - - Get the display name for a MethodInfo - - A MethodInfo - The display name - - - - Get the display name for a method with args - - A MethodInfo - Argument list for the method - The display name - - - - TestProgressReporter translates ITestListener events into - the async callbacks that are used to inform the client - software about the progress of a test run. - - - - - Initializes a new instance of the class. - - The callback handler to be used for reporting progress. - - - - Called when a test has just started - - The test that is starting - - - - Called when a test has finished. Sends a result summary to the callback. - to - - The result of the test - - - - Returns the parent test item for the targer test item if it exists - - - parent test item - - - - Makes a string safe for use as an attribute, replacing - characters characters that can't be used with their - corresponding xml representations. - - The string to be used - A new string with the _values replaced - - - - ParameterizedFixtureSuite serves as a container for the set of test - fixtures created from a given Type using various parameters. - - - - - TestSuite represents a composite test, which contains other tests. - - - - - The Test abstract class represents a test within the framework. - - - - - Static value to seed ids. It's started at 1000 so any - uninitialized ids will stand out. - - - - - The SetUp methods. - - - - - The teardown methods - - - - - Constructs a test given its name - - The name of the test - - - - Constructs a test given the path through the - test hierarchy to its parent and a name. - - The parent tests full name - The name of the test - - - - TODO: Documentation needed for constructor - - - - - - Construct a test from a MethodInfo - - - - - - Creates a TestResult for this test. - - A TestResult suitable for this type of test. - - - - Modify a newly constructed test by applying any of NUnit's common - attributes, based on a supplied ICustomAttributeProvider, which is - usually the reflection element from which the test was constructed, - but may not be in some instances. The attributes retrieved are - saved for use in subsequent operations. - - An object implementing ICustomAttributeProvider - - - - Add standard attributes and members to a test node. - - - - - - - Returns the Xml representation of the test - - If true, include child tests recursively - - - - - Returns an XmlNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Compares this test to another test for sorting purposes - - The other test - Value of -1, 0 or +1 depending on whether the current test is less than, equal to or greater than the other test - - - - Gets or sets the id of the test - - - - - - Gets or sets the name of the test - - - - - Gets or sets the fully qualified name of the test - - - - - - Gets the name of the class containing this test. Returns - null if the test is not associated with a class. - - - - - Gets the name of the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the TypeInfo of the fixture used in running this test - or null if no fixture type is associated with it. - - - - - Gets a MethodInfo for the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Whether or not the test should be run - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Gets a string representing the type of test. Used as an attribute - value in the XML representation of a test and has no other - function in the framework. - - - - - Gets a count of test cases represented by - or contained under this test. - - - - - Gets the properties for this test - - - - - Returns true if this is a TestSuite - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets the parent as a Test object. - Used by the core to set the parent. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets or sets a fixture object for running this test. - - - - - Static prefix used for ids in this AppDomain. - Set by FrameworkController. - - - - - Gets or Sets the Int value representing the seed for the RandomGenerator - - - - - - Our collection of child tests - - - - - Initializes a new instance of the class. - - The name of the suite. - - - - Initializes a new instance of the class. - - Name of the parent suite. - The name of the suite. - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - Sorts tests under this suite. - - - - - Adds a test to the suite. - - The test. - - - - Overridden to return a TestSuiteResult. - - A TestResult for this test. - - - - Returns an XmlNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Check that setup and teardown methods marked by certain attributes - meet NUnit's requirements and mark the tests not runnable otherwise. - - The attribute type to check for - - - - Gets this test's child tests - - The list of child tests - - - - Gets a count of test cases represented by - or contained under this test. - - - - - - The arguments to use in creating the fixture - - - - - Set to true to suppress sorting this suite's contents - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Initializes a new instance of the class. - - The ITypeInfo for the type that represents the suite. - - - - Gets a string representing the type of test - - - - - - ParameterizedMethodSuite holds a collection of individual - TestMethods with their arguments applied. - - - - - Construct from a MethodInfo - - - - - - Gets a string representing the type of test - - - - - - SetUpFixture extends TestSuite and supports - Setup and TearDown methods. - - - - - Initializes a new instance of the class. - - The type. - - - - TestAssembly is a TestSuite that represents the execution - of tests in a managed assembly. - - - - - Initializes a new instance of the class - specifying the Assembly and the path from which it was loaded. - - The assembly this test represents. - The path used to load the assembly. - - - - Initializes a new instance of the class - for a path which could not be loaded. - - The path used to load the assembly. - - - - Gets the Assembly represented by this instance. - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - TestFixture is a surrogate for a user test fixture class, - containing one or more tests. - - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - The TestMethod class represents a Test implemented as a method. - - - - - The ParameterSet used to create this test method - - - - - Initializes a new instance of the class. - - The method to be used as a test. - - - - Initializes a new instance of the class. - - The method to be used as a test. - The suite or fixture to which the new test will be added - - - - Overridden to return a TestCaseResult. - - A TestResult for this test. - - - - Returns a TNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Returns the name of the method - - - - - ThreadUtility provides a set of static methods convenient - for working with threads. - - - - - Do our best to Kill a thread - - The thread to kill - - - - Do our best to kill a thread, passing state info - - The thread to kill - Info for the ThreadAbortException handler - - - - TypeHelper provides static methods that operate on Types. - - - - - A special value, which is used to indicate that BestCommonType() method - was unable to find a common type for the specified arguments. - - - - - Gets the display name for a Type as used by NUnit. - - The Type for which a display name is needed. - The display name for the Type - - - - Gets the display name for a Type as used by NUnit. - - The Type for which a display name is needed. - The arglist provided. - The display name for the Type - - - - Returns the best fit for a common type to be used in - matching actual arguments to a methods Type parameters. - - The first type. - The second type. - Either type1 or type2, depending on which is more general. - - - - Determines whether the specified type is numeric. - - The type to be examined. - - true if the specified type is numeric; otherwise, false. - - - - - Convert an argument list to the required parameter types. - Currently, only widening numeric conversions are performed. - - An array of args to be converted - A ParameterInfo[] whose types will be used as targets - - - - Determines whether this instance can deduce type args for a generic type from the supplied arguments. - - The type to be examined. - The arglist. - The type args to be used. - - true if this the provided args give sufficient information to determine the type args to be used; otherwise, false. - - - - - Gets the _values for an enumeration, using Enum.GetTypes - where available, otherwise through reflection. - - - - - - - Gets the ids of the _values for an enumeration, - using Enum.GetNames where available, otherwise - through reflection. - - - - - - - The TypeWrapper class wraps a Type so it may be used in - a platform-independent manner. - - - - - Construct a TypeWrapper for a specified Type. - - - - - Returns true if the Type wrapped is T - - - - - Get the display name for this type - - - - - Get the display name for an object of this type, constructed with the specified args. - - - - - Returns a new ITypeInfo representing an instance of this generic Type using the supplied Type arguments - - - - - Returns a Type representing a generic type definition from which this Type can be constructed. - - - - - Returns an array of custom attributes of the specified type applied to this type - - - - - Returns a value indicating whether the type has an attribute of the specified type. - - - - - - - - Returns a flag indicating whether this type has a method with an attribute of the specified type. - - - - - - - Returns an array of IMethodInfos for methods of this Type - that match the specified flags. - - - - - Returns a value indicating whether this Type has a public constructor taking the specified argument Types. - - - - - Construct an object of this Type, using the specified arguments. - - - - - Override ToString() so that error messages in NUnit's own tests make sense - - - - - Gets the underlying Type on which this TypeWrapper is based. - - - - - Gets the base type of this type as an ITypeInfo - - - - - Gets the Name of the Type - - - - - Gets the FullName of the Type - - - - - Gets the assembly in which the type is declared - - - - - Gets the namespace of the Type - - - - - Gets a value indicating whether the type is abstract. - - - - - Gets a value indicating whether the Type is a generic Type - - - - - Gets a value indicating whether the Type has generic parameters that have not been replaced by specific Types. - - - - - Gets a value indicating whether the Type is a generic Type definition - - - - - Gets a value indicating whether the type is sealed. - - - - - Gets a value indicating whether this type represents a static class. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable to the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable to the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that tests whether the path provided - is the same as an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is a subpath of the expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the actual value falls - inclusively within a specified range. - - from must be less than or equal to true - Inclusive beginning of the range. Must be less than or equal to to. - Inclusive end of the range. Must be greater than or equal to from. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether an object graph is serializable in binary format. - - - - - Returns a constraint that tests whether an object graph is serializable in xml format. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - The Iz class is a synonym for Is intended for use in VB, - which regards Is as a keyword. - - - - - The List class is a helper class with properties and methods - that supply a number of constraints used with lists and collections. - - - - - List.Map returns a ListMapper, which can be used to map - the original collection to another collection. - - - - - - - ListMapper is used to transform a collection used as an actual argument - producing another collection to be used in the assertion. - - - - - Construct a ListMapper based on a collection - - The collection to be transformed - - - - Produces a collection containing all the _values of a property - - The collection of property _values - - - - - The SpecialValue enum is used to represent TestCase arguments - that cannot be used as arguments to an Attribute. - - - - - Null represents a null value, which cannot be used as an - argument to an attriute under .NET 1.x - - - - - Basic Asserts on strings. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - - - - Asserts that a string is not found within another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - - - - Asserts that two strings are not equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that two strings are not equal, without regard to case. - - The expected string - The actual string - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - - - - The TestCaseData class represents a set of arguments - and other parameter info to be used for a parameterized - test case. It is derived from TestCaseParameters and adds a - fluent syntax for use in initializing the test case. - - - - - Initializes a new instance of the class. - - The arguments. - - - - Initializes a new instance of the class. - - The argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - The third argument. - - - - Sets the expected result for the test - - The expected result - A modified TestCaseData - - - - Sets the name of the test case - - The modified TestCaseData instance - - - - Sets the description for the test case - being constructed. - - The description. - The modified TestCaseData instance. - - - - Applies a category to the test - - - - - - - Applies a named property to the test - - - - - - - - Applies a named property to the test - - - - - - - - Applies a named property to the test - - - - - - - - Marks the test case as explicit. - - - - - Marks the test case as explicit, specifying the reason. - - - - - Ignores this TestCase, specifying the reason. - - The reason. - - - - - Provide the context information of the current test. - This is an adapter for the internal ExecutionContext - class, hiding the internals from the user test. - - - - - Construct a TestContext for an ExecutionContext - - The ExecutionContext to adapt - - - Write the string representation of a boolean value to the current result - - - Write a char to the current result - - - Write a char array to the current result - - - Write the string representation of a double to the current result - - - Write the string representation of an Int32 value to the current result - - - Write the string representation of an Int64 value to the current result - - - Write the string representation of a decimal value to the current result - - - Write the string representation of an object to the current result - - - Write the string representation of a Single value to the current result - - - Write a string to the current result - - - Write the string representation of a UInt32 value to the current result - - - Write the string representation of a UInt64 value to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a line terminator to the current result - - - Write the string representation of a boolean value to the current result followed by a line terminator - - - Write a char to the current result followed by a line terminator - - - Write a char array to the current result followed by a line terminator - - - Write the string representation of a double to the current result followed by a line terminator - - - Write the string representation of an Int32 value to the current result followed by a line terminator - - - Write the string representation of an Int64 value to the current result followed by a line terminator - - - Write the string representation of a decimal value to the current result followed by a line terminator - - - Write the string representation of an object to the current result followed by a line terminator - - - Write the string representation of a Single value to the current result followed by a line terminator - - - Write a string to the current result followed by a line terminator - - - Write the string representation of a UInt32 value to the current result followed by a line terminator - - - Write the string representation of a UInt64 value to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - - Get the current test context. This is created - as needed. The user may save the context for - use within a test, but it should not be used - outside the test for which it is created. - - - - - Gets a TextWriter that will send output to the current test result. - - - - - Get a representation of the current test. - - - - - Gets a Representation of the TestResult for the current test. - - - - - Gets the directory containing the current test assembly. - - - - - Gets the directory to be used for outputting files created - by this test run. - - - - - Gets the random generator. - - - The random generator. - - - - - TestAdapter adapts a Test for consumption by - the user test code. - - - - - Construct a TestAdapter for a Test - - The Test to be adapted - - - - Gets the unique Id of a test - - - - - The name of the test, which may or may not be - the same as the method name. - - - - - The name of the method representing the test. - - - - - The FullName of the test - - - - - The ClassName of the test - - - - - The properties of the test. - - - - - ResultAdapter adapts a TestResult for consumption by - the user test code. - - - - - Construct a ResultAdapter for a TestResult - - The TestResult to be adapted - - - - Gets a ResultState representing the outcome of the test. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - The TestFixtureData class represents a set of arguments - and other parameter info to be used for a parameterized - fixture. It is derived from TestFixtureParameters and adds a - fluent syntax for use in initializing the fixture. - - - - - Initializes a new instance of the class. - - The arguments. - - - - Initializes a new instance of the class. - - The argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - The third argument. - - - - Marks the test fixture as explicit. - - - - - Marks the test fixture as explicit, specifying the reason. - - - - - Ignores this TestFixture, specifying the reason. - - The reason. - - - - - Helper class with properties and methods that supply - constraints that operate on exceptions. - - - - - Creates a constraint specifying the exact type of exception expected - - - - - Creates a constraint specifying the exact type of exception expected - - - - - Creates a constraint specifying the type of exception expected - - - - - Creates a constraint specifying the type of exception expected - - - - - Creates a constraint specifying an expected exception - - - - - Creates a constraint specifying an exception with a given InnerException - - - - - Creates a constraint specifying an expected TargetInvocationException - - - - - Creates a constraint specifying an expected ArgumentException - - - - - Creates a constraint specifying an expected ArgumentNUllException - - - - - Creates a constraint specifying an expected InvalidOperationException - - - - - Creates a constraint specifying that no exception is thrown - - - - - Represents the result of running a single test case. - - - - - Construct a TestCaseResult based on a TestMethod - - A TestMethod to which the result applies. - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Represents the result of running a test suite - - - - - Construct a TestSuiteResult base on a TestSuite - - The TestSuite to which the result applies - - - - Add a child result - - The child result to be added - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - ExactCountConstraint applies another constraint to each - item in a collection, succeeding only if a specified - number of items succeed. - - - - - Construct an ExactCountConstraint on top of an existing constraint - - - - - - - Apply the item constraint to each item in the collection, - succeeding only if the expected number of items pass. - - - - - - - Represents a constraint that succeeds if the specified - count of members of a collection match a base constraint. - - - - - Construct an ExactCountOperator for a specified count - - The expected count - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - none of them succeed. - - - - - ExceptionTypeConstraint is a special version of ExactTypeConstraint - used to provided detailed info about the exception thrown in - an error message. - - - - - Constructs an ExceptionTypeConstraint - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - diff --git a/packages/NUnit.3.0.0/lib/net45/nunit.framework.dll b/packages/NUnit.3.0.0/lib/net45/nunit.framework.dll deleted file mode 100644 index 237f8258e..000000000 Binary files a/packages/NUnit.3.0.0/lib/net45/nunit.framework.dll and /dev/null differ diff --git a/packages/NUnit.3.0.0/lib/net45/nunit.framework.xml b/packages/NUnit.3.0.0/lib/net45/nunit.framework.xml deleted file mode 100644 index 9be0b5ff2..000000000 --- a/packages/NUnit.3.0.0/lib/net45/nunit.framework.xml +++ /dev/null @@ -1,16832 +0,0 @@ - - - - nunit.framework - - - - - AssemblyHelper provides static methods for working - with assemblies. - - - - - Gets the path from which the assembly defining a type was loaded. - - The Type. - The path. - - - - Gets the path from which an assembly was loaded. - - The assembly. - The path. - - - - Gets the path to the directory from which an assembly was loaded. - - The assembly. - The path. - - - - Gets the AssemblyName of an assembly. - - The assembly - An AssemblyName - - - - Loads an assembly given a string, which may be the - path to the assembly or the AssemblyName - - - - - - - Gets the assembly path from code base. - - Public for testing purposes - The code base. - - - - - Env is a static class that provides some of the features of - System.Environment that are not available under all runtimes - - - - - The newline sequence in the current environment. - - - - - Path to the 'My Documents' folder - - - - - Directory used for file output if not specified on commandline. - - - - - Class used to guard against unexpected argument values - or operations by throwing an appropriate exception. - - - - - Throws an exception if an argument is null - - The value to be tested - The name of the argument - - - - Throws an exception if a string argument is null or empty - - The value to be tested - The name of the argument - - - - Throws an ArgumentOutOfRangeException if the specified condition is not met. - - The condition that must be met - The exception message to be used - The name of the argument - - - - Throws an ArgumentException if the specified condition is not met. - - The condition that must be met - The exception message to be used - The name of the argument - - - - Throws an InvalidOperationException if the specified condition is not met. - - The condition that must be met - The exception message to be used - - - - Interface for logging within the engine - - - - - Logs the specified message at the error level. - - The message. - - - - Logs the specified message at the error level. - - The message. - The arguments. - - - - Logs the specified message at the warning level. - - The message. - - - - Logs the specified message at the warning level. - - The message. - The arguments. - - - - Logs the specified message at the info level. - - The message. - - - - Logs the specified message at the info level. - - The message. - The arguments. - - - - Logs the specified message at the debug level. - - The message. - - - - Logs the specified message at the debug level. - - The message. - The arguments. - - - - InternalTrace provides facilities for tracing the execution - of the NUnit framework. Tests and classes under test may make use - of Console writes, System.Diagnostics.Trace or various loggers and - NUnit itself traps and processes each of them. For that reason, a - separate internal trace is needed. - - Note: - InternalTrace uses a global lock to allow multiple threads to write - trace messages. This can easily make it a bottleneck so it must be - used sparingly. Keep the trace Level as low as possible and only - insert InternalTrace writes where they are needed. - TODO: add some buffering and a separate writer thread as an option. - TODO: figure out a way to turn on trace in specific classes only. - - - - - Initialize the internal trace facility using the name of the log - to be written to and the trace level. - - The log name - The trace level - - - - Initialize the internal trace using a provided TextWriter and level - - A TextWriter - The InternalTraceLevel - - - - Get a named Logger - - - - - - Get a logger named for a particular Type. - - - - - Gets a flag indicating whether the InternalTrace is initialized - - - - - InternalTraceLevel is an enumeration controlling the - level of detailed presented in the internal log. - - - - - Use the default settings as specified by the user. - - - - - Do not display any trace messages - - - - - Display Error messages only - - - - - Display Warning level and higher messages - - - - - Display informational and higher messages - - - - - Display debug messages and higher - i.e. all messages - - - - - Display debug messages and higher - i.e. all messages - - - - - A trace listener that writes to a separate file per domain - and process using it. - - - - - Construct an InternalTraceWriter that writes to a file. - - Path to the file to use - - - - Construct an InternalTraceWriter that writes to a - TextWriter provided by the caller. - - - - - - Writes a character to the text string or stream. - - The character to write to the text stream. - - - - Writes a string to the text string or stream. - - The string to write. - - - - Writes a string followed by a line terminator to the text string or stream. - - The string to write. If is null, only the line terminator is written. - - - - Releases the unmanaged resources used by the and optionally releases the managed resources. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - Clears all buffers for the current writer and causes any buffered data to be written to the underlying device. - - - - - Returns the character encoding in which the output is written. - - The character encoding in which the output is written. - - - - Provides internal logging to the NUnit framework - - - - - Initializes a new instance of the class. - - The name. - The log level. - The writer where logs are sent. - - - - Logs the message at error level. - - The message. - - - - Logs the message at error level. - - The message. - The message arguments. - - - - Logs the message at warm level. - - The message. - - - - Logs the message at warning level. - - The message. - The message arguments. - - - - Logs the message at info level. - - The message. - - - - Logs the message at info level. - - The message. - The message arguments. - - - - Logs the message at debug level. - - The message. - - - - Logs the message at debug level. - - The message. - The message arguments. - - - - PackageSettings is a static class containing constant values that - are used as keys in setting up a TestPackage. These values are used in - the engine and framework. Setting values may be a string, int or bool. - - - - - Flag (bool) indicating whether tests are being debugged. - - - - - The InternalTraceLevel for this run. Values are: "Default", - "Off", "Error", "Warning", "Info", "Debug", "Verbose". - Default is "Off". "Debug" and "Verbose" are synonyms. - - - - - Full path of the directory to be used for work and result files. - This path is provided to tests by the frameowrk TestContext. - - - - - The name of the config to use in loading a project. - If not specified, the first config found is used. - - - - - Bool indicating whether the engine should determine the private - bin path by examining the paths to all the tests. Defaults to - true unless PrivateBinPath is specified. - - - - - The ApplicationBase to use in loading the tests. If not - specified, and each assembly has its own process, then the - location of the assembly is used. For multiple assemblies - in a single process, the closest common root directory is used. - - - - - Path to the config file to use in running the tests. - - - - - Bool flag indicating whether a debugger should be launched at agent - startup. Used only for debugging NUnit itself. - - - - - Indicates how to load tests across AppDomains. Values are: - "Default", "None", "Single", "Multiple". Default is "Multiple" - if more than one assembly is loaded in a process. Otherwise, - it is "Single". - - - - - The private binpath used to locate assemblies. Directory paths - is separated by a semicolon. It's an error to specify this and - also set AutoBinPath to true. - - - - - The maximum number of test agents permitted to run simultneously. - Ignored if the ProcessModel is not set or defaulted to Multiple. - - - - - Indicates how to allocate assemblies to processes. Values are: - "Default", "Single", "Separate", "Multiple". Default is "Multiple" - for more than one assembly, "Separate" for a single assembly. - - - - - Indicates the desired runtime to use for the tests. Values - are strings like "net-4.5", "mono-4.0", etc. Default is to - use the target framework for which an assembly was built. - - - - - Bool flag indicating that the test should be run in a 32-bit process - on a 64-bit system. By default, NUNit runs in a 64-bit process on - a 64-bit system. Ignored if set on a 32-bit system. - - - - - Indicates that test runners should be disposed after the tests are executed - - - - - Bool flag indicating that the test assemblies should be shadow copied. - Defaults to false. - - - - - Integer value in milliseconds for the default timeout value - for test cases. If not specified, there is no timeout except - as specified by attributes on the tests themselves. - - - - - A TextWriter to which the internal trace will be sent. - - - - - A list of tests to be loaded. - - - - - The number of test threads to run for the assembly. If set to - 1, a single queue is used. If set to 0, tests are executed - directly, without queuing. - - - - - The random seed to be used for this assembly. If specified - as the value reported from a prior run, the framework should - generate identical random values for tests as were used for - that run, provided that no change has been made to the test - assembly. Default is a random value itself. - - - - - If true, execution stops after the first error or failure. - - - - - If true, use of the event queue is suppressed and test events are synchronous. - - - - - The different targets a test action attribute can be applied to - - - - - Default target, which is determined by where the action attribute is attached - - - - - Target a individual test case - - - - - Target a suite of test cases - - - - - DefaultTestAssemblyBuilder loads a single assembly and builds a TestSuite - containing test fixtures present in the assembly. - - - - - The ITestAssemblyBuilder interface is implemented by a class - that is able to build a suite of tests given an assembly or - an assembly filename. - - - - - Build a suite of tests from a provided assembly - - The assembly from which tests are to be built - A dictionary of options to use in building the suite - A TestSuite containing the tests found in the assembly - - - - Build a suite of tests given the filename of an assembly - - The filename of the assembly from which tests are to be built - A dictionary of options to use in building the suite - A TestSuite containing the tests found in the assembly - - - - The default suite builder used by the test assembly builder. - - - - - Initializes a new instance of the class. - - - - - Build a suite of tests from a provided assembly - - The assembly from which tests are to be built - A dictionary of options to use in building the suite - - A TestSuite containing the tests found in the assembly - - - - - Build a suite of tests given the filename of an assembly - - The filename of the assembly from which tests are to be built - A dictionary of options to use in building the suite - - A TestSuite containing the tests found in the assembly - - - - - FrameworkController provides a facade for use in loading, browsing - and running tests without requiring a reference to the NUnit - framework. All calls are encapsulated in constructors for - this class and its nested classes, which only require the - types of the Common Type System as arguments. - - The controller supports four actions: Load, Explore, Count and Run. - They are intended to be called by a driver, which should allow for - proper sequencing of calls. Load must be called before any of the - other actions. The driver may support other actions, such as - reload on run, by combining these calls. - - - - - A MarshalByRefObject that lives forever - - - - - Obtains a lifetime service object to control the lifetime policy for this instance. - - - - - Construct a FrameworkController using the default builder and runner. - - The AssemblyName or path to the test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - - - - Construct a FrameworkController using the default builder and runner. - - The test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - - - - Construct a FrameworkController, specifying the types to be used - for the runner and builder. This constructor is provided for - purposes of development. - - The full AssemblyName or the path to the test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - The Type of the test runner - The Type of the test builder - - - - Construct a FrameworkController, specifying the types to be used - for the runner and builder. This constructor is provided for - purposes of development. - - The test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - The Type of the test runner - The Type of the test builder - - - - Inserts environment element - - Target node - The new node - - - - Inserts settings element - - Target node - Settings dictionary - The new node - - - - Gets the ITestAssemblyBuilder used by this controller instance. - - The builder. - - - - Gets the ITestAssemblyRunner used by this controller instance. - - The runner. - - - - Gets the AssemblyName or the path for which this FrameworkController was created - - - - - Gets the Assembly for which this - - - - - Gets a dictionary of settings for the FrameworkController - - - - - FrameworkControllerAction is the base class for all actions - performed against a FrameworkController. - - - - - LoadTestsAction loads a test into the FrameworkController - - - - - LoadTestsAction loads the tests in an assembly. - - The controller. - The callback handler. - - - - ExploreTestsAction returns info about the tests in an assembly - - - - - Initializes a new instance of the class. - - The controller for which this action is being performed. - Filter used to control which tests are included (NYI) - The callback handler. - - - - CountTestsAction counts the number of test cases in the loaded TestSuite - held by the FrameworkController. - - - - - Construct a CountsTestAction and perform the count of test cases. - - A FrameworkController holding the TestSuite whose cases are to be counted - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - RunTestsAction runs the loaded TestSuite held by the FrameworkController. - - - - - Construct a RunTestsAction and run all tests in the loaded TestSuite. - - A FrameworkController holding the TestSuite to run - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - RunAsyncAction initiates an asynchronous test run, returning immediately - - - - - Construct a RunAsyncAction and run all tests in the loaded TestSuite. - - A FrameworkController holding the TestSuite to run - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - StopRunAction stops an ongoing run. - - - - - Construct a StopRunAction and stop any ongoing run. If no - run is in process, no error is raised. - - The FrameworkController for which a run is to be stopped. - True the stop should be forced, false for a cooperative stop. - >A callback handler used to report results - A forced stop will cause threads and processes to be killed as needed. - - - - The ITestAssemblyRunner interface is implemented by classes - that are able to execute a suite of tests loaded - from an assembly. - - - - - Loads the tests found in an Assembly, returning an - indication of whether or not the load succeeded. - - File name of the assembly to load - Dictionary of options to use in loading the test - An ITest representing the loaded tests - - - - Loads the tests found in an Assembly, returning an - indication of whether or not the load succeeded. - - The assembly to load - Dictionary of options to use in loading the test - An ITest representing the loaded tests - - - - Count Test Cases using a filter - - The filter to apply - The number of test cases found - - - - Run selected tests and return a test result. The test is run synchronously, - and the listener interface is notified as it progresses. - - Interface to receive ITestListener notifications. - A test filter used to select tests to be run - - - - Run selected tests asynchronously, notifying the listener interface as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - - - Wait for the ongoing run to complete. - - Time to wait in milliseconds - True if the run completed, otherwise false - - - - Signal any test run that is in process to stop. Return without error if no test is running. - - If true, kill any test-running threads - - - - Gets the tree of loaded tests, or null if - no tests have been loaded. - - - - - Gets the tree of test results, if the test - run is completed, otherwise null. - - - - - Indicates whether a test has been loaded - - - - - Indicates whether a test is currently running - - - - - Indicates whether a test run is complete - - - - - Implementation of ITestAssemblyRunner - - - - - Initializes a new instance of the class. - - The builder. - - - - Loads the tests found in an Assembly - - File name of the assembly to load - Dictionary of option settings for loading the assembly - True if the load was successful - - - - Loads the tests found in an Assembly - - The assembly to load - Dictionary of option settings for loading the assembly - True if the load was successful - - - - Count Test Cases using a filter - - The filter to apply - The number of test cases found - - - - Run selected tests and return a test result. The test is run synchronously, - and the listener interface is notified as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - - - - Run selected tests asynchronously, notifying the listener interface as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - RunAsync is a template method, calling various abstract and - virtual methods to be overridden by derived classes. - - - - - Wait for the ongoing run to complete. - - Time to wait in milliseconds - True if the run completed, otherwise false - - - - Initiate the test run. - - - - - Signal any test run that is in process to stop. Return without error if no test is running. - - If true, kill any test-running threads - - - - Create the initial TestExecutionContext used to run tests - - The ITestListener specified in the RunAsync call - - - - Handle the the Completed event for the top level work item - - - - - Gets the default level of parallel execution (worker threads) - - - - - The tree of tests that was loaded by the builder - - - - - The test result, if a run has completed - - - - - Indicates whether a test is loaded - - - - - Indicates whether a test is running - - - - - Indicates whether a test run is complete - - - - - Our settings, specified when loading the assembly - - - - - The top level WorkItem created for the assembly as a whole - - - - - The TestExecutionContext for the top level WorkItem - - - - - The Assert class contains a collection of static methods that - implement the most common assertions used in NUnit. - - - - - Verifies that the first int is greater than the second - int. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first int is greater than the second - int. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - - - - We don't actually want any instances of this object, but some people - like to inherit from it to add other static methods. Hence, the - protected constructor disallows any instances of this object. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - The message to initialize the with. - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - - - - Throws an with the message and arguments - that are passed in. This is used by the other Assert functions. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This is used by the other Assert functions. - - The message to initialize the with. - - - - Throws an . - This is used by the other Assert functions. - - - - - Throws an with the message and arguments - that are passed in. This causes the test to be reported as ignored. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This causes the test to be reported as ignored. - - The message to initialize the with. - - - - Throws an . - This causes the test to be reported as ignored. - - - - - Throws an with the message and arguments - that are passed in. This causes the test to be reported as inconclusive. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This causes the test to be reported as inconclusive. - - The message to initialize the with. - - - - Throws an . - This causes the test to be reported as Inconclusive. - - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - - - - Helper for Assert.AreEqual(double expected, double actual, ...) - allowing code generation to work consistently. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestDelegate - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestDelegate - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - A TestDelegate - - - - Verifies that a delegate does not throw an exception - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate does not throw an exception. - - A TestDelegate - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - - This method is provided for use by VB developers needing to test - the value of properties with private setters. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Delegate used by tests that execute code and - capture any thrown exception. - - - - - AssertionHelper is an optional base class for user tests, - allowing the use of shorter ids for constraints and - asserts and avoiding conflict with the definition of - , from which it inherits much of its - behavior, in certain mock object frameworks. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that fails if the actual - value matches the pattern supplied as an argument. - - - - - Returns a constraint that tests whether the path provided - is the same as an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is a subpath of the expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether an object graph is serializable in binary format. - - - - - Returns a constraint that tests whether an object graph is serializable in xml format. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically to - . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically to . - - The evaluated condition - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Returns a ListMapper based on a collection. - - The original collection - - - - - Provides static methods to express the assumptions - that must be met for a test to give a meaningful - result. If an assumption is not met, the test - should produce an inconclusive result. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the - method throws an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Marks a test that must run in a particular threading apartment state, causing it - to run in a separate thread if necessary. - - - - - PropertyAttribute is used to attach information to a test as a name/value pair.. - - - - - The abstract base class for all custom attributes defined by NUnit. - - - - - Default constructor - - - - - The IApplyToTest interface is implemented by self-applying - attributes that modify the state of a test in some way. - - - - - Modifies a test as defined for the specific attribute. - - The test to modify - - - - Construct a PropertyAttribute with a name and string value - - The name of the property - The property value - - - - Construct a PropertyAttribute with a name and int value - - The name of the property - The property value - - - - Construct a PropertyAttribute with a name and double value - - The name of the property - The property value - - - - Constructor for derived classes that set the - property dictionary directly. - - - - - Constructor for use by derived classes that use the - name of the type as the property name. Derived classes - must ensure that the Type of the property value is - a standard type supported by the BCL. Any custom - types will cause a serialization Exception when - in the client. - - - - - Modifies a test by adding properties to it. - - The test to modify - - - - Gets the property dictionary for this attribute - - - - - Construct an ApartmentAttribute - - The apartment state that this test must be run under. You must pass in a valid apartment state. - - - - Provides the Author of a test or test fixture. - - - - - Initializes a new instance of the class. - - The name of the author. - - - - Initializes a new instance of the class. - - The name of the author. - The email address of the author. - - - - Attribute used to apply a category to a test - - - - - The name of the category - - - - - Construct attribute for a given category based on - a name. The name may not contain the characters ',', - '+', '-' or '!'. However, this is not checked in the - constructor since it would cause an error to arise at - as the test was loaded without giving a clear indication - of where the problem is located. The error is handled - in NUnitFramework.cs by marking the test as not - runnable. - - The name of the category - - - - Protected constructor uses the Type name as the name - of the category. - - - - - Modifies a test by adding a category to it. - - The test to modify - - - - The name of the category - - - - - Marks a test to use a combinatorial join of any argument - data provided. Since this is the default, the attribute is - optional. - - - - - Marks a test to use a particular CombiningStrategy to join - any parameter data provided. Since this is the default, the - attribute is optional. - - - - - The ITestBuilder interface is exposed by a class that knows how to - build one or more TestMethods from a MethodInfo. In general, it is exposed - by an attribute, which has additional information available to provide - the necessary test parameters to distinguish the test cases built. - - - - - Build one or more TestMethods from the provided MethodInfo. - - The method to be used as a test - The TestSuite to which the method will be added - A TestMethod object - - - - Construct a CombiningStrategyAttribute incorporating an object - that implements ICombiningStrategy. - - Combining strategy to be used - - - - Construct a CombiningStrategyAttribute incorporating an object - that implements ICombiningStrategy. This constructor is provided - for CLS compliance. - - Combining strategy to be used - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Modify the test by adding the name of the combining strategy - to the properties. - - The test to modify - - - - Default constructor - - - - - CultureAttribute is used to mark a test fixture or an - individual method as applying to a particular Culture only. - - - - - Abstract base for Attributes that are used to include tests - in the test run based on environmental settings. - - - - - Constructor with no included items specified, for use - with named property syntax. - - - - - Constructor taking one or more included items - - Comma-delimited list of included items - - - - Name of the item that is needed in order for - a test to run. Multiple items may be given, - separated by a comma. - - - - - Name of the item to be excluded. Multiple items - may be given, separated by a comma. - - - - - The reason for including or excluding the test - - - - - Constructor with no cultures specified, for use - with named property syntax. - - - - - Constructor taking one or more cultures - - Comma-deliminted list of cultures - - - - Causes a test to be skipped if this CultureAttribute is not satisfied. - - The test to modify - - - - Tests to determine if the current culture is supported - based on the properties of this attribute. - - True, if the current culture is supported - - - - Test to determine if the a particular culture or comma- - delimited set of cultures is in use. - - Name of the culture or comma-separated list of culture ids - True if the culture is in use on the system - - - - Test to determine if one of a collection of cultures - is being used currently. - - - - - - - The abstract base class for all data-providing attributes - defined by NUnit. Used to select all data sources for a - method, class or parameter. - - - - - Default constructor - - - - - Used to mark a field for use as a datapoint when executing a theory - within the same fixture that requires an argument of the field's Type. - - - - - Used to mark a field, property or method providing a set of datapoints to - be used in executing any theories within the same fixture that require an - argument of the Type provided. The data source may provide an array of - the required Type or an . - Synonymous with DatapointSourceAttribute. - - - - - Used to mark a field, property or method providing a set of datapoints to - be used in executing any theories within the same fixture that require an - argument of the Type provided. The data source may provide an array of - the required Type or an . - Synonymous with DatapointsAttribute. - - - - - Attribute used to provide descriptive text about a - test case or fixture. - - - - - Construct a description Attribute - - The text of the description - - - - ExplicitAttribute marks a test or test fixture so that it will - only be run if explicitly executed from the gui or command line - or if it is included by use of a filter. The test will not be - run simply because an enclosing suite is run. - - - - - Default constructor - - - - - Constructor with a reason - - The reason test is marked explicit - - - - Modifies a test by marking it as explicit. - - The test to modify - - - - Attribute used to mark a test that is to be ignored. - Ignored tests result in a warning message when the - tests are run. - - - - - Constructs the attribute giving a reason for ignoring the test - - The reason for ignoring the test - - - - Modifies a test by marking it as Ignored. - - The test to modify - - - - The date in the future to stop ignoring the test as a string in UTC time. - For example for a date and time, "2014-12-25 08:10:00Z" or for just a date, - "2014-12-25". If just a date is given, the Ignore will expire at midnight UTC. - - - Once the ignore until date has passed, the test will be marked - as runnable. Tests with an ignore until date will have an IgnoreUntilDate - property set which will appear in the test results. - - The string does not contain a valid string representation of a date and time. - - - - LevelOfParallelismAttribute is used to set the number of worker threads - that may be allocated by the framework for running tests. - - - - - Construct a LevelOfParallelismAttribute. - - The number of worker threads to be created by the framework. - - - - Summary description for MaxTimeAttribute. - - - - - Objects implementing this interface are used to wrap - the entire test, including SetUp and TearDown. - - - - - ICommandWrapper is implemented by attributes and other - objects able to wrap a TestCommand with another command. - - - Attributes or other objects should implement one of the - derived interfaces, rather than this one, since they - indicate in which part of the command chain the wrapper - should be applied. - - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - Construct a MaxTimeAttribute, given a time in milliseconds. - - The maximum elapsed time in milliseconds - - - - Attribute used to identify a method that is called once - to perform setup before any child tests are run. - - - - - Attribute used to identify a method that is called once - after all the child tests have run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Marks a test to use a pairwise join of any argument - data provided. Arguments will be combined in such a - way that all possible pairs of arguments are used. - - - - - Default constructor - - - - - ParallelizableAttribute is used to mark tests that may be run in parallel. - - - - - The IApplyToContext interface is implemented by attributes - that want to make changes to the execution context before - a test is run. - - - - - Apply changes to the execution context - - The execution context - - - - Construct a ParallelizableAttribute using default ParallelScope.Self. - - - - - Construct a ParallelizableAttribute with a specified scope. - - The ParallelScope associated with this attribute. - - - - Modify the context to be used for child tests - - The current TestExecutionContext - - - - The ParallelScope enumeration permits specifying the degree to - which a test and its descendants may be run in parallel. - - - - - No Parallelism is permitted - - - - - The test itself may be run in parallel with others at the same level - - - - - Descendants of the test may be run in parallel with one another - - - - - Descendants of the test down to the level of TestFixtures may be run in parallel - - - - - PlatformAttribute is used to mark a test fixture or an - individual method as applying to a particular platform only. - - - - - Constructor with no platforms specified, for use - with named property syntax. - - - - - Constructor taking one or more platforms - - Comma-delimited list of platforms - - - - Causes a test to be skipped if this PlatformAttribute is not satisfied. - - The test to modify - - - - RandomAttribute is used to supply a set of random _values - to a single parameter of a parameterized test. - - - - - The IParameterDataSource interface is implemented by types - that can provide data for a test method parameter. - - - - - Gets an enumeration of data items for use as arguments - for a test method parameter. - - The parameter for which data is needed - An enumeration containing individual data items - - - - Construct a random set of values appropriate for the Type of the - parameter on which the attribute appears, specifying only the count. - - - - - - Construct a set of ints within a specified range - - - - - Construct a set of unsigned ints within a specified range - - - - - Construct a set of longs within a specified range - - - - - Construct a set of unsigned longs within a specified range - - - - - Construct a set of shorts within a specified range - - - - - Construct a set of unsigned shorts within a specified range - - - - - Construct a set of doubles within a specified range - - - - - Construct a set of floats within a specified range - - - - - Construct a set of bytes within a specified range - - - - - Construct a set of sbytes within a specified range - - - - - Get the collection of _values to be used as arguments. - - - - - RangeAttribute is used to supply a range of _values to an - individual parameter of a parameterized test. - - - - - ValuesAttribute is used to provide literal arguments for - an individual parameter of a test. - - - - - The collection of data to be returned. Must - be set by any derived attribute classes. - We use an object[] so that the individual - elements may have their type changed in GetData - if necessary - - - - - Constructs for use with an Enum parameter. Will pass every enum - value in to the test. - - - - - Construct with one argument - - - - - - Construct with two arguments - - - - - - - Construct with three arguments - - - - - - - - Construct with an array of arguments - - - - - - Get the collection of _values to be used as arguments - - - - - Construct a range of ints using default step of 1 - - - - - - - Construct a range of ints specifying the step size - - - - - - - - Construct a range of unsigned ints using default step of 1 - - - - - - - Construct a range of unsigned ints specifying the step size - - - - - - - - Construct a range of longs using a default step of 1 - - - - - - - Construct a range of longs - - - - - - - - Construct a range of unsigned longs using default step of 1 - - - - - - - Construct a range of unsigned longs specifying the step size - - - - - - - - Construct a range of doubles - - - - - - - - Construct a range of floats - - - - - - - - RepeatAttribute may be applied to test case in order - to run it multiple times. - - - - - Construct a RepeatAttribute - - The number of times to run the test - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - The test command for the RepeatAttribute - - - - - TODO: Documentation needed for class - - - - - TestCommand is the abstract base class for all test commands - in the framework. A TestCommand represents a single stage in - the execution of a test, e.g.: SetUp/TearDown, checking for - Timeout, verifying the returned result from a method, etc. - - TestCommands may decorate other test commands so that the - execution of a lower-level command is nested within that - of a higher level command. All nested commands are executed - synchronously, as a single unit. Scheduling test execution - on separate threads is handled at a higher level, using the - task dispatcher. - - - - - Construct a TestCommand for a test. - - The test to be executed - - - - Runs the test in a specified context, returning a TestResult. - - The TestExecutionContext to be used for running the test. - A TestResult - - - - Gets the test associated with this command. - - - - TODO: Documentation needed for field - - - - TODO: Documentation needed for constructor - - - - - - Initializes a new instance of the class. - - The inner command. - The number of repetitions - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - Marks a test that must run in the MTA, causing it - to run in a separate thread if necessary. - - On methods, you may also use MTAThreadAttribute - to serve the same purpose. - - - - - Construct a RequiresMTAAttribute - - - - - Marks a test that must run in the STA, causing it - to run in a separate thread if necessary. - - - - - Construct a RequiresSTAAttribute - - - - - Marks a test that must run on a separate thread. - - - - - Construct a RequiresThreadAttribute - - - - - Construct a RequiresThreadAttribute, specifying the apartment - - - - - RepeatAttribute may be applied to test case in order - to run it multiple times. - - - - - Construct a RepeatAttribute - - The number of times to run the test - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - The test command for the RetryAttribute - - - - - Initializes a new instance of the class. - - The inner command. - The number of repetitions - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - Marks a test to use a Sequential join of any argument - data provided. Arguments will be combined into test cases, - taking the next value of each argument until all are used. - - - - - Default constructor - - - - - Summary description for SetCultureAttribute. - - - - - Construct given the name of a culture - - - - - - Summary description for SetUICultureAttribute. - - - - - Construct given the name of a culture - - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - SetUpFixtureAttribute is used to identify a SetUpFixture - - - - - The IFixtureBuilder interface is exposed by a class that knows how to - build a TestFixture from one or more Types. In general, it is exposed - by an attribute, but may be implemented in a helper class used by the - attribute in some cases. - - - - - Build one or more TestFixtures from type provided. At least one - non-null TestSuite must always be returned, since the method is - generally called because the user has marked the target class as - a fixture. If something prevents the fixture from being used, it - will be returned nonetheless, labelled as non-runnable. - - The type info of the fixture to be used. - A TestSuite object or one derived from TestSuite. - - - - Build a SetUpFixture from type provided. Normally called for a Type - on which the attribute has been placed. - - The type info of the fixture to be used. - A SetUpFixture object as a TestSuite. - - - - Attribute used to identify a method that is called - immediately after each test is run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Provide actions to execute before and after tests. - - - - - When implemented by an attribute, this interface implemented to provide actions to execute before and after tests. - - - - - Executed before each test is run - - The test that is going to be run. - - - - Executed after each test is run - - The test that has just been run. - - - - Provides the target for the action attribute - - The target for the action attribute - - - - Executed before each test is run - - The test that is going to be run. - - - - Executed after each test is run - - The test that has just been run. - - - - Provides the target for the action attribute - - - - - Adding this attribute to a method within a - class makes the method callable from the NUnit test runner. There is a property - called Description which is optional which you can provide a more detailed test - description. This class cannot be inherited. - - - - [TestFixture] - public class Fixture - { - [Test] - public void MethodToTest() - {} - - [Test(Description = "more detailed description")] - public void TestDescriptionMethod() - {} - } - - - - - - The ISimpleTestBuilder interface is exposed by a class that knows how to - build a single TestMethod from a suitable MethodInfo Types. In general, - it is exposed by an attribute, but may be implemented in a helper class - used by the attribute in some cases. - - - - - Build a TestMethod from the provided MethodInfo. - - The method to be used as a test - The TestSuite to which the method will be added - A TestMethod object - - - - IImplyFixture is an empty marker interface used by attributes like - TestAttribute that cause the class where they are used to be treated - as a TestFixture even without a TestFixtureAttribute. - - Marker interfaces are not usually considered a good practice, but - we use it here to avoid cluttering the attribute hierarchy with - classes that don't contain any extra implementation. - - - - - Modifies a test by adding a description, if not already set. - - The test to modify - - - - Construct a TestMethod from a given method. - - The method for which a test is to be constructed. - The suite to which the test will be added. - A TestMethod - - - - Descriptive text for this test - - - - - The author of this test - - - - - The type that this test is testing - - - - - Gets or sets the expected result. - - The result. - - - - Returns true if an expected result has been set - - - - - TestCaseAttribute is used to mark parameterized test cases - and provide them with their arguments. - - - - - The ITestCaseData interface is implemented by a class - that is able to return complete testcases for use by - a parameterized test method. - - - - - The ITestData interface is implemented by a class that - represents a single instance of a parameterized test. - - - - - Gets the name to be used for the test - - - - - Gets the RunState for this test case. - - - - - Gets the argument list to be provided to the test - - - - - Gets the property dictionary for the test case - - - - - Gets the expected result of the test case - - - - - Returns true if an expected result has been set - - - - - Construct a TestCaseAttribute with a list of arguments. - This constructor is not CLS-Compliant - - - - - - Construct a TestCaseAttribute with a single argument - - - - - - Construct a TestCaseAttribute with a two arguments - - - - - - - Construct a TestCaseAttribute with a three arguments - - - - - - - - Performs several special conversions allowed by NUnit in order to - permit arguments with types that cannot be used in the constructor - of an Attribute such as TestCaseAttribute or to simplify their use. - - The arguments to be converted - The ParameterInfo array for the method - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Gets or sets the name of the test. - - The name of the test. - - - - Gets or sets the RunState of this test case. - - - - - Gets the list of arguments to a test case - - - - - Gets the properties of the test case - - - - - Gets or sets the expected result. - - The result. - - - - Returns true if the expected result has been set - - - - - Gets or sets the description. - - The description. - - - - The author of this test - - - - - The type that this test is testing - - - - - Gets or sets the reason for ignoring the test - - - - - Gets or sets a value indicating whether this is explicit. - - - true if explicit; otherwise, false. - - - - - Gets or sets the reason for not running the test. - - The reason. - - - - Gets or sets the ignore reason. When set to a non-null - non-empty value, the test is marked as ignored. - - The ignore reason. - - - - Comma-delimited list of platforms to run the test for - - - - - Comma-delimited list of platforms to not run the test for - - - - - Gets and sets the category for this test case. - May be a comma-separated list of categories. - - - - - TestCaseSourceAttribute indicates the source to be used to - provide test cases for a test method. - - - - - Construct with the name of the method, property or field that will provide data - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Construct with a Type - - The type that will provide data - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The IMethod for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Returns a set of ITestCaseDataItems for use as arguments - to a parameterized test method. - - The method for which data is needed. - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - Gets or sets the category associated with every fixture created from - this attribute. May be a single category or a comma-separated list. - - - - - TestFixtureAttribute is used to mark a class that represents a TestFixture. - - - - - The ITestCaseData interface is implemented by a class - that is able to return the data required to create an - instance of a parameterized test fixture. - - - - - Get the TypeArgs if separately set - - - - - Default constructor - - - - - Construct with a object[] representing a set of arguments. - In .NET 2.0, the arguments may later be separated into - type arguments and constructor arguments. - - - - - - Build a fixture from type provided. Normally called for a Type - on which the attribute has been placed. - - The type info of the fixture to be used. - A an IEnumerable holding one TestFixture object. - - - - Gets or sets the name of the test. - - The name of the test. - - - - Gets or sets the RunState of this test fixture. - - - - - The arguments originally provided to the attribute - - - - - Properties pertaining to this fixture - - - - - Get or set the type arguments. If not set - explicitly, any leading arguments that are - Types are taken as type arguments. - - - - - Descriptive text for this fixture - - - - - The author of this fixture - - - - - The type that this fixture is testing - - - - - Gets or sets the ignore reason. May set RunState as a side effect. - - The ignore reason. - - - - Gets or sets the reason for not running the fixture. - - The reason. - - - - Gets or sets the ignore reason. When set to a non-null - non-empty value, the test is marked as ignored. - - The ignore reason. - - - - Gets or sets a value indicating whether this is explicit. - - - true if explicit; otherwise, false. - - - - - Gets and sets the category for this fixture. - May be a comma-separated list of categories. - - - - - Attribute used to identify a method that is - called before any tests in a fixture are run. - - - - - TestCaseSourceAttribute indicates the source to be used to - provide test fixture instances for a test class. - - - - - Error message string is public so the tests can use it - - - - - Construct with the name of the method, property or field that will provide data - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Construct with a Type - - The type that will provide data - - - - Construct one or more TestFixtures from a given Type, - using available parameter data. - - The TypeInfo for which fixures are to be constructed. - One or more TestFixtures as TestSuite - - - - Returns a set of ITestFixtureData items for use as arguments - to a parameterized test fixture. - - The type for which data is needed. - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - Gets or sets the category associated with every fixture created from - this attribute. May be a single category or a comma-separated list. - - - - - Attribute used to identify a method that is called after - all the tests in a fixture have run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Indicates which class the test or test fixture is testing - - - - - Initializes a new instance of the class. - - The type that is being tested. - - - - Initializes a new instance of the class. - - The type that is being tested. - - - - Adding this attribute to a method within a - class makes the method callable from the NUnit test runner. There is a property - called Description which is optional which you can provide a more detailed test - description. This class cannot be inherited. - - - - [TestFixture] - public class Fixture - { - [Test] - public void MethodToTest() - {} - - [Test(Description = "more detailed description")] - public void TestDescriptionMethod() - {} - } - - - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Used on a method, marks the test with a timeout value in milliseconds. - The test will be run in a separate thread and is cancelled if the timeout - is exceeded. Used on a class or assembly, sets the default timeout - for all contained test methods. - - - - - Construct a TimeoutAttribute given a time in milliseconds - - The timeout value in milliseconds - - - - ValueSourceAttribute indicates the source to be used to - provide data for one parameter of a test method. - - - - - Construct with the name of the factory - for use with languages - that don't support params arrays. - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - for use with languages - that don't support params arrays. - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Gets an enumeration of data items for use as arguments - for a test method parameter. - - The parameter for which data is needed - - An enumeration containing individual data items - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - A set of Assert methods operating on one or more collections - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable containing objects to be considered - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the superset does not contain the subset - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - - - - Asserts that the superset does not contain the subset - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the superset contains the subset. - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - - - - Asserts that the superset contains the subset. - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the subset does not contain the superset - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - - - - Asserts that the subset does not contain the superset - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the subset contains the superset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - - - - Asserts that the subset contains the superset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array,list or other collection is empty - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array,list or other collection is empty - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - - - - Provides a platform-independent methods for getting attributes - for use by AttributeConstraint and AttributeExistsConstraint. - - - - - Gets the custom attributes from the given object. - - Portable libraries do not have an ICustomAttributeProvider, so we need to cast to each of - it's direct subtypes and try to get attributes off those instead. - The actual. - Type of the attribute. - if set to true [inherit]. - A list of the given attribute on the given object. - - - - This class is a System.Diagnostics.Stopwatch on operating systems that support it. On those that don't, - it replicates the functionality at the resolution supported. - - - - - AllItemsConstraint applies another constraint to each - item in a collection, succeeding if they all succeed. - - - - - Abstract base class used for prefixes - - - - - The Constraint class is the base of all built-in constraints - within NUnit. It provides the operator overloads used to combine - constraints. - - - - - Interface for all constraints - - - - - The IResolveConstraint interface is implemented by all - complete and resolvable constraints and expressions. - - - - - Return the top-level constraint for this expression - - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - The default implementation simply dereferences the value but - derived classes may override it to provide for delayed processing. - - A reference to the value to be tested - A ConstraintResult - - - - The display name of this Constraint for use by ToString(). - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Arguments provided to this Constraint, for use in - formatting the description. - - - - - The ConstraintBuilder holding this constraint - - - - - Construct a constraint with optional arguments - - Arguments to be saved - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - The default implementation simply dereferences the value but - derived classes may override it to provide for delayed processing. - - A reference to the value to be tested - A ConstraintResult - - - - Default override of ToString returns the constraint DisplayName - followed by any arguments within angle brackets. - - - - - - Returns the string representation of this constraint - - - - - This operator creates a constraint that is satisfied only if both - argument constraints are satisfied. - - - - - This operator creates a constraint that is satisfied if either - of the argument constraints is satisfied. - - - - - This operator creates a constraint that is satisfied if the - argument constraint is not satisfied. - - - - - Returns a DelayedConstraint with the specified delay time. - - The delay in milliseconds. - - - - - Returns a DelayedConstraint with the specified delay time - and polling interval. - - The delay in milliseconds. - The interval at which to test the constraint. - - - - - Resolves any pending operators and returns the resolved constraint. - - - - - The display name of this Constraint for use by ToString(). - The default value is the name of the constraint with - trailing "Constraint" removed. Derived classes may set - this to another name in their constructors. - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Arguments provided to this Constraint, for use in - formatting the description. - - - - - The ConstraintBuilder holding this constraint - - - - - Returns a ConstraintExpression by appending And - to the current constraint. - - - - - Returns a ConstraintExpression by appending And - to the current constraint. - - - - - Returns a ConstraintExpression by appending Or - to the current constraint. - - - - - The base constraint - - - - - Prefix used in forming the constraint description - - - - - Construct given a base constraint - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Construct an AllItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - failing if any item fails. - - - - - - - AndConstraint succeeds only if both members succeed. - - - - - BinaryConstraint is the abstract base of all constraints - that combine two other constraints in some fashion. - - - - - The first constraint being combined - - - - - The second constraint being combined - - - - - Construct a BinaryConstraint from two other constraints - - The first constraint - The second constraint - - - - Create an AndConstraint from two other constraints - - The first constraint - The second constraint - - - - Apply both member constraints to an actual value, succeeding - succeeding only if both of them succeed. - - The actual value - True if the constraints both succeeded - - - - Gets text describing a constraint - - - - - Contain the result of matching a against an actual value. - - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - The status of the new ConstraintResult. - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - If true, applies a status of Success to the result, otherwise Failure. - - - - Write the failure message to the MessageWriter provided - as an argument. The default implementation simply passes - the result and the actual value to the writer, which - then displays the constraint description and the value. - - Constraints that need to provide additional details, - such as where the error occured can override this. - - The MessageWriter on which to display the message - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - The actual value that was passed to the method. - - - - - Gets and sets the ResultStatus for this result. - - - - - True if actual value meets the Constraint criteria otherwise false. - - - - - Display friendly name of the constraint. - - - - - Description of the constraint may be affected by the state the constraint had - when was performed against the actual value. - - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - AssignableFromConstraint is used to test that an object - can be assigned from a given Type. - - - - - TypeConstraint is the abstract base for constraints - that take a Type as their expected value. - - - - - The expected Type used by the constraint - - - - - The type of the actual argument to which the constraint was applied - - - - - Construct a TypeConstraint for a given Type - - The expected type for the constraint - Prefix used in forming the constraint description - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - Construct an AssignableFromConstraint for the type provided - - - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - AssignableToConstraint is used to test that an object - can be assigned to a given Type. - - - - - Construct an AssignableToConstraint for the type provided - - - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - AttributeConstraint tests that a specified attribute is present - on a Type or other provider and that the value of the attribute - satisfies some other constraint. - - - - - Constructs an AttributeConstraint for a specified attribute - Type and base constraint. - - - - - - - Determines whether the Type or other provider has the - expected attribute and if its value matches the - additional constraint specified. - - - - - Returns a string representation of the constraint. - - - - - AttributeExistsConstraint tests for the presence of a - specified attribute on a Type. - - - - - Constructs an AttributeExistsConstraint for a specific attribute Type - - - - - - Tests whether the object provides the expected attribute. - - A Type, MethodInfo, or other ICustomAttributeProvider - True if the expected attribute is present, otherwise false - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - BinarySerializableConstraint tests whether - an object is serializable in binary format. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Returns the string representation - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionConstraint is the abstract base class for - constraints that operate on collections. - - - - - Construct an empty CollectionConstraint - - - - - Construct a CollectionConstraint - - - - - - Determines whether the specified enumerable is empty. - - The enumerable. - - true if the specified enumerable is empty; otherwise, false. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Protected method to be implemented by derived classes - - - - - - - CollectionContainsConstraint is used to test whether a collection - contains an expected object as a member. - - - - - CollectionItemsEqualConstraint is the abstract base class for all - collection constraints that apply some notion of item equality - as a part of their operation. - - - - - Construct an empty CollectionConstraint - - - - - Construct a CollectionConstraint - - - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Compares two collection members for equality - - - - - Return a new CollectionTally for use in making tests - - The collection to be included in the tally - - - - Flag the constraint to ignore case and return self. - - - - - Construct a CollectionContainsConstraint - - - - - - Test whether the expected item is contained in the collection - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Gets the expected object - - - - - CollectionEquivalentConstraint is used to determine whether two - collections are equivalent. - - - - - Construct a CollectionEquivalentConstraint - - - - - - Test whether two collections are equivalent - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionOrderedConstraint is used to test whether a collection is ordered. - - - - - Construct a CollectionOrderedConstraint - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use a and returns self. - - - - - Modifies the constraint to test ordering by the value of - a specified property and returns self. - - - - - Test whether the collection is ordered - - - - - - - Returns the string representation of the constraint. - - - - - - If used performs a reverse comparison - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionSubsetConstraint is used to determine whether - one collection is a subset of another - - - - - Construct a CollectionSubsetConstraint - - The collection that the actual value is expected to be a subset of - - - - Test whether the actual collection is a subset of - the expected collection provided. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionSupersetConstraint is used to determine whether - one collection is a superset of another - - - - - Construct a CollectionSupersetConstraint - - The collection that the actual value is expected to be a superset of - - - - Test whether the actual collection is a superset of - the expected collection provided. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionTally counts (tallies) the number of - occurrences of each object in one or more enumerations. - - - - - Construct a CollectionTally object from a comparer and a collection - - - - - Try to remove an object from the tally - - The object to remove - True if successful, false if the object was not found - - - - Try to remove a set of objects from the tally - - The objects to remove - True if successful, false if any object was not found - - - - The number of objects remaining in the tally - - - - - ComparisonAdapter class centralizes all comparisons of - _values in NUnit, adapting to the use of any provided - , - or . - - - - - Returns a ComparisonAdapter that wraps an - - - - - Returns a ComparisonAdapter that wraps an - - - - - Returns a ComparisonAdapter that wraps a - - - - - Compares two objects - - - - - Gets the default ComparisonAdapter, which wraps an - NUnitComparer object. - - - - - Construct a ComparisonAdapter for an - - - - - Compares two objects - - - - - - - - Construct a default ComparisonAdapter - - - - - ComparerAdapter extends and - allows use of an or - to actually perform the comparison. - - - - - Construct a ComparisonAdapter for an - - - - - Compare a Type T to an object - - - - - Construct a ComparisonAdapter for a - - - - - Compare a Type T to an object - - - - - Abstract base class for constraints that compare _values to - determine if one is greater than, equal to or less than - the other. - - - - - The value against which a comparison is to be made - - - - - If true, less than returns success - - - - - if true, equal returns success - - - - - if true, greater than returns success - - - - - ComparisonAdapter to be used in making the comparison - - - - - Initializes a new instance of the class. - - The value against which to make a comparison. - if set to true less succeeds. - if set to true equal succeeds. - if set to true greater succeeds. - String used in describing the constraint. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Modifies the constraint to use an and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Modifies the constraint to use an and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Modifies the constraint to use a and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Delegate used to delay evaluation of the actual value - to be used in evaluating a constraint - - - - - ConstraintBuilder maintains the stacks that are used in - processing a ConstraintExpression. An OperatorStack - is used to hold operators that are waiting for their - operands to be reorganized. a ConstraintStack holds - input constraints as well as the results of each - operator applied. - - - - - Initializes a new instance of the class. - - - - - Appends the specified operator to the expression by first - reducing the operator stack and then pushing the new - operator on the stack. - - The operator to push. - - - - Appends the specified constraint to the expression by pushing - it on the constraint stack. - - The constraint to push. - - - - Sets the top operator right context. - - The right context. - - - - Reduces the operator stack until the topmost item - precedence is greater than or equal to the target precedence. - - The target precedence. - - - - Resolves this instance, returning a Constraint. If the Builder - is not currently in a resolvable state, an exception is thrown. - - The resolved constraint - - - - Gets a value indicating whether this instance is resolvable. - - - true if this instance is resolvable; otherwise, false. - - - - - OperatorStack is a type-safe stack for holding ConstraintOperators - - - - - Initializes a new instance of the class. - - The ConstraintBuilder using this stack. - - - - Pushes the specified operator onto the stack. - - The operator to put onto the stack. - - - - Pops the topmost operator from the stack. - - The topmost operator on the stack - - - - Gets a value indicating whether this is empty. - - true if empty; otherwise, false. - - - - Gets the topmost operator without modifying the stack. - - - - - ConstraintStack is a type-safe stack for holding Constraints - - - - - Initializes a new instance of the class. - - The ConstraintBuilder using this stack. - - - - Pushes the specified constraint. As a side effect, - the constraint's Builder field is set to the - ConstraintBuilder owning this stack. - - The constraint to put onto the stack - - - - Pops this topmost constraint from the stack. - As a side effect, the constraint's Builder - field is set to null. - - The topmost contraint on the stack - - - - Gets a value indicating whether this is empty. - - true if empty; otherwise, false. - - - - ConstraintExpression represents a compound constraint in the - process of being constructed from a series of syntactic elements. - - Individual elements are appended to the expression as they are - reorganized. When a constraint is appended, it is returned as the - value of the operation so that modifiers may be applied. However, - any partially built expression is attached to the constraint for - later resolution. When an operator is appended, the partial - expression is returned. If it's a self-resolving operator, then - a ResolvableConstraintExpression is returned. - - - - - The ConstraintBuilder holding the elements recognized so far - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class passing in a ConstraintBuilder, which may be pre-populated. - - The builder. - - - - Returns a string representation of the expression as it - currently stands. This should only be used for testing, - since it has the side-effect of resolving the expression. - - - - - - Appends an operator to the expression and returns the - resulting expression itself. - - - - - Appends a self-resolving operator to the expression and - returns a new ResolvableConstraintExpression. - - - - - Appends a constraint to the expression and returns that - constraint, which is associated with the current state - of the expression being built. Note that the constraint - is not reduced at this time. For example, if there - is a NotOperator on the stack we don't reduce and - return a NotConstraint. The original constraint must - be returned because it may support modifiers that - are yet to be applied. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns the constraint provided as an argument - used to allow custom - custom constraints to easily participate in the syntax. - - - - - Returns the constraint provided as an argument - used to allow custom - custom constraints to easily participate in the syntax. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that tests whether the path provided - is the same as an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the a subpath of the expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - With is currently a NOP - reserved for future use. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether an object graph is serializable in binary format. - - - - - Returns a constraint that tests whether an object graph is serializable in xml format. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - Returns a constraint that succeeds if the value - is a file or directory and it exists. - - - - - ConstraintStatus represents the status of a ConstraintResult - returned by a Constraint being applied to an actual value. - - - - - The status has not yet been set - - - - - The constraint succeeded - - - - - The constraint failed - - - - - An error occured in applying the constraint (reserved for future use) - - - - - ContainsConstraint tests a whether a string contains a substring - or a collection contains an object. It postpones the decision of - which test to use until the type of the actual argument is known. - This allows testing whether a string is contained in a collection - or as a substring of another string using the same syntax. - - - - - Initializes a new instance of the class. - - The _expected. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Flag the constraint to ignore case and return self. - - - - - Applies a delay to the match so that a match can be evaluated in the future. - - - - - Creates a new DelayedConstraint - - The inner constraint to decorate - The time interval after which the match is performed - If the value of is less than 0 - - - - Creates a new DelayedConstraint - - The inner constraint to decorate - The time interval after which the match is performed, in milliseconds - The time interval used for polling, in milliseconds - If the value of is less than 0 - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for if the base constraint fails, false if it succeeds - - - - Test whether the constraint is satisfied by a delegate - - The delegate whose value is to be tested - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - Overridden to wait for the specified delay period before - calling the base constraint with the dereferenced value. - - A reference to the value to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - Adjusts a Timestamp by a given TimeSpan - - - - - - - - Returns the difference between two Timestamps as a TimeSpan - - - - - - - - Gets text describing a constraint - - - - - DictionaryContainsKeyConstraint is used to test whether a dictionary - contains an expected object as a key. - - - - - Construct a DictionaryContainsKeyConstraint - - - - - - Test whether the expected key is contained in the dictionary - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - DictionaryContainsValueConstraint is used to test whether a dictionary - contains an expected object as a value. - - - - - Construct a DictionaryContainsValueConstraint - - - - - - Test whether the expected value is contained in the dictionary - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyCollectionConstraint tests whether a collection is empty. - - - - - Check that the collection is empty - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyConstraint tests a whether a string or collection is empty, - postponing the decision about which test is applied until the - type of the actual argument is known. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyDirectoryConstraint is used to test that a directory is empty - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyStringConstraint tests whether a string is empty. - - - - - StringConstraint is the abstract base for constraints - that operate on strings. It supports the IgnoreCase - modifier for string operations. - - - - - The expected value - - - - - Indicates whether tests should be case-insensitive - - - - - Description of this constraint - - - - - Constructs a StringConstraint without an expected value - - - - - Constructs a StringConstraint given an expected value - - The expected value - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Test whether the constraint is satisfied by a given string - - The string to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Modify the constraint to ignore case in matching. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EndsWithConstraint can test whether a string ends - with an expected substring. - - - - - Initializes a new instance of the class. - - The expected string - - - - Test whether the constraint is matched by the actual value. - This is a template method, which calls the IsMatch method - of the derived class. - - - - - - - EqualConstraint is able to compare an actual value with the - expected value provided in its constructor. Two objects are - considered equal if both are null, or if both have the same - value. NUnit has special semantics for some object types. - - - - - NUnitEqualityComparer used to test equality. - - - - - Initializes a new instance of the class. - - The expected value. - - - - Flag the constraint to use a tolerance when determining equality. - - Tolerance value to be used - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Gets the tolerance for this comparison. - - - The tolerance. - - - - - Gets a value indicating whether to compare case insensitive. - - - true if comparing case insensitive; otherwise, false. - - - - - Gets a value indicating whether or not to clip strings. - - - true if set to clip strings otherwise, false. - - - - - Gets the failure points. - - - The failure points. - - - - - Flag the constraint to ignore case and return self. - - - - - Flag the constraint to suppress string clipping - and return self. - - - - - Flag the constraint to compare arrays as collections - and return self. - - - - - Flags the constraint to include - property in comparison of two values. - - - Using this modifier does not allow to use the - constraint modifier. - - - - - Switches the .Within() modifier to interpret its tolerance as - a distance in representable _values (see remarks). - - Self. - - Ulp stands for "unit in the last place" and describes the minimum - amount a given value can change. For any integers, an ulp is 1 whole - digit. For floating point _values, the accuracy of which is better - for smaller numbers and worse for larger numbers, an ulp depends - on the size of the number. Using ulps for comparison of floating - point results instead of fixed tolerances is safer because it will - automatically compensate for the added inaccuracy of larger numbers. - - - - - Switches the .Within() modifier to interpret its tolerance as - a percentage that the actual _values is allowed to deviate from - the expected value. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in days. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in hours. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in minutes. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in seconds. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in milliseconds. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in clock ticks. - - Self - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EqualityAdapter class handles all equality comparisons - that use an , - or a . - - - - - Compares two objects, returning true if they are equal - - - - - Returns true if the two objects can be compared by this adapter. - The base adapter cannot handle IEnumerables except for strings. - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps a . - - - - - that wraps an . - - - - - Returns true if the two objects can be compared by this adapter. - Generic adapter requires objects of the specified type. - - - - - that wraps an . - - - - - ExactTypeConstraint is used to test that an object - is of the exact type provided in the constructor - - - - - Construct an ExactTypeConstraint for a given Type - - The expected Type. - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - FalseConstraint tests that the actual value is false - - - - - Initializes a new instance of the class. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - FileExistsConstraint is used to determine if a file exists - - - - - FileOrDirectoryExistsConstraint is used to determine if a file or directory exists - - - - - Initializes a new instance of the class that - will check files and directories. - - - - - Initializes a new instance of the class that - will only check files if ignoreDirectories is true. - - if set to true [ignore directories]. - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - If true, the constraint will only check if files exist, not directories - - - - - If true, the constraint will only check if directories exist, not files - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Initializes a new instance of the class. - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - Helper routines for working with floating point numbers - - - The floating point comparison code is based on this excellent article: - http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm - - - "ULP" means Unit in the Last Place and in the context of this library refers to - the distance between two adjacent floating point numbers. IEEE floating point - numbers can only represent a finite subset of natural numbers, with greater - accuracy for smaller numbers and lower accuracy for very large numbers. - - - If a comparison is allowed "2 ulps" of deviation, that means the _values are - allowed to deviate by up to 2 adjacent floating point _values, which might be - as low as 0.0000001 for small numbers or as high as 10.0 for large numbers. - - - - - Compares two floating point _values for equality - First floating point value to be compared - Second floating point value t be compared - - Maximum number of representable floating point _values that are allowed to - be between the left and the right floating point _values - - True if both numbers are equal or close to being equal - - - Floating point _values can only represent a finite subset of natural numbers. - For example, the _values 2.00000000 and 2.00000024 can be stored in a float, - but nothing inbetween them. - - - This comparison will count how many possible floating point _values are between - the left and the right number. If the number of possible _values between both - numbers is less than or equal to maxUlps, then the numbers are considered as - being equal. - - - Implementation partially follows the code outlined here: - http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ - - - - - Compares two double precision floating point _values for equality - First double precision floating point value to be compared - Second double precision floating point value t be compared - - Maximum number of representable double precision floating point _values that are - allowed to be between the left and the right double precision floating point _values - - True if both numbers are equal or close to being equal - - - Double precision floating point _values can only represent a limited series of - natural numbers. For example, the _values 2.0000000000000000 and 2.0000000000000004 - can be stored in a double, but nothing inbetween them. - - - This comparison will count how many possible double precision floating point - _values are between the left and the right number. If the number of possible - _values between both numbers is less than or equal to maxUlps, then the numbers - are considered as being equal. - - - Implementation partially follows the code outlined here: - http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ - - - - - - Reinterprets the memory contents of a floating point value as an integer value - - - Floating point value whose memory contents to reinterpret - - - The memory contents of the floating point value interpreted as an integer - - - - - Reinterprets the memory contents of a double precision floating point - value as an integer value - - - Double precision floating point value whose memory contents to reinterpret - - - The memory contents of the double precision floating point value - interpreted as an integer - - - - - Reinterprets the memory contents of an integer as a floating point value - - Integer value whose memory contents to reinterpret - - The memory contents of the integer value interpreted as a floating point value - - - - - Reinterprets the memory contents of an integer value as a double precision - floating point value - - Integer whose memory contents to reinterpret - - The memory contents of the integer interpreted as a double precision - floating point value - - - - Union of a floating point variable and an integer - - - The union's value as a floating point variable - - - The union's value as an integer - - - The union's value as an unsigned integer - - - Union of a double precision floating point variable and a long - - - The union's value as a double precision floating point variable - - - The union's value as a long - - - The union's value as an unsigned long - - - - Tests whether a value is greater than the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - Tests whether a value is greater than or equal to the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - InstanceOfTypeConstraint is used to test that an object - is of the same type provided or derived from it. - - - - - Construct an InstanceOfTypeConstraint for the type provided - - The expected Type - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - Tests whether a value is less than the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - Tests whether a value is less than or equal to the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - MessageWriter is the abstract base for classes that write - constraint descriptions and messages in some form. The - class has separate methods for writing various components - of a message, allowing implementations to tailor the - presentation as needed. - - - - - Construct a MessageWriter given a culture - - - - - Method to write single line message with optional args, usually - written to precede the general failure message. - - The message to be written - Any arguments used in formatting the message - - - - Method to write single line message with optional args, usually - written to precede the general failure message, at a givel - indentation level. - - The indentation level of the message - The message to be written - Any arguments used in formatting the message - - - - Display Expected and Actual lines for a constraint. This - is called by MessageWriter's default implementation of - WriteMessageTo and provides the generic two-line display. - - The failing constraint result - - - - Display Expected and Actual lines for given _values. This - method may be called by constraints that need more control over - the display of actual and expected _values than is provided - by the default implementation. - - The expected value - The actual value causing the failure - - - - Display Expected and Actual lines for given _values, including - a tolerance value on the Expected line. - - The expected value - The actual value causing the failure - The tolerance within which the test was made - - - - Display the expected and actual string _values on separate lines. - If the mismatch parameter is >=0, an additional line is displayed - line containing a caret that points to the mismatch point. - - The expected string value - The actual string value - The point at which the strings don't match or -1 - If true, case is ignored in locating the point where the strings differ - If true, the strings should be clipped to fit the line - - - - Writes the text for an actual value. - - The actual value. - - - - Writes the text for a generalized value. - - The value. - - - - Writes the text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Abstract method to get the max line length - - - - - Static methods used in creating messages - - - - - Static string used when strings are clipped - - - - - Formatting strings used for expected and actual _values - - - - - Formats text to represent a generalized value. - - The value - The formatted text - - - - Formats text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Returns the representation of a type as used in NUnitLite. - This is the same as Type.ToString() except for arrays, - which are displayed with their declared sizes. - - - - - - - Converts any control characters in a string - to their escaped representation. - - The string to be converted - The converted string - - - - Return the a string representation for a set of indices into an array - - Array of indices for which a string is needed - - - - Get an array of indices representing the point in a collection or - array corresponding to a single int index into the collection. - - The collection to which the indices apply - Index in the collection - Array of indices - - - - Clip a string to a given length, starting at a particular offset, returning the clipped - string with ellipses representing the removed parts - - The string to be clipped - The maximum permitted length of the result string - The point at which to start clipping - The clipped string - - - - Clip the expected and actual strings in a coordinated fashion, - so that they may be displayed together. - - - - - - - - - Shows the position two strings start to differ. Comparison - starts at the start index. - - The expected string - The actual string - The index in the strings at which comparison should start - Boolean indicating whether case should be ignored - -1 if no mismatch found, or the index where mismatch found - - - - NaNConstraint tests that the actual value is a double or float NaN - - - - - Test that the actual value is an NaN - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - NoItemConstraint applies another constraint to each - item in a collection, failing if any of them succeeds. - - - - - Construct a SomeItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - failing if any item fails. - - - - - - - NotConstraint negates the effect of some other constraint - - - - - Initializes a new instance of the class. - - The base constraint to be negated. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for if the base constraint fails, false if it succeeds - - - - NullConstraint tests that the actual value is null - - - - - Initializes a new instance of the class. - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - The Numerics class contains common operations on numeric _values. - - - - - Checks the type of the object, returning true if - the object is a numeric type. - - The object to check - true if the object is a numeric type - - - - Checks the type of the object, returning true if - the object is a floating point numeric type. - - The object to check - true if the object is a floating point numeric type - - - - Checks the type of the object, returning true if - the object is a fixed point numeric type. - - The object to check - true if the object is a fixed point numeric type - - - - Test two numeric _values for equality, performing the usual numeric - conversions and using a provided or default tolerance. If the tolerance - provided is Empty, this method may set it to a default tolerance. - - The expected value - The actual value - A reference to the tolerance in effect - True if the _values are equal - - - - Compare two numeric _values, performing the usual numeric conversions. - - The expected value - The actual value - The relationship of the _values to each other - - - - NUnitComparer encapsulates NUnit's default behavior - in comparing two objects. - - - - - Compares two objects - - - - - - - - Returns the default NUnitComparer. - - - - - NUnitEqualityComparer encapsulates NUnit's handling of - equality tests between objects. - - - - - If true, all string comparisons will ignore case - - - - - If true, arrays will be treated as collections, allowing - those of different dimensions to be compared - - - - - Comparison objects used in comparisons for some constraints. - - - - - List of points at which a failure occurred. - - - - - Compares two objects for equality within a tolerance. - - - - - Helper method to compare two arrays - - - - - Method to compare two DirectoryInfo objects - - first directory to compare - second directory to compare - true if equivalent, false if not - - - - Returns the default NUnitEqualityComparer - - - - - Gets and sets a flag indicating whether case should - be ignored in determining equality. - - - - - Gets and sets a flag indicating that arrays should be - compared as collections, without regard to their shape. - - - - - Gets the list of external comparers to be used to - test for equality. They are applied to members of - collections, in place of NUnit's own logic. - - - - - Gets the list of failure points for the last Match performed. - The list consists of objects to be interpreted by the caller. - This generally means that the caller may only make use of - objects it has placed on the list at a particular depthy. - - - - - Flags the comparer to include - property in comparison of two values. - - - Using this modifier does not allow to use the - modifier. - - - - - FailurePoint class represents one point of failure - in an equality test. - - - - - The location of the failure - - - - - The expected value - - - - - The actual value - - - - - Indicates whether the expected value is valid - - - - - Indicates whether the actual value is valid - - - - - Represents a constraint that succeeds if all the - members of a collection match a base constraint. - - - - - Abstract base for operators that indicate how to - apply a constraint to items in a collection. - - - - - PrefixOperator takes a single constraint and modifies - it's action in some way. - - - - - The ConstraintOperator class is used internally by a - ConstraintBuilder to represent an operator that - modifies or combines constraints. - - Constraint operators use left and right precedence - _values to determine whether the top operator on the - stack should be reduced before pushing a new operator. - - - - - The precedence value used when the operator - is about to be pushed to the stack. - - - - - The precedence value used when the operator - is on the top of the stack. - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - The syntax element preceding this operator - - - - - The syntax element following this operator - - - - - The precedence value used when the operator - is about to be pushed to the stack. - - - - - The precedence value used when the operator - is on the top of the stack. - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Returns the constraint created by applying this - prefix to another constraint. - - - - - - - Constructs a CollectionOperator - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - they all succeed. - - - - - Operator that requires both it's arguments to succeed - - - - - Abstract base class for all binary operators - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Abstract method that produces a constraint by applying - the operator to its left and right constraint arguments. - - - - - Gets the left precedence of the operator - - - - - Gets the right precedence of the operator - - - - - Construct an AndOperator - - - - - Apply the operator to produce an AndConstraint - - - - - Operator that tests for the presence of a particular attribute - on a type and optionally applies further tests to the attribute. - - - - - Abstract base class for operators that are able to reduce to a - constraint whether or not another syntactic element follows. - - - - - Construct an AttributeOperator for a particular Type - - The Type of attribute tested - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - Represents a constraint that succeeds if none of the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - none of them succeed. - - - - - Negates the test of the constraint it wraps. - - - - - Constructs a new NotOperator - - - - - Returns a NotConstraint applied to its argument. - - - - - Operator that requires at least one of it's arguments to succeed - - - - - Construct an OrOperator - - - - - Apply the operator to produce an OrConstraint - - - - - Operator used to test for the presence of a named Property - on an object and optionally apply further tests to the - value of that property. - - - - - Constructs a PropOperator for a particular named property - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Gets the name of the property to which the operator applies - - - - - Represents a constraint that succeeds if any of the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - any of them succeed. - - - - - Operator that tests that an exception is thrown and - optionally applies further tests to the exception. - - - - - Construct a ThrowsOperator - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - Represents a constraint that simply wraps the - constraint provided as an argument, without any - further functionality, but which modifies the - order of evaluation because of its precedence. - - - - - Constructor for the WithOperator - - - - - Returns a constraint that wraps its argument - - - - - OrConstraint succeeds if either member succeeds - - - - - Create an OrConstraint from two other constraints - - The first constraint - The second constraint - - - - Apply the member constraints to an actual value, succeeding - succeeding as soon as one of them succeeds. - - The actual value - True if either constraint succeeded - - - - Gets text describing a constraint - - - - - PathConstraint serves as the abstract base of constraints - that operate on paths and provides several helper methods. - - - - - Construct a PathConstraint for a give expected path - - The expected path - - - - Returns the string representation of this constraint - - - - - Canonicalize the provided path - - - The path in standardized form - - - - Test whether one path in canonical form is a subpath of another path - - The first path - supposed to be the parent path - The second path - supposed to be the child path - - - - - Modifies the current instance to be case-sensitive - and returns it. - - - - - Predicate constraint wraps a Predicate in a constraint, - returning success if the predicate is true. - - - - - Construct a PredicateConstraint from a predicate - - - - - Determines whether the predicate succeeds when applied - to the actual value. - - - - - Gets text describing a constraint - - - - - PropertyConstraint extracts a named property and uses - its value as the actual value for a chained constraint. - - - - - Initializes a new instance of the class. - - The name. - The constraint to apply to the property. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - - PropertyExistsConstraint tests that a named property - exists on the object provided through Match. - - Originally, PropertyConstraint provided this feature - in addition to making optional tests on the value - of the property. The two constraints are now separate. - - - - - Initializes a new instance of the class. - - The name of the property. - - - - Test whether the property exists for a given object - - The object to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - RangeConstraint tests whether two _values are within a - specified range. - - - - - Initializes a new instance of the class. - - from must be less than or equal to true - Inclusive beginning of the range. Must be less than or equal to to. - Inclusive end of the range. Must be greater than or equal to from. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use a and returns self. - - - - - Gets text describing a constraint - - - - - RegexConstraint can test whether a string matches - the pattern provided. - - - - - Initializes a new instance of the class. - - The pattern. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - ResolvableConstraintExpression is used to represent a compound - constraint being constructed at a point where the last operator - may either terminate the expression or may have additional - qualifying constraints added to it. - - It is used, for example, for a Property element or for - an Exception element, either of which may be optionally - followed by constraints that apply to the property or - exception. - - - - - Create a new instance of ResolvableConstraintExpression - - - - - Create a new instance of ResolvableConstraintExpression, - passing in a pre-populated ConstraintBuilder. - - - - - Resolve the current expression to a Constraint - - - - - Appends an And Operator to the expression - - - - - Appends an Or operator to the expression. - - - - - ReusableConstraint wraps a constraint expression after - resolving it so that it can be reused consistently. - - - - - Construct a ReusableConstraint from a constraint expression - - The expression to be resolved and reused - - - - Converts a constraint to a ReusableConstraint - - The constraint to be converted - A ReusableConstraint - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Return the top-level constraint for this expression - - - - - - SameAsConstraint tests whether an object is identical to - the object passed to its constructor - - - - - Initializes a new instance of the class. - - The expected object. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Summary description for SamePathConstraint. - - - - - Initializes a new instance of the class. - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - SamePathOrUnderConstraint tests that one path is under another - - - - - Initializes a new instance of the class. - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - SomeItemsConstraint applies another constraint to each - item in a collection, succeeding if any of them succeeds. - - - - - Construct a SomeItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - succeeding if any item succeeds. - - - - - - - The EqualConstraintResult class is tailored for formatting - and displaying the result of an EqualConstraint. - - - - - Construct an EqualConstraintResult - - - - - Write a failure message. Overridden to provide custom - failure messages for EqualConstraint. - - The MessageWriter to write to - - - - Display the failure information for two collections that did not match. - - The MessageWriter on which to display - The expected collection. - The actual collection - The depth of this failure in a set of nested collections - - - - Displays a single line showing the types and sizes of the expected - and actual collections or arrays. If both are identical, the value is - only shown once. - - The MessageWriter on which to display - The expected collection or array - The actual collection or array - The indentation level for the message line - - - - Displays a single line showing the point in the expected and actual - arrays at which the comparison failed. If the arrays have different - structures or dimensions, both _values are shown. - - The MessageWriter on which to display - The expected array - The actual array - Index of the failure point in the underlying collections - The indentation level for the message line - - - - Display the failure information for two IEnumerables that did not match. - - The MessageWriter on which to display - The expected enumeration. - The actual enumeration - The depth of this failure in a set of nested collections - - - - StartsWithConstraint can test whether a string starts - with an expected substring. - - - - - Initializes a new instance of the class. - - The expected string - - - - Test whether the constraint is matched by the actual value. - This is a template method, which calls the IsMatch method - of the derived class. - - - - - - - SubPathConstraint tests that the actual path is under the expected path - - - - - Initializes a new instance of the class. - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - SubstringConstraint can test whether a string contains - the expected substring. - - - - - Initializes a new instance of the class. - - The expected. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - ThrowsConstraint is used to test the exception thrown by - a delegate by applying a constraint to it. - - - - - Initializes a new instance of the class, - using a constraint to be applied to the exception. - - A constraint to apply to the caught exception. - - - - Executes the code of the delegate and captures any exception. - If a non-null base constraint was provided, it applies that - constraint to the exception. - - A delegate representing the code to be tested - True if an exception is thrown and the constraint succeeds, otherwise false - - - - Converts an ActualValueDelegate to a TestDelegate - before calling the primary overload. - - - - - - - Get the actual exception thrown - used by Assert.Throws. - - - - - Gets text describing a constraint - - - - - Write the actual value for a failing constraint test to a - MessageWriter. This override only handles the special message - used when an exception is expected but none is thrown. - - The writer on which the actual value is displayed - - - - ThrowsExceptionConstraint tests that an exception has - been thrown, without any further tests. - - - - - Executes the code and returns success if an exception is thrown. - - A delegate representing the code to be tested - True if an exception is thrown, otherwise false - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - ThrowsNothingConstraint tests that a delegate does not - throw an exception. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True if no exception is thrown, otherwise false - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Gets text describing a constraint - - - - - The Tolerance class generalizes the notion of a tolerance - within which an equality test succeeds. Normally, it is - used with numeric types, but it can be used with any - type that supports taking a difference between two - objects and comparing that difference to a value. - - - - - Constructs a linear tolerance of a specified amount - - - - - Constructs a tolerance given an amount and - - - - - Tests that the current Tolerance is linear with a - numeric value, throwing an exception if it is not. - - - - - Returns a default Tolerance object, equivalent to - specifying an exact match unless - is set, in which case, the - will be used. - - - - - Returns an empty Tolerance object, equivalent to - specifying an exact match even if - is set. - - - - - Gets the for the current Tolerance - - - - - Gets the value of the current Tolerance instance. - - - - - Returns a new tolerance, using the current amount as a percentage. - - - - - Returns a new tolerance, using the current amount in Ulps - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of days. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of hours. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of minutes. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of seconds. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of milliseconds. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of clock ticks. - - - - - Returns true if the current tolerance has not been set or is using the . - - - - - Modes in which the tolerance value for a comparison can be interpreted. - - - - - The tolerance was created with a value, without specifying - how the value would be used. This is used to prevent setting - the mode more than once and is generally changed to Linear - upon execution of the test. - - - - - The tolerance is used as a numeric range within which - two compared _values are considered to be equal. - - - - - Interprets the tolerance as the percentage by which - the two compared _values my deviate from each other. - - - - - Compares two _values based in their distance in - representable numbers. - - - - - TrueConstraint tests that the actual value is true - - - - - Initializes a new instance of the class. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - UniqueItemsConstraint tests whether all the items in a - collection are unique. - - - - - Check that all items are unique. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - XmlSerializableConstraint tests whether - an object is serializable in xml format. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Returns the string representation of this constraint - - - - - Gets text describing a constraint - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new DictionaryContainsKeyConstraint checking for the - presence of a particular key in the dictionary. - - - - - Returns a new DictionaryContainsValueConstraint checking for the - presence of a particular value in the dictionary. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Asserts on Directories - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Verifies that two directories are equal. Two directories are considered - equal if both are null, or if both point to the same directory. - If they are not equal an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - The message to display if the directories are not equal - Arguments to be used in formatting the message - - - - Verifies that two directories are equal. Two directories are considered - equal if both are null, or if both point to the same directory. - If they are not equal an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - - - - Asserts that two directories are not equal. If they are equal - an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that two directories are not equal. If they are equal - an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - - - - Asserts that the directory exists. If it does not exist - an is thrown. - - A directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory exists. If it does not exist - an is thrown. - - A directory containing the actual value - - - - Asserts that the directory exists. If it does not exist - an is thrown. - - The path to a directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory exists. If it does not exist - an is thrown. - - The path to a directory containing the actual value - - - - Asserts that the directory does not exist. If it does exist - an is thrown. - - A directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory does not exist. If it does exist - an is thrown. - - A directory containing the actual value - - - - Asserts that the directory does not exist. If it does exist - an is thrown. - - The path to a directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory does not exist. If it does exist - an is thrown. - - The path to a directory containing the actual value - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a constraint that succeeds if the value - is a file or directory and it exists. - - - - - Thrown when an assertion failed. - - - - - Abstract base for Exceptions that terminate a test and provide a ResultState. - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - Thrown when an assertion failed. - - - - - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - Thrown when a test executes inconclusively. - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - Thrown when an assertion failed. - - - - - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Gets the ResultState provided by this exception - - - - - Asserts on Files - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Verifies that two Streams are equal. Two Streams are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The expected Stream - The actual Stream - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Verifies that two Streams are equal. Two Streams are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The expected Stream - The actual Stream - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A file containing the value that is expected - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A file containing the value that is expected - A file containing the actual value - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - - - - Asserts that two Streams are not equal. If they are equal - an is thrown. - - The expected Stream - The actual Stream - The message to be displayed when the two Stream are the same. - Arguments to be used in formatting the message - - - - Asserts that two Streams are not equal. If they are equal - an is thrown. - - The expected Stream - The actual Stream - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - A file containing the value that is expected - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - A file containing the value that is expected - A file containing the actual value - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - - - - Asserts that the file exists. If it does not exist - an is thrown. - - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that the file exists. If it does not exist - an is thrown. - - A file containing the actual value - - - - Asserts that the file exists. If it does not exist - an is thrown. - - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that the file exists. If it does not exist - an is thrown. - - The path to a file containing the actual value - - - - Asserts that the file does not exist. If it does exist - an is thrown. - - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that the file does not exist. If it does exist - an is thrown. - - A file containing the actual value - - - - Asserts that the file does not exist. If it does exist - an is thrown. - - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that the file does not exist. If it does exist - an is thrown. - - The path to a file containing the actual value - - - - GlobalSettings is a place for setting default _values used - by the framework in performing asserts. - - - - - Default tolerance for floating point equality - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - Objects implementing this interface are used to wrap - the TestMethodCommand itself. They apply after SetUp - has been run and before TearDown. - - - - - Any ITest that implements this interface is at a level that the implementing - class should be disposed at the end of the test run - - - - - The IMethodInfo class is used to encapsulate information - about a method in a platform-independent manner. - - - - - The IReflectionInfo interface is implemented by NUnit wrapper objects that perform reflection. - - - - - Returns an array of custom attributes of the specified type applied to this object - - - - - Returns a value indicating whether an attribute of the specified type is defined on this object. - - - - - Gets the parameters of the method. - - - - - - Returns the Type arguments of a generic method or the Type parameters of a generic method definition. - - - - - Replaces the type parameters of the method with the array of types provided and returns a new IMethodInfo. - - The type arguments to be used - A new IMethodInfo with the type arguments replaced - - - - Invokes the method, converting any TargetInvocationException to an NUnitException. - - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - Gets the Type from which this method was reflected. - - - - - Gets the MethodInfo for this method. - - - - - Gets the name of the method. - - - - - Gets a value indicating whether the method is abstract. - - - - - Gets a value indicating whether the method is public. - - - - - Gets a value indicating whether the method contains unassigned generic type parameters. - - - - - Gets a value indicating whether the method is a generic method. - - - - - Gets a value indicating whether the MethodInfo represents the definition of a generic method. - - - - - Gets the return Type of the method. - - - - - The IDataPointProvider interface is used by extensions - that provide data for a single test parameter. - - - - - Determine whether any data is available for a parameter. - - An IParameterInfo representing one - argument to a parameterized test - True if any data is available, otherwise false. - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - An IParameterInfo representing one - argument to a parameterized test - An IEnumerable providing the required data - - - - The IParameterInfo interface is an abstraction of a .NET parameter. - - - - - Gets a value indicating whether the parameter is optional - - - - - Gets an IMethodInfo representing the method for which this is a parameter - - - - - Gets the underlying .NET ParameterInfo - - - - - Gets the Type of the parameter - - - - - A PropertyBag represents a collection of name/value pairs - that allows duplicate entries with the same key. Methods - are provided for adding a new pair as well as for setting - a key to a single value. All keys are strings but _values - may be of any type. Null _values are not permitted, since - a null entry represents the absence of the key. - - The entries in a PropertyBag are of two kinds: those that - take a single value and those that take multiple _values. - However, the PropertyBag has no knowledge of which entries - fall into each category and the distinction is entirely - up to the code using the PropertyBag. - - When working with multi-valued properties, client code - should use the Add method to add name/value pairs and - indexing to retrieve a list of all _values for a given - key. For example: - - bag.Add("Tag", "one"); - bag.Add("Tag", "two"); - Assert.That(bag["Tag"], - Is.EqualTo(new string[] { "one", "two" })); - - When working with single-valued propeties, client code - should use the Set method to set the value and Get to - retrieve the value. The GetSetting methods may also be - used to retrieve the value in a type-safe manner while - also providing default. For example: - - bag.Set("Priority", "low"); - bag.Set("Priority", "high"); // replaces value - Assert.That(bag.Get("Priority"), - Is.EqualTo("high")); - Assert.That(bag.GetSetting("Priority", "low"), - Is.EqualTo("high")); - - - - - An object implementing IXmlNodeBuilder is able to build - an XML representation of itself and any children. - - - - - Returns a TNode representing the current object. - - If true, children are included where applicable - A TNode representing the result - - - - Returns a TNode representing the current object after - adding it as a child of the supplied parent node. - - The parent node. - If true, children are included, where applicable - - - - - Adds a key/value pair to the property bag - - The key - The value - - - - Sets the value for a key, removing any other - _values that are already in the property set. - - - - - - - Gets a single value for a key, using the first - one if multiple _values are present and returning - null if the value is not found. - - - - - Gets a flag indicating whether the specified key has - any entries in the property set. - - The key to be checked - True if their are _values present, otherwise false - - - - Gets or sets the list of _values for a particular key - - The key for which the _values are to be retrieved or set - - - - Gets a collection containing all the keys in the property set - - - - - The ISuiteBuilder interface is exposed by a class that knows how to - build a suite from one or more Types. - - - - - Examine the type and determine if it is suitable for - this builder to use in building a TestSuite. - - Note that returning false will cause the type to be ignored - in loading the tests. If it is desired to load the suite - but label it as non-runnable, ignored, etc., then this - method must return true. - - The type of the fixture to be used - True if the type can be used to build a TestSuite - - - - Build a TestSuite from type provided. - - The type of the fixture to be used - A TestSuite - - - - Common interface supported by all representations - of a test. Only includes informational fields. - The Run method is specifically excluded to allow - for data-only representations of a test. - - - - - Gets the id of the test - - - - - Gets the name of the test - - - - - Gets the fully qualified name of the test - - - - - Gets the name of the class containing this test. Returns - null if the test is not associated with a class. - - - - - Gets the name of the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the Type of the test fixture, if applicable, or - null if no fixture type is associated with this test. - - - - - Gets an IMethod for the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the RunState of the test, indicating whether it can be run. - - - - - Count of the test cases ( 1 if this is a test case ) - - - - - Gets the properties of the test - - - - - Gets the parent test, if any. - - The parent test or null if none exists. - - - - Returns true if this is a test suite - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets a fixture object for running this test. - - - - - The ITestCaseBuilder interface is exposed by a class that knows how to - build a test case from certain methods. - - - This interface is not the same as the ITestCaseBuilder interface in NUnit 2.x. - We have reused the name because the two products don't interoperate at all. - - - - - Examine the method and determine if it is suitable for - this builder to use in building a TestCase to be - included in the suite being populated. - - Note that returning false will cause the method to be ignored - in loading the tests. If it is desired to load the method - but label it as non-runnable, ignored, etc., then this - method must return true. - - The test method to examine - The suite being populated - True is the builder can use this method - - - - Build a TestCase from the provided MethodInfo for - inclusion in the suite being constructed. - - The method to be used as a test case - The test suite being populated, or null - A TestCase or null - - - - Interface to be implemented by filters applied to tests. - The filter applies when running the test, after it has been - loaded, since this is the only time an ITest exists. - - - - - Determine if a particular test passes the filter criteria. Pass - may examine the parents and/or descendants of a test, depending - on the semantics of the particular filter - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - The ITestListener interface is used internally to receive - notifications of significant events while a test is being - run. The events are propagated to clients by means of an - AsyncCallback. NUnit extensions may also monitor these events. - - - - - Called when a test has just started - - The test that is starting - - - - Called when a test has finished - - The result of the test - - - - The ITestResult interface represents the result of a test. - - - - - Gets the ResultState of the test result, which - indicates the success or failure of the test. - - - - - Gets the name of the test result - - - - - Gets the full name of the test result - - - - - Gets the elapsed time for running the test in seconds - - - - - Gets or sets the time the test started running. - - - - - Gets or sets the time the test finished running. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. Not available in - the Compact Framework 1.0. - - - - - Gets the number of asserts executed - when running the test and all its children. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Indicates whether this result has any child results. - Accessing HasChildren should not force creation of the - Children collection in classes implementing this interface. - - - - - Gets the the collection of child results. - - - - - Gets the Test to which this result applies. - - - - - Gets any text output written to this result. - - - - - The ITypeInfo interface is an abstraction of a .NET Type - - - - - Returns true if the Type wrapped is equal to the argument - - - - - Get the display name for this typeInfo. - - - - - Get the display name for an oject of this type, constructed with specific arguments - - - - - Returns a Type representing a generic type definition from which this Type can be constructed. - - - - - Returns a new ITypeInfo representing an instance of this generic Type using the supplied Type arguments - - - - - Returns a value indicating whether this type has a method with a specified public attribute - - - - - Returns an array of IMethodInfos for methods of this Type - that match the specified flags. - - - - - Returns a value indicating whether this Type has a public constructor taking the specified argument Types. - - - - - Construct an object of this Type, using the specified arguments. - - - - - Gets the underlying Type on which this ITypeInfo is based - - - - - Gets the base type of this type as an ITypeInfo - - - - - Gets the Name of the Type - - - - - Gets the FullName of the Type - - - - - Gets the assembly in which the type is declared - - - - - Gets the Namespace of the Type - - - - - Gets a value indicating whether the type is abstract. - - - - - Gets a value indicating whether the Type is a generic Type - - - - - Gets a value indicating whether the Type has generic parameters that have not been replaced by specific Types. - - - - - Gets a value indicating whether the Type is a generic Type definition - - - - - Gets a value indicating whether the type is sealed. - - - - - Gets a value indicating whether this type is a static class. - - - - - The ResultState class represents the outcome of running a test. - It contains two pieces of information. The Status of the test - is an enum indicating whether the test passed, failed, was - skipped or was inconclusive. The Label provides a more - detailed breakdown for use by client runners. - - - - - Initializes a new instance of the class. - - The TestStatus. - - - - Initializes a new instance of the class. - - The TestStatus. - The label. - - - - Initializes a new instance of the class. - - The TestStatus. - The stage at which the result was produced - - - - Initializes a new instance of the class. - - The TestStatus. - The label. - The stage at which the result was produced - - - - The result is inconclusive - - - - - The test has been skipped. - - - - - The test has been ignored. - - - - - The test was skipped because it is explicit - - - - - The test succeeded - - - - - The test failed - - - - - The test encountered an unexpected exception - - - - - The test was cancelled by the user - - - - - The test was not runnable. - - - - - A suite failed because one or more child tests failed or had errors - - - - - A suite failed in its OneTimeSetUp - - - - - A suite had an unexpected exception in its OneTimeSetUp - - - - - A suite had an unexpected exception in its OneTimeDown - - - - - Get a new ResultState, which is the same as the current - one but with the FailureSite set to the specified value. - - The FailureSite to use - A new ResultState - - - - Determines whether the specified , is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Returns a hash code for this instance. - - - A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Gets the TestStatus for the test. - - The status. - - - - Gets the label under which this test result is - categorized, if any. - - - - - Gets the stage of test execution in which - the failure or other result took place. - - - - - The FailureSite enum indicates the stage of a test - in which an error or failure occurred. - - - - - Failure in the test itself - - - - - Failure in the SetUp method - - - - - Failure in the TearDown method - - - - - Failure of a parent test - - - - - Failure of a child test - - - - - The RunState enum indicates whether a test can be executed. - - - - - The test is not runnable. - - - - - The test is runnable. - - - - - The test can only be run explicitly - - - - - The test has been skipped. This value may - appear on a Test when certain attributes - are used to skip the test. - - - - - The test has been ignored. May appear on - a Test, when the IgnoreAttribute is used. - - - - - The TestStatus enum indicates the result of running a test - - - - - The test was inconclusive - - - - - The test has skipped - - - - - The test succeeded - - - - - The test failed - - - - - TNode represents a single node in the XML representation - of a Test or TestResult. It replaces System.Xml.XmlNode and - System.Xml.Linq.XElement, providing a minimal set of methods - for operating on the XML in a platform-independent manner. - - - - - Constructs a new instance of TNode - - The name of the node - - - - Constructs a new instance of TNode with a value - - The name of the node - The text content of the node - - - - Constructs a new instance of TNode with a value - - The name of the node - The text content of the node - Flag indicating whether to use CDATA when writing the text - - - - Create a TNode from it's XML text representation - - The XML text to be parsed - A TNode - - - - Adds a new element as a child of the current node and returns it. - - The element name. - The newly created child element - - - - Adds a new element with a value as a child of the current node and returns it. - - The element name - The text content of the new element - The newly created child element - - - - Adds a new element with a value as a child of the current node and returns it. - The value will be output using a CDATA section. - - The element name - The text content of the new element - The newly created child element - - - - Adds an attribute with a specified name and value to the XmlNode. - - The name of the attribute. - The value of the attribute. - - - - Finds a single descendant of this node matching an xpath - specification. The format of the specification is - limited to what is needed by NUnit and its tests. - - - - - - - Finds all descendants of this node matching an xpath - specification. The format of the specification is - limited to what is needed by NUnit and its tests. - - - - - Writes the XML representation of the node to an XmlWriter - - - - - - Gets the name of the node - - - - - Gets the value of the node - - - - - Gets a flag indicating whether the value should be output using CDATA. - - - - - Gets the dictionary of attributes - - - - - Gets a list of child nodes - - - - - Gets the first ChildNode - - - - - Gets the XML representation of this node. - - - - - Class used to represent a list of XmlResults - - - - - Class used to represent the attributes of a node - - - - - Gets or sets the value associated with the specified key. - Overridden to return null if attribute is not found. - - The key. - Value of the attribute or null - - - - CombinatorialStrategy creates test cases by using all possible - combinations of the parameter data. - - - - - CombiningStrategy is the abstract base for classes that - know how to combine values provided for individual test - parameters to create a set of test cases. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - Provides data from fields marked with the DatapointAttribute or the - DatapointsAttribute. - - - - - Determine whether any data is available for a parameter. - - A ParameterInfo representing one - argument to a parameterized test - - True if any data is available, otherwise false. - - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - A ParameterInfo representing one - argument to a parameterized test - - An IEnumerable providing the required data - - - - - Built-in SuiteBuilder for all types of test classes. - - - - - Checks to see if the provided Type is a fixture. - To be considered a fixture, it must be a non-abstract - class with one or more attributes implementing the - IFixtureBuilder interface or one or more methods - marked as tests. - - The fixture type to check - True if the fixture can be built, false if not - - - - Build a TestSuite from TypeInfo provided. - - The fixture type to build - A TestSuite built from that type - - - - We look for attributes implementing IFixtureBuilder at one level - of inheritance at a time. Attributes on base classes are not used - unless there are no fixture builder attributes at all on the derived - class. This is by design. - - The type being examined for attributes - A list of the attributes found. - - - - Class to build ether a parameterized or a normal NUnitTestMethod. - There are four cases that the builder must deal with: - 1. The method needs no params and none are provided - 2. The method needs params and they are provided - 3. The method needs no params but they are provided in error - 4. The method needs params but they are not provided - This could have been done using two different builders, but it - turned out to be simpler to have just one. The BuildFrom method - takes a different branch depending on whether any parameters are - provided, but all four cases are dealt with in lower-level methods - - - - - Determines if the method can be used to build an NUnit test - test method of some kind. The method must normally be marked - with an identifying attribute for this to be true. - - Note that this method does not check that the signature - of the method for validity. If we did that here, any - test methods with invalid signatures would be passed - over in silence in the test run. Since we want such - methods to be reported, the check for validity is made - in BuildFrom rather than here. - - An IMethodInfo for the method being used as a test method - True if the builder can create a test case from this method - - - - Build a Test from the provided MethodInfo. Depending on - whether the method takes arguments and on the availability - of test case data, this method may return a single test - or a group of tests contained in a ParameterizedMethodSuite. - - The method for which a test is to be built - A Test representing one or more method invocations - - - - Determines if the method can be used to build an NUnit test - test method of some kind. The method must normally be marked - with an identifying attribute for this to be true. - - Note that this method does not check that the signature - of the method for validity. If we did that here, any - test methods with invalid signatures would be passed - over in silence in the test run. Since we want such - methods to be reported, the check for validity is made - in BuildFrom rather than here. - - An IMethodInfo for the method being used as a test method - The test suite being built, to which the new test would be added - True if the builder can create a test case from this method - - - - Build a Test from the provided MethodInfo. Depending on - whether the method takes arguments and on the availability - of test case data, this method may return a single test - or a group of tests contained in a ParameterizedMethodSuite. - - The method for which a test is to be built - The test fixture being populated, or null - A Test representing one or more method invocations - - - - Builds a ParameterizedMethodSuite containing individual test cases. - - The method for which a test is to be built. - The list of test cases to include. - A ParameterizedMethodSuite populated with test cases - - - - Build a simple, non-parameterized TestMethod for this method. - - The MethodInfo for which a test is to be built - The test suite for which the method is being built - A TestMethod. - - - - Class that can build a tree of automatic namespace - suites from a group of fixtures. - - - - - NamespaceDictionary of all test suites we have created to represent - namespaces. Used to locate namespace parent suites for fixtures. - - - - - The root of the test suite being created by this builder. - - - - - Initializes a new instance of the class. - - The root suite. - - - - Adds the specified fixtures to the tree. - - The fixtures to be added. - - - - Adds the specified fixture to the tree. - - The fixture to be added. - - - - Gets the root entry in the tree created by the NamespaceTreeBuilder. - - The root suite. - - - - NUnitTestCaseBuilder is a utility class used by attributes - that build test cases. - - - - - Constructs an - - - - - Builds a single NUnitTestMethod, either as a child of the fixture - or as one of a set of test cases under a ParameterizedTestMethodSuite. - - The MethodInfo from which to construct the TestMethod - The suite or fixture to which the new test will be added - The ParameterSet to be used, or null - - - - - Helper method that checks the signature of a TestMethod and - any supplied parameters to determine if the test is valid. - - Currently, NUnitTestMethods are required to be public, - non-abstract methods, either static or instance, - returning void. They may take arguments but the _values must - be provided or the TestMethod is not considered runnable. - - Methods not meeting these criteria will be marked as - non-runnable and the method will return false in that case. - - The TestMethod to be checked. If it - is found to be non-runnable, it will be modified. - Parameters to be used for this test, or null - True if the method signature is valid, false if not - - The return value is no longer used internally, but is retained - for testing purposes. - - - - - NUnitTestFixtureBuilder is able to build a fixture given - a class marked with a TestFixtureAttribute or an unmarked - class containing test methods. In the first case, it is - called by the attribute and in the second directly by - NUnitSuiteBuilder. - - - - - Build a TestFixture from type provided. A non-null TestSuite - must always be returned, since the method is generally called - because the user has marked the target class as a fixture. - If something prevents the fixture from being used, it should - be returned nonetheless, labelled as non-runnable. - - An ITypeInfo for the fixture to be used. - A TestSuite object or one derived from TestSuite. - - - - Overload of BuildFrom called by tests that have arguments. - Builds a fixture using the provided type and information - in the ITestFixtureData object. - - The TypeInfo for which to construct a fixture. - An object implementing ITestFixtureData or null. - - - - - Method to add test cases to the newly constructed fixture. - - The fixture to which cases should be added - - - - Method to create a test case from a MethodInfo and add - it to the fixture being built. It first checks to see if - any global TestCaseBuilder addin wants to build the - test case. If not, it uses the internal builder - collection maintained by this fixture builder. - - The default implementation has no test case builders. - Derived classes should add builders to the collection - in their constructor. - - The method for which a test is to be created - The test suite being built. - A newly constructed Test - - - - PairwiseStrategy creates test cases by combining the parameter - data so that all possible pairs of data items are used. - - - - The number of test cases that cover all possible pairs of test function - parameters values is significantly less than the number of test cases - that cover all possible combination of test function parameters values. - And because different studies show that most of software failures are - caused by combination of no more than two parameters, pairwise testing - can be an effective ways to test the system when it's impossible to test - all combinations of parameters. - - - The PairwiseStrategy code is based on "jenny" tool by Bob Jenkins: - http://burtleburtle.net/bob/math/jenny.html - - - - - - Gets the test cases generated by this strategy instance. - - A set of test cases. - - - - FleaRand is a pseudo-random number generator developed by Bob Jenkins: - http://burtleburtle.net/bob/rand/talksmall.html#flea - - - - - Initializes a new instance of the FleaRand class. - - The seed. - - - - FeatureInfo represents coverage of a single value of test function - parameter, represented as a pair of indices, Dimension and Feature. In - terms of unit testing, Dimension is the index of the test parameter and - Feature is the index of the supplied value in that parameter's list of - sources. - - - - - Initializes a new instance of FeatureInfo class. - - Index of a dimension. - Index of a feature. - - - - A FeatureTuple represents a combination of features, one per test - parameter, which should be covered by a test case. In the - PairwiseStrategy, we are only trying to cover pairs of features, so the - tuples actually may contain only single feature or pair of features, but - the algorithm itself works with triplets, quadruples and so on. - - - - - Initializes a new instance of FeatureTuple class for a single feature. - - Single feature. - - - - Initializes a new instance of FeatureTuple class for a pair of features. - - First feature. - Second feature. - - - - TestCase represents a single test case covering a list of features. - - - - - Initializes a new instance of TestCaseInfo class. - - A number of features in the test case. - - - - PairwiseTestCaseGenerator class implements an algorithm which generates - a set of test cases which covers all pairs of possible values of test - function. - - - - The algorithm starts with creating a set of all feature tuples which we - will try to cover (see method). This set - includes every single feature and all possible pairs of features. We - store feature tuples in the 3-D collection (where axes are "dimension", - "feature", and "all combinations which includes this feature"), and for - every two feature (e.g. "A" and "B") we generate both ("A", "B") and - ("B", "A") pairs. This data structure extremely reduces the amount of - time needed to calculate coverage for a single test case (this - calculation is the most time-consuming part of the algorithm). - - - Then the algorithm picks one tuple from the uncovered tuple, creates a - test case that covers this tuple, and then removes this tuple and all - other tuples covered by this test case from the collection of uncovered - tuples. - - - Picking a tuple to cover - - - There are no any special rules defined for picking tuples to cover. We - just pick them one by one, in the order they were generated. - - - Test generation - - - Test generation starts from creating a completely random test case which - covers, nevertheless, previously selected tuple. Then the algorithm - tries to maximize number of tuples which this test covers. - - - Test generation and maximization process repeats seven times for every - selected tuple and then the algorithm picks the best test case ("seven" - is a magic number which provides good results in acceptable time). - - Maximizing test coverage - - To maximize tests coverage, the algorithm walks thru the list of mutable - dimensions (mutable dimension is a dimension that are not included in - the previously selected tuple). Then for every dimension, the algorithm - walks thru the list of features and checks if this feature provides - better coverage than randomly selected feature, and if yes keeps this - feature. - - - This process repeats while it shows progress. If the last iteration - doesn't improve coverage, the process ends. - - - In addition, for better results, before start every iteration, the - algorithm "scrambles" dimensions - so for every iteration dimension - probes in a different order. - - - - - - Creates a set of test cases for specified dimensions. - - - An array which contains information about dimensions. Each element of - this array represents a number of features in the specific dimension. - - - A set of test cases. - - - - - ParameterDataProvider supplies individual argument _values for - single parameters using attributes derived from DataAttribute. - - - - - Determine whether any data is available for a parameter. - - A ParameterInfo representing one - argument to a parameterized test - - True if any data is available, otherwise false. - - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - An IParameterInfo representing one - argument to a parameterized test - - An IEnumerable providing the required data - - - - - SequentialStrategy creates test cases by using all of the - parameter data sources in parallel, substituting null - when any of them run out of data. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - Waits for pending asynchronous operations to complete, if appropriate, - and returns a proper result of the invocation by unwrapping task results - - The raw result of the method invocation - The unwrapped result, if necessary - - - - OneTimeSetUpCommand runs any one-time setup methods for a suite, - constructing the user test object if necessary. - - - - - Constructs a OneTimeSetUpCommand for a suite - - The suite to which the command applies - A SetUpTearDownList for use by the command - A List of TestActionItems to be run after Setup - - - - Overridden to run the one-time setup for a suite. - - The TestExecutionContext to be used. - A TestResult - - - - OneTimeTearDownCommand performs any teardown actions - specified for a suite and calls Dispose on the user - test object, if any. - - - - - Construct a OneTimeTearDownCommand - - The test suite to which the command applies - A SetUpTearDownList for use by the command - A List of TestActionItems to be run before teardown. - - - - Overridden to run the teardown methods specified on the test. - - The TestExecutionContext to be used. - A TestResult - - - - ContextSettingsCommand applies specified changes to the - TestExecutionContext prior to running a test. No special - action is needed after the test runs, since the prior - context will be restored automatically. - - - - - The CommandStage enumeration represents the defined stages - of execution for a series of TestCommands. The int _values - of the enum are used to apply decorators in the proper - order. Lower _values are applied first and are therefore - "closer" to the actual test execution. - - - No CommandStage is defined for actual invocation of the test or - for creation of the context. Execution may be imagined as - proceeding from the bottom of the list upwards, with cleanup - after the test running in the opposite order. - - - - - Use an application-defined default value. - - - - - Make adjustments needed before and after running - the raw test - that is, after any SetUp has run - and before TearDown. - - - - - Run SetUp and TearDown for the test. This stage is used - internally by NUnit and should not normally appear - in user-defined decorators. - - - - - Make adjustments needed before and after running - the entire test - including SetUp and TearDown. - - - - - TODO: Documentation needed for class - - - - - Initializes a new instance of the class. - - The inner command. - The max time allowed in milliseconds - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext - - The context in which the test should run. - A TestResult - - - - SetUpTearDownCommand runs any SetUp methods for a suite, - runs the test and then runs any TearDown methods. - - - - - Initializes a new instance of the class. - - The inner command. - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - SetUpTearDownItem holds the setup and teardown methods - for a single level of the inheritance hierarchy. - - - - - Construct a SetUpTearDownNode - - A list of setup methods for this level - A list teardown methods for this level - - - - Run SetUp on this level. - - The execution context to use for running. - - - - Run TearDown for this level. - - - - - - Returns true if this level has any methods at all. - This flag is used to discard levels that do nothing. - - - - - TODO: Documentation needed for class - - - - - Initializes a new instance of the class. - - The test being skipped. - - - - Overridden to simply set the CurrentResult to the - appropriate Skipped state. - - The execution context for the test - A TestResult - - - - TestActionCommand runs the BeforeTest actions for a test, - then runs the test and finally runs the AfterTestActions. - - - - - Initializes a new instance of the class. - - The inner command. - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - TestActionItem represents a single execution of an - ITestAction. It is used to track whether the BeforeTest - method has been called and suppress calling the - AfterTest method if it has not. - - - - - Construct a TestActionItem - - The ITestAction to be included - - - - Run the BeforeTest method of the action and remember that it has been run. - - The test to which the action applies - - - - Run the AfterTest action, but only if the BeforeTest - action was actually run. - - The test to which the action applies - - - - TestMethodCommand is the lowest level concrete command - used to run actual test cases. - - - - - Initializes a new instance of the class. - - The test. - - - - Runs the test, saving a TestResult in the execution context, as - well as returning it. If the test has an expected result, it - is asserts on that value. Since failed tests and errors throw - an exception, this command must be wrapped in an outer command, - will handle that exception and records the failure. This role - is usually played by the SetUpTearDown command. - - The execution context - - - - TheoryResultCommand adjusts the result of a Theory so that - it fails if all the results were inconclusive. - - - - - Constructs a TheoryResultCommand - - The command to be wrapped by this one - - - - Overridden to call the inner command and adjust the result - in case all chlid results were inconclusive. - - - - - - - ClassName filter selects tests based on the class FullName - - - - - ValueMatchFilter selects tests based on some value, which - is expected to be contained in the test. - - - - - Interface to be implemented by filters applied to tests. - The filter applies when running the test, after it has been - loaded, since this is the only time an ITest exists. - - - - - Unique Empty filter. - - - - - Determine if a particular test passes the filter criteria. The default - implementation checks the test itself, its parents and any descendants. - - Derived classes may override this method or any of the Match methods - to change the behavior of the filter. - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - Determine whether the test itself matches the filter criteria, without - examining either parents or descendants. This is overridden by each - different type of filter to perform the necessary tests. - - The test to which the filter is applied - True if the filter matches the any parent of the test - - - - Determine whether any ancestor of the test matches the filter criteria - - The test to which the filter is applied - True if the filter matches the an ancestor of the test - - - - Determine whether any descendant of the test matches the filter criteria. - - The test to be matched - True if at least one descendant matches the filter criteria - - - - Create a TestFilter instance from an xml representation. - - - - - Create a TestFilter from it's TNode representation - - - - - Adds an XML node - - True if recursive - The added XML node - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Indicates whether this is the EmptyFilter - - - - - Indicates whether this is a top-level filter, - not contained in any other filter. - - - - - Nested class provides an empty filter - one that always - returns true when called. It never matches explicitly. - - - - - Construct a ValueMatchFilter for a single value. - - The value to be included. - - - - Match the input provided by the derived class - - The value to be matchedT - True for a match, false otherwise. - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Returns the value matched by the filter - used for testing - - - - - Indicates whether the value is a regular expression - - - - - Gets the element name - - Element name - - - - Construct a FullNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - A base class for multi-part filters - - - - - Constructs an empty CompositeFilter - - - - - Constructs a CompositeFilter from an array of filters - - - - - - Adds a filter to the list of filters - - The filter to be added - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Return a list of the composing filters. - - - - - Gets the element name - - Element name - - - - FullName filter selects tests based on their FullName - - - - - Construct a FullNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - FullName filter selects tests based on their FullName - - - - - Construct a MethodNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - PropertyFilter is able to select or exclude tests - based on their properties. - - - - - - Construct a PropertyFilter using a property name and expected value - - A property name - The expected value of the property - - - - Check whether the filter matches a test - - The test to be matched - - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Gets the element name - - Element name - - - - TestName filter selects tests based on their Name - - - - - Construct a TestNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - GenericMethodHelper is able to deduce the Type arguments for - a generic method from the actual arguments provided. - - - - - Construct a GenericMethodHelper for a method - - MethodInfo for the method to examine - - - - Return the type argments for the method, deducing them - from the arguments actually provided. - - The arguments to the method - An array of type arguments. - - - - InvalidTestFixtureException is thrown when an appropriate test - fixture constructor using the provided arguments cannot be found. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The message. - - - - Initializes a new instance of the class. - - The message. - The inner. - - - - Serialization Constructor - - - - - CultureDetector is a helper class used by NUnit to determine - whether a test should be run based on the current culture. - - - - - Default constructor uses the current culture. - - - - - Construct a CultureDetector for a particular culture for testing. - - The culture to be used - - - - Test to determine if one of a collection of cultures - is being used currently. - - - - - - - Tests to determine if the current culture is supported - based on a culture attribute. - - The attribute to examine - - - - - Test to determine if the a particular culture or comma- - delimited set of cultures is in use. - - Name of the culture or comma-separated list of culture ids - True if the culture is in use on the system - - - - Return the last failure reason. Results are not - defined if called before IsSupported( Attribute ) - is called. - - - - - ExceptionHelper provides static methods for working with exceptions - - - - - Builds up a message, using the Message field of the specified exception - as well as any InnerExceptions. - - The exception. - A combined message string. - - - - Builds up a message, using the Message field of the specified exception - as well as any InnerExceptions. - - The exception. - A combined stack trace. - - - - Gets the stack trace of the exception. - - The exception. - A string representation of the stack trace. - - - - A utility class to create TestCommands - - - - - Gets the command to be executed before any of - the child tests are run. - - A TestCommand - - - - Gets the command to be executed after all of the - child tests are run. - - A TestCommand - - - - Creates a test command for use in running this test. - - - - - - Creates a command for skipping a test. The result returned will - depend on the test RunState. - - - - - Builds the set up tear down list. - - Type of the fixture. - Type of the set up attribute. - Type of the tear down attribute. - A list of SetUpTearDownItems - - - - A CompositeWorkItem represents a test suite and - encapsulates the execution of the suite as well - as all its child tests. - - - - - A WorkItem may be an individual test case, a fixture or - a higher level grouping of tests. All WorkItems inherit - from the abstract WorkItem class, which uses the template - pattern to allow derived classes to perform work in - whatever way is needed. - - A WorkItem is created with a particular TestExecutionContext - and is responsible for re-establishing that context in the - current thread before it begins or resumes execution. - - - - - Creates a work item. - - The test for which this WorkItem is being created. - The filter to be used in selecting any child Tests. - - - - - Construct a WorkItem for a particular test. - - The test that the WorkItem will run - - - - Initialize the TestExecutionContext. This must be done - before executing the WorkItem. - - - Originally, the context was provided in the constructor - but delaying initialization of the context until the item - is about to be dispatched allows changes in the parent - context during OneTimeSetUp to be reflected in the child. - - The TestExecutionContext to use - - - - Execute the current work item, including any - child work items. - - - - - Method that performs actually performs the work. It should - set the State to WorkItemState.Complete when done. - - - - - Method called by the derived class when all work is complete - - - - - Event triggered when the item is complete - - - - - Gets the current state of the WorkItem - - - - - The test being executed by the work item - - - - - The execution context - - - - - The test actions to be performed before and after this test - - - - - Indicates whether this WorkItem may be run in parallel - - - - - The test result - - - - - Construct a CompositeWorkItem for executing a test suite - using a filter to select child tests. - - The TestSuite to be executed - A filter used to select child tests - - - - Method that actually performs the work. Overridden - in CompositeWorkItem to do setup, run all child - items and then do teardown. - - - - - The EventPumpState enum represents the state of an - EventPump. - - - - - The pump is stopped - - - - - The pump is pumping events with no stop requested - - - - - The pump is pumping events but a stop has been requested - - - - - EventPump pulls events out of an EventQueue and sends - them to a listener. It is used to send events back to - the client without using the CallContext of the test - runner thread. - - - - - The handle on which a thread enqueuing an event with == true - waits, until the EventPump has sent the event to its listeners. - - - - - The downstream listener to which we send events - - - - - The queue that holds our events - - - - - Thread to do the pumping - - - - - The current state of the eventpump - - - - - Constructor - - The EventListener to receive events - The event queue to pull events from - - - - Dispose stops the pump - Disposes the used WaitHandle, too. - - - - - Start the pump - - - - - Tell the pump to stop after emptying the queue. - - - - - Our thread proc for removing items from the event - queue and sending them on. Note that this would - need to do more locking if any other thread were - removing events from the queue. - - - - - Gets or sets the current state of the pump - - - On volatile and , see - "http://www.albahari.com/threading/part4.aspx". - - - - - Gets or sets the name of this EventPump - (used only internally and for testing). - - - - - NUnit.Core.Event is the abstract base for all stored events. - An Event is the stored representation of a call to the - ITestListener interface and is used to record such calls - or to queue them for forwarding on another thread or at - a later time. - - - - - The Send method is implemented by derived classes to send the event to the specified listener. - - The listener. - - - - Gets a value indicating whether this event is delivered synchronously by the NUnit . - - If true, and if has been used to - set a WaitHandle, blocks its calling thread until the - thread has delivered the event and sets the WaitHandle. - - - - - - TestStartedEvent holds information needed to call the TestStarted method. - - - - - Initializes a new instance of the class. - - The test. - - - - Calls TestStarted on the specified listener. - - The listener. - - - - TestFinishedEvent holds information needed to call the TestFinished method. - - - - - Initializes a new instance of the class. - - The result. - - - - Calls TestFinished on the specified listener. - - The listener. - - - - Implements a queue of work items each of which - is queued as a WaitCallback. - - - - - Construct a new EventQueue - - - - - WaitHandle for synchronous event delivery in . - - Having just one handle for the whole implies that - there may be only one producer (the test thread) for synchronous events. - If there can be multiple producers for synchronous events, one would have - to introduce one WaitHandle per event. - - - - - - Sets a handle on which to wait, when is called - for an with == true. - - - The wait handle on which to wait, when is called - for an with == true. - The caller is responsible for disposing this wait handle. - - - - - Enqueues the specified event - - The event to enqueue. - - - - Removes the first element from the queue and returns it (or null). - - - If true and the queue is empty, the calling thread is blocked until - either an element is enqueued, or is called. - - - - - If the queue not empty - the first element. - - - otherwise, if ==false - or has been called - null. - - - - - - - Stop processing of the queue - - - - - Gets the count of items in the queue. - - - - - An IWorkItemDispatcher handles execution of work items. - - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and used when stopping the run. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - ParallelWorkItemDispatcher handles execution of work items by - queuing them for worker threads to process. - - - - - Construct a ParallelWorkItemDispatcher - - Number of workers to use - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and used when stopping the run. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - Enumerates all the shifts supported by the dispatcher - - - - - QueuingEventListener uses an EventQueue to store any - events received on its EventListener interface. - - - - - A test has started - - The test that is starting - - - - A test case finished - - Result of the test case - - - - The EvenQueue created and filled by this listener - - - - - A SimpleWorkItem represents a single test case and is - marked as completed immediately upon execution. This - class is also used for skipped or ignored test suites. - - - - - Construct a simple work item for a test. - - The test to be executed - The filter used to select this test - - - - Method that performs actually performs the work. - - - - - SimpleWorkItemDispatcher handles execution of WorkItems by - directly executing them. It is provided so that a dispatcher - is always available in the context, thereby simplifying the - code needed to run child tests. - - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and a thread is created on which to - run it. Subsequent calls come from the top level - item or its descendants on the proper thread. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - A TestWorker pulls work items from a queue - and executes them. - - - - - Construct a new TestWorker. - - The queue from which to pull work items - The name of this worker - The apartment state to use for running tests - - - - Our ThreadProc, which pulls and runs tests in a loop - - - - - Start processing work items. - - - - - Stop the thread, either immediately or after finishing the current WorkItem - - - - - Event signaled immediately before executing a WorkItem - - - - - Event signaled immediately after executing a WorkItem - - - - - The name of this worker - also used for the thread - - - - - Indicates whether the worker thread is running - - - - - The TextCapture class intercepts console output and writes it - to the current execution context, if one is present on the thread. - If no execution context is found, the output is written to a - default destination, normally the original destination of the - intercepted output. - - - - - Construct a TextCapture object - - The default destination for non-intercepted output - - - - Writes a single character - - The char to write - - - - Writes a string - - The string to write - - - - Writes a string followed by a line terminator - - The string to write - - - - Gets the Encoding in use by this TextWriter - - - - - TextMessageWriter writes constraint descriptions and messages - in displayable form as a text stream. It tailors the display - of individual message components to form the standard message - format of NUnit assertion failure messages. - - - - - Prefix used for the expected value line of a message - - - - - Prefix used for the actual value line of a message - - - - - Length of a message prefix - - - - - Construct a TextMessageWriter - - - - - Construct a TextMessageWriter, specifying a user message - and optional formatting arguments. - - - - - - - Method to write single line message with optional args, usually - written to precede the general failure message, at a given - indentation level. - - The indentation level of the message - The message to be written - Any arguments used in formatting the message - - - - Display Expected and Actual lines for a constraint. This - is called by MessageWriter's default implementation of - WriteMessageTo and provides the generic two-line display. - - The result of the constraint that failed - - - - Display Expected and Actual lines for given _values. This - method may be called by constraints that need more control over - the display of actual and expected _values than is provided - by the default implementation. - - The expected value - The actual value causing the failure - - - - Display Expected and Actual lines for given _values, including - a tolerance value on the expected line. - - The expected value - The actual value causing the failure - The tolerance within which the test was made - - - - Display the expected and actual string _values on separate lines. - If the mismatch parameter is >=0, an additional line is displayed - line containing a caret that points to the mismatch point. - - The expected string value - The actual string value - The point at which the strings don't match or -1 - If true, case is ignored in string comparisons - If true, clip the strings to fit the max line length - - - - Writes the text for an actual value. - - The actual value. - - - - Writes the text for a generalized value. - - The value. - - - - Writes the text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Write the generic 'Expected' line for a constraint - - The constraint that failed - - - - Write the generic 'Expected' line for a given value - - The expected value - - - - Write the generic 'Expected' line for a given value - and tolerance. - - The expected value - The tolerance within which the test was made - - - - Write the generic 'Actual' line for a constraint - - The ConstraintResult for which the actual value is to be written - - - - Write the generic 'Actual' line for a given value - - The actual value causing a failure - - - - Gets or sets the maximum line length for this writer - - - - - WorkItemQueueState indicates the current state of a WorkItemQueue - - - - - The queue is paused - - - - - The queue is running - - - - - The queue is stopped - - - - - A WorkItemQueue holds work items that are ready to - be run, either initially or after some dependency - has been satisfied. - - - - - Initializes a new instance of the class. - - The name of the queue. - - - - Enqueue a WorkItem to be processed - - The WorkItem to process - - - - Dequeue a WorkItem for processing - - A WorkItem or null if the queue has stopped - - - - Start or restart processing of items from the queue - - - - - Signal the queue to stop - - - - - Pause the queue for restarting later - - - - - Gets the name of the work item queue. - - - - - Gets the total number of items processed so far - - - - - Gets the maximum number of work items. - - - - - Gets the current state of the queue - - - - - Get a bool indicating whether the queue is empty. - - - - - The current state of a work item - - - - - Ready to run or continue - - - - - Work Item is executing - - - - - Complete - - - - - The dispatcher needs to do different things at different, - non-overlapped times. For example, non-parallel tests may - not be run at the same time as parallel tests. We model - this using the metaphor of a working shift. The WorkShift - class associates one or more WorkItemQueues with one or - more TestWorkers. - - Work in the queues is processed until all queues are empty - and all workers are idle. Both tests are needed because a - worker that is busy may end up adding more work to one of - the queues. At that point, the shift is over and another - shift may begin. This cycle continues until all the tests - have been run. - - - - - Construct a WorkShift - - - - - Add a WorkItemQueue to the shift, starting it if the - shift is currently active. - - - - - Assign a worker to the shift. - - - - - - Start or restart processing for the shift - - - - - End the shift, pausing all queues and raising - the EndOfShift event. - - - - - Shut down the shift. - - - - - Cancel the shift without completing all work - - - - - Event that fires when the shift has ended - - - - - Gets a flag indicating whether the shift is currently active - - - - - Gets a list of the queues associated with this shift. - - Used for testing - - - - Gets the list of workers associated with this shift. - - - - - Gets a bool indicating whether this shift has any work to do - - - - - Combines multiple filters so that a test must pass all - of them in order to pass this filter. - - - - - Constructs an empty AndFilter - - - - - Constructs an AndFilter from an array of filters - - - - - - Checks whether the AndFilter is matched by a test - - The test to be matched - True if all the component filters pass, otherwise false - - - - Checks whether the AndFilter is matched by a test - - The test to be matched - True if all the component filters match, otherwise false - - - - Gets the element name - - Element name - - - - CategoryFilter is able to select or exclude tests - based on their categories. - - - - - - Construct a CategoryFilter using a single category name - - A category name - - - - Check whether the filter matches a test - - The test to be matched - - - - - Gets the element name - - Element name - - - - IdFilter selects tests based on their id - - - - - Construct an IdFilter for a single value - - The id the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - NotFilter negates the operation of another filter - - - - - Construct a not filter on another filter - - The filter to be negated - - - - Determine if a particular test passes the filter criteria. The default - implementation checks the test itself, its parents and any descendants. - - Derived classes may override this method or any of the Match methods - to change the behavior of the filter. - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Check whether the filter matches a test - - The test to be matched - True if it matches, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Gets the base filter - - - - - Combines multiple filters so that a test must pass one - of them in order to pass this filter. - - - - - Constructs an empty OrFilter - - - - - Constructs an AndFilter from an array of filters - - - - - - Checks whether the OrFilter is matched by a test - - The test to be matched - True if any of the component filters pass, otherwise false - - - - Checks whether the OrFilter is matched by a test - - The test to be matched - True if any of the component filters match, otherwise false - - - - Gets the element name - - Element name - - - - InvalidTestFixtureException is thrown when an appropriate test - fixture constructor using the provided arguments cannot be found. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The message. - - - - Initializes a new instance of the class. - - The message. - The inner. - - - - Serialization Constructor - - - - - The MethodWrapper class wraps a MethodInfo so that it may - be used in a platform-independent manner. - - - - - Construct a MethodWrapper for a Type and a MethodInfo. - - - - - Construct a MethodInfo for a given Type and method name. - - - - - Gets the parameters of the method. - - - - - - Returns the Type arguments of a generic method or the Type parameters of a generic method definition. - - - - - Replaces the type parameters of the method with the array of types provided and returns a new IMethodInfo. - - The type arguments to be used - A new IMethodInfo with the type arguments replaced - - - - Returns an array of custom attributes of the specified type applied to this method - - - - - Gets a value indicating whether one or more attributes of the spcified type are defined on the method. - - - - - Invokes the method, converting any TargetInvocationException to an NUnitException. - - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - Override ToString() so that error messages in NUnit's own tests make sense - - - - - Gets the Type from which this method was reflected. - - - - - Gets the MethodInfo for this method. - - - - - Gets the name of the method. - - - - - Gets a value indicating whether the method is abstract. - - - - - Gets a value indicating whether the method is public. - - - - - Gets a value indicating whether the method contains unassigned generic type parameters. - - - - - Gets a value indicating whether the method is a generic method. - - - - - Gets a value indicating whether the MethodInfo represents the definition of a generic method. - - - - - Gets the return Type of the method. - - - - - Thrown when an assertion failed. Here to preserve the inner - exception and hence its stack trace. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The error message that explains - the reason for the exception - - - - Initializes a new instance of the class. - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - OSPlatform represents a particular operating system platform - - - - - Platform ID for Unix as defined by Microsoft .NET 2.0 and greater - - - - - Platform ID for Unix as defined by Mono - - - - - Platform ID for XBox as defined by .NET and Mono, but not CF - - - - - Platform ID for MacOSX as defined by .NET and Mono, but not CF - - - - - Gets the actual OS Version, not the incorrect value that might be - returned for Win 8.1 and Win 10 - - - If an application is not manifested as Windows 8.1 or Windows 10, - the version returned from Environment.OSVersion will not be 6.3 and 10.0 - respectively, but will be 6.2 and 6.3. The correct value can be found in - the registry. - - The original version - The correct OS version - - - - Construct from a platform ID and version - - - - - Construct from a platform ID, version and product type - - - - - Get the OSPlatform under which we are currently running - - - - - Get the platform ID of this instance - - - - - Get the Version of this instance - - - - - Get the Product Type of this instance - - - - - Return true if this is a windows platform - - - - - Return true if this is a Unix or Linux platform - - - - - Return true if the platform is Win32S - - - - - Return true if the platform is Win32Windows - - - - - Return true if the platform is Win32NT - - - - - Return true if the platform is Windows CE - - - - - Return true if the platform is Xbox - - - - - Return true if the platform is MacOSX - - - - - Return true if the platform is Windows 95 - - - - - Return true if the platform is Windows 98 - - - - - Return true if the platform is Windows ME - - - - - Return true if the platform is NT 3 - - - - - Return true if the platform is NT 4 - - - - - Return true if the platform is NT 5 - - - - - Return true if the platform is Windows 2000 - - - - - Return true if the platform is Windows XP - - - - - Return true if the platform is Windows 2003 Server - - - - - Return true if the platform is NT 6 - - - - - Return true if the platform is NT 6.0 - - - - - Return true if the platform is NT 6.1 - - - - - Return true if the platform is NT 6.2 - - - - - Return true if the platform is NT 6.3 - - - - - Return true if the platform is Vista - - - - - Return true if the platform is Windows 2008 Server (original or R2) - - - - - Return true if the platform is Windows 2008 Server (original) - - - - - Return true if the platform is Windows 2008 Server R2 - - - - - Return true if the platform is Windows 2012 Server (original or R2) - - - - - Return true if the platform is Windows 2012 Server (original) - - - - - Return true if the platform is Windows 2012 Server R2 - - - - - Return true if the platform is Windows 7 - - - - - Return true if the platform is Windows 8 - - - - - Return true if the platform is Windows 8.1 - - - - - Return true if the platform is Windows 10 - - - - - Return true if the platform is Windows Server. This is named Windows - Server 10 to distinguish it from previous versions of Windows Server. - - - - - Product Type Enumeration used for Windows - - - - - Product type is unknown or unspecified - - - - - Product type is Workstation - - - - - Product type is Domain Controller - - - - - Product type is Server - - - - - The ParameterWrapper class wraps a ParameterInfo so that it may - be used in a platform-independent manner. - - - - - Construct a ParameterWrapper for a given method and parameter - - - - - - - Returns an array of custom attributes of the specified type applied to this method - - - - - Gets a value indicating whether one or more attributes of the specified type are defined on the parameter. - - - - - Gets a value indicating whether the parameter is optional - - - - - Gets an IMethodInfo representing the method for which this is a parameter. - - - - - Gets the underlying ParameterInfo - - - - - Gets the Type of the parameter - - - - - PlatformHelper class is used by the PlatformAttribute class to - determine whether a platform is supported. - - - - - Comma-delimited list of all supported OS platform constants - - - - - Comma-delimited list of all supported Runtime platform constants - - - - - Default constructor uses the operating system and - common language runtime of the system. - - - - - Construct a PlatformHelper for a particular operating - system and common language runtime. Used in testing. - - OperatingSystem to be used - RuntimeFramework to be used - - - - Test to determine if one of a collection of platforms - is being used currently. - - - - - - - Tests to determine if the current platform is supported - based on a platform attribute. - - The attribute to examine - - - - - Tests to determine if the current platform is supported - based on a platform attribute. - - The attribute to examine - - - - - Test to determine if the a particular platform or comma- - delimited set of platforms is in use. - - Name of the platform or comma-separated list of platform ids - True if the platform is in use on the system - - - - Return the last failure reason. Results are not - defined if called before IsSupported( Attribute ) - is called. - - - - - A PropertyBag represents a collection of name value pairs - that allows duplicate entries with the same key. Methods - are provided for adding a new pair as well as for setting - a key to a single value. All keys are strings but _values - may be of any type. Null _values are not permitted, since - a null entry represents the absence of the key. - - - - - Adds a key/value pair to the property set - - The key - The value - - - - Sets the value for a key, removing any other - _values that are already in the property set. - - - - - - - Gets a single value for a key, using the first - one if multiple _values are present and returning - null if the value is not found. - - - - - - - Gets a flag indicating whether the specified key has - any entries in the property set. - - The key to be checked - - True if their are _values present, otherwise false - - - - - Returns an XmlNode representating the current PropertyBag. - - Not used - An XmlNode representing the PropertyBag - - - - Returns an XmlNode representing the PropertyBag after - adding it as a child of the supplied parent node. - - The parent node. - Not used - - - - - Gets a collection containing all the keys in the property set - - - - - - Gets or sets the list of _values for a particular key - - - - - The PropertyNames class provides static constants for the - standard property ids that NUnit uses on tests. - - - - - The FriendlyName of the AppDomain in which the assembly is running - - - - - The selected strategy for joining parameter data into test cases - - - - - The process ID of the executing assembly - - - - - The stack trace from any data provider that threw - an exception. - - - - - The reason a test was not run - - - - - The author of the tests - - - - - The ApartmentState required for running the test - - - - - The categories applying to a test - - - - - The Description of a test - - - - - The number of threads to be used in running tests - - - - - The maximum time in ms, above which the test is considered to have failed - - - - - The ParallelScope associated with a test - - - - - The number of times the test should be repeated - - - - - Indicates that the test should be run on a separate thread - - - - - The culture to be set for a test - - - - - The UI culture to be set for a test - - - - - The type that is under test - - - - - The timeout value for the test - - - - - The test will be ignored until the given date - - - - - Randomizer returns a set of random _values in a repeatable - way, to allow re-running of tests if necessary. It extends - the .NET Random class, providing random values for a much - wider range of types. - - The class is used internally by the framework to generate - test case data and is also exposed for use by users through - the TestContext.Random property. - - - For consistency with the underlying Random Type, methods - returning a single value use the prefix "Next..." Those - without an argument return a non-negative value up to - the full positive range of the Type. Overloads are provided - for specifying a maximum or a range. Methods that return - arrays or strings use the prefix "Get..." to avoid - confusion with the single-value methods. - - - - - Default characters for random functions. - - Default characters are the English alphabet (uppercase & lowercase), arabic numerals, and underscore - - - - Get a Randomizer for a particular member, returning - one that has already been created if it exists. - This ensures that the same _values are generated - each time the tests are reloaded. - - - - - Get a randomizer for a particular parameter, returning - one that has already been created if it exists. - This ensures that the same values are generated - each time the tests are reloaded. - - - - - Create a new Randomizer using the next seed - available to ensure that each randomizer gives - a unique sequence of values. - - - - - - Default constructor - - - - - Construct based on seed value - - - - - - Returns a random unsigned int. - - - - - Returns a random unsigned int less than the specified maximum. - - - - - Returns a random unsigned int within a specified range. - - - - - Returns a non-negative random short. - - - - - Returns a non-negative random short less than the specified maximum. - - - - - Returns a non-negative random short within a specified range. - - - - - Returns a random unsigned short. - - - - - Returns a random unsigned short less than the specified maximum. - - - - - Returns a random unsigned short within a specified range. - - - - - Returns a random long. - - - - - Returns a random long less than the specified maximum. - - - - - Returns a non-negative random long within a specified range. - - - - - Returns a random ulong. - - - - - Returns a random ulong less than the specified maximum. - - - - - Returns a non-negative random long within a specified range. - - - - - Returns a random Byte - - - - - Returns a random Byte less than the specified maximum. - - - - - Returns a random Byte within a specified range - - - - - Returns a random SByte - - - - - Returns a random sbyte less than the specified maximum. - - - - - Returns a random sbyte within a specified range - - - - - Returns a random bool - - - - - Returns a random bool based on the probablility a true result - - - - - Returns a random double between 0.0 and the specified maximum. - - - - - Returns a random double within a specified range. - - - - - Returns a random float. - - - - - Returns a random float between 0.0 and the specified maximum. - - - - - Returns a random float within a specified range. - - - - - Returns a random enum value of the specified Type as an object. - - - - - Returns a random enum value of the specified Type. - - - - - Generate a random string based on the characters from the input string. - - desired length of output string. - string representing the set of characters from which to construct the resulting string - A random string of arbitrary length - - - - Generate a random string based on the characters from the input string. - - desired length of output string. - A random string of arbitrary length - Uses DefaultStringChars as the input character set - - - - Generate a random string based on the characters from the input string. - - A random string of the default length - Uses DefaultStringChars as the input character set - - - - Returns a random decimal. - - - - - Returns a random decimal between positive zero and the specified maximum. - - - - - Returns a random decimal within a specified range, which is not - permitted to exceed decimal.MaxVal in the current implementation. - - - A limitation of this implementation is that the range from min - to max must not exceed decimal.MaxVal. - - - - - Initial seed used to create randomizers for this run - - - - - Helper methods for inspecting a type by reflection. - - Many of these methods take ICustomAttributeProvider as an - argument to avoid duplication, even though certain attributes can - only appear on specific types of members, like MethodInfo or Type. - - In the case where a type is being examined for the presence of - an attribute, interface or named member, the Reflect methods - operate with the full name of the member being sought. This - removes the necessity of the caller having a reference to the - assembly that defines the item being sought and allows the - NUnit core to inspect assemblies that reference an older - version of the NUnit framework. - - - - - Examine a fixture type and return an array of methods having a - particular attribute. The array is order with base methods first. - - The type to examine - The attribute Type to look for - Specifies whether to search the fixture type inheritance chain - The array of methods found - - - - Examine a fixture type and return true if it has a method with - a particular attribute. - - The type to examine - The attribute Type to look for - True if found, otherwise false - - - - Invoke the default constructor on a Type - - The Type to be constructed - An instance of the Type - - - - Invoke a constructor on a Type with arguments - - The Type to be constructed - Arguments to the constructor - An instance of the Type - - - - Returns an array of types from an array of objects. - Used because the compact framework doesn't support - Type.GetTypeArray() - - An array of objects - An array of Types - - - - Invoke a parameterless method returning void on an object. - - A MethodInfo for the method to be invoked - The object on which to invoke the method - - - - Invoke a method, converting any TargetInvocationException to an NUnitException. - - A MethodInfo for the method to be invoked - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - The TestResult class represents the result of a test. - - - - - The minimum duration for tests - - - - - Error message for when child tests have errors - - - - - Error message for when child tests are ignored - - - - - List of child results - - - - - Construct a test result given a Test - - The test to be used - - - - Returns the Xml representation of the result. - - If true, descendant results are included - An XmlNode representing the result - - - - Adds the XML representation of the result as a child of the - supplied parent node.. - - The parent node. - If true, descendant results are included - - - - - Adds a child result to this result, setting this result's - ResultState to Failure if the child result failed. - - The result to be added - - - - Set the result of the test - - The ResultState to use in the result - - - - Set the result of the test - - The ResultState to use in the result - A message associated with the result state - - - - Set the result of the test - - The ResultState to use in the result - A message associated with the result state - Stack trace giving the location of the command - - - - Set the test result based on the type of exception thrown - - The exception that was thrown - - - - Set the test result based on the type of exception thrown - - The exception that was thrown - THe FailureSite to use in the result - - - - RecordTearDownException appends the message and stacktrace - from an exception arising during teardown of the test - to any previously recorded information, so that any - earlier failure information is not lost. Note that - calling Assert.Ignore, Assert.Inconclusive, etc. during - teardown is treated as an error. If the current result - represents a suite, it may show a teardown error even - though all contained tests passed. - - The Exception to be recorded - - - - Adds a reason element to a node and returns it. - - The target node. - The new reason element. - - - - Adds a failure element to a node and returns it. - - The target node. - The new failure element. - - - - Gets the test with which this result is associated. - - - - - Gets the ResultState of the test result, which - indicates the success or failure of the test. - - - - - Gets the name of the test result - - - - - Gets the full name of the test result - - - - - Gets or sets the elapsed time for running the test in seconds - - - - - Gets or sets the time the test started running. - - - - - Gets or sets the time the test finished running. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. - - - - - Gets or sets the count of asserts executed - when running the test. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Indicates whether this result has any child results. - Test HasChildren before accessing Children to avoid - the creation of an empty collection. - - - - - Gets the collection of child results. - - - - - Gets a TextWriter, which will write output to be included in the result. - - - - - Gets any text output written to this result. - - - - - Enumeration identifying a common language - runtime implementation. - - - - Any supported runtime framework - - - Microsoft .NET Framework - - - Microsoft .NET Compact Framework - - - Microsoft Shared Source CLI - - - Mono - - - Silverlight - - - MonoTouch - - - - RuntimeFramework represents a particular version - of a common language runtime implementation. - - - - - DefaultVersion is an empty Version, used to indicate that - NUnit should select the CLR version to use for the test. - - - - - Construct from a runtime type and version. If the version has - two parts, it is taken as a framework version. If it has three - or more, it is taken as a CLR version. In either case, the other - version is deduced based on the runtime type and provided version. - - The runtime type of the framework - The version of the framework - - - - Parses a string representing a RuntimeFramework. - The string may be just a RuntimeType name or just - a Version or a hyphenated RuntimeType-Version or - a Version prefixed by 'versionString'. - - - - - - - Overridden to return the short name of the framework - - - - - - Returns true if the current framework matches the - one supplied as an argument. Two frameworks match - if their runtime types are the same or either one - is RuntimeType.Any and all specified version components - are equal. Negative (i.e. unspecified) version - components are ignored. - - The RuntimeFramework to be matched. - True on match, otherwise false - - - - Static method to return a RuntimeFramework object - for the framework that is currently in use. - - - - - The type of this runtime framework - - - - - The framework version for this runtime framework - - - - - The CLR version for this runtime framework - - - - - Return true if any CLR version may be used in - matching this RuntimeFramework object. - - - - - Returns the Display name for this framework - - - - - StackFilter class is used to remove internal NUnit - entries from a stack trace so that the resulting - trace provides better information about the test. - - - - - Filters a raw stack trace and returns the result. - - The original stack trace - A filtered stack trace - - - - Provides methods to support legacy string comparison methods. - - - - - Compares two strings for equality, ignoring case if requested. - - The first string. - The second string.. - if set to true, the case of the letters in the strings is ignored. - Zero if the strings are equivalent, a negative number if strA is sorted first, a positive number if - strB is sorted first - - - - Compares two strings for equality, ignoring case if requested. - - The first string. - The second string.. - if set to true, the case of the letters in the strings is ignored. - True if the strings are equivalent, false if not. - - - - The TestCaseParameters class encapsulates method arguments and - other selected parameters needed for constructing - a parameterized test case. - - - - - TestParameters is the abstract base class for all classes - that know how to provide data for constructing a test. - - - - - Default Constructor creates an empty parameter set - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a ParameterSet from an object implementing ITestData - - - - - - Applies ParameterSet _values to the test itself. - - A test. - - - - The RunState for this set of parameters. - - - - - The arguments to be used in running the test, - which must match the method signature. - - - - - A name to be used for this test case in lieu - of the standard generated name containing - the argument list. - - - - - Gets the property dictionary for this test - - - - - The original arguments provided by the user, - used for display purposes. - - - - - The expected result to be returned - - - - - Default Constructor creates an empty parameter set - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a ParameterSet from an object implementing ITestCaseData - - - - - - The expected result of the test, which - must match the method return type. - - - - - Gets a value indicating whether an expected result was specified. - - - - - Helper class used to save and restore certain static or - singleton settings in the environment that affect tests - or which might be changed by the user tests. - - An internal class is used to hold settings and a stack - of these objects is pushed and popped as Save and Restore - are called. - - - - - Link to a prior saved context - - - - - Indicates that a stop has been requested - - - - - The event listener currently receiving notifications - - - - - The number of assertions for the current test - - - - - The current culture - - - - - The current UI culture - - - - - The current test result - - - - - The current Principal. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - An existing instance of TestExecutionContext. - - - - The current context, head of the list of saved contexts. - - - - - Get the current context or return null if none is found. - - - - - Clear the current context. This is provided to - prevent "leakage" of the CallContext containing - the current context back to any runners. - - - - - Record any changes in the environment made by - the test code in the execution context so it - will be passed on to lower level tests. - - - - - Set up the execution environment to match a context. - Note that we may be running on the same thread where the - context was initially created or on a different thread. - - - - - Increments the assert count by one. - - - - - Increments the assert count by a specified amount. - - - - - Obtain lifetime service object - - - - - - Gets the current context. - - The current context. - - - - Gets or sets the current test - - - - - The time the current test started execution - - - - - The time the current test started in Ticks - - - - - Gets or sets the current test result - - - - - Gets a TextWriter that will send output to the current test result. - - - - - The current test object - that is the user fixture - object on which tests are being executed. - - - - - Get or set the working directory - - - - - Get or set indicator that run should stop on the first error - - - - - Gets an enum indicating whether a stop has been requested. - - - - - The current test event listener - - - - - The current WorkItemDispatcher - - - - - The ParallelScope to be used by tests running in this context. - For builds with out the parallel feature, it has no effect. - - - - - Gets the RandomGenerator specific to this Test - - - - - Gets the assert count. - - The assert count. - - - - Gets or sets the test case timeout value - - - - - Gets a list of ITestActions set by upstream tests - - - - - Saves or restores the CurrentCulture - - - - - Saves or restores the CurrentUICulture - - - - - Gets or sets the current for the Thread. - - - - - Enumeration indicating whether the tests are - running normally or being cancelled. - - - - - Running normally with no stop requested - - - - - A graceful stop has been requested - - - - - A forced stop has been requested - - - - - The TestCaseParameters class encapsulates method arguments and - other selected parameters needed for constructing - a parameterized test case. - - - - - Default Constructor creates an empty parameter set - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a ParameterSet from an object implementing ITestCaseData - - - - - - Type arguments used to create a generic fixture instance - - - - - TestListener provides an implementation of ITestListener that - does nothing. It is used only through its NULL property. - - - - - Called when a test has just started - - The test that is starting - - - - Called when a test case has finished - - The result of the test - - - - Construct a new TestListener - private so it may not be used. - - - - - Get a listener that does nothing - - - - - TestNameGenerator is able to create test names according to - a coded pattern. - - - - - Construct a TestNameGenerator - - The pattern used by this generator. - - - - Get the display name for a TestMethod and it's arguments - - A TestMethod - The display name - - - - Get the display name for a TestMethod and it's arguments - - A TestMethod - Arguments to be used - The display name - - - - Get the display name for a MethodInfo - - A MethodInfo - The display name - - - - Get the display name for a method with args - - A MethodInfo - Argument list for the method - The display name - - - - TestProgressReporter translates ITestListener events into - the async callbacks that are used to inform the client - software about the progress of a test run. - - - - - Initializes a new instance of the class. - - The callback handler to be used for reporting progress. - - - - Called when a test has just started - - The test that is starting - - - - Called when a test has finished. Sends a result summary to the callback. - to - - The result of the test - - - - Returns the parent test item for the targer test item if it exists - - - parent test item - - - - Makes a string safe for use as an attribute, replacing - characters characters that can't be used with their - corresponding xml representations. - - The string to be used - A new string with the _values replaced - - - - ParameterizedFixtureSuite serves as a container for the set of test - fixtures created from a given Type using various parameters. - - - - - TestSuite represents a composite test, which contains other tests. - - - - - The Test abstract class represents a test within the framework. - - - - - Static value to seed ids. It's started at 1000 so any - uninitialized ids will stand out. - - - - - The SetUp methods. - - - - - The teardown methods - - - - - Constructs a test given its name - - The name of the test - - - - Constructs a test given the path through the - test hierarchy to its parent and a name. - - The parent tests full name - The name of the test - - - - TODO: Documentation needed for constructor - - - - - - Construct a test from a MethodInfo - - - - - - Creates a TestResult for this test. - - A TestResult suitable for this type of test. - - - - Modify a newly constructed test by applying any of NUnit's common - attributes, based on a supplied ICustomAttributeProvider, which is - usually the reflection element from which the test was constructed, - but may not be in some instances. The attributes retrieved are - saved for use in subsequent operations. - - An object implementing ICustomAttributeProvider - - - - Add standard attributes and members to a test node. - - - - - - - Returns the Xml representation of the test - - If true, include child tests recursively - - - - - Returns an XmlNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Compares this test to another test for sorting purposes - - The other test - Value of -1, 0 or +1 depending on whether the current test is less than, equal to or greater than the other test - - - - Gets or sets the id of the test - - - - - - Gets or sets the name of the test - - - - - Gets or sets the fully qualified name of the test - - - - - - Gets the name of the class containing this test. Returns - null if the test is not associated with a class. - - - - - Gets the name of the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the TypeInfo of the fixture used in running this test - or null if no fixture type is associated with it. - - - - - Gets a MethodInfo for the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Whether or not the test should be run - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Gets a string representing the type of test. Used as an attribute - value in the XML representation of a test and has no other - function in the framework. - - - - - Gets a count of test cases represented by - or contained under this test. - - - - - Gets the properties for this test - - - - - Returns true if this is a TestSuite - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets the parent as a Test object. - Used by the core to set the parent. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets or sets a fixture object for running this test. - - - - - Static prefix used for ids in this AppDomain. - Set by FrameworkController. - - - - - Gets or Sets the Int value representing the seed for the RandomGenerator - - - - - - Our collection of child tests - - - - - Initializes a new instance of the class. - - The name of the suite. - - - - Initializes a new instance of the class. - - Name of the parent suite. - The name of the suite. - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - Sorts tests under this suite. - - - - - Adds a test to the suite. - - The test. - - - - Overridden to return a TestSuiteResult. - - A TestResult for this test. - - - - Returns an XmlNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Check that setup and teardown methods marked by certain attributes - meet NUnit's requirements and mark the tests not runnable otherwise. - - The attribute type to check for - - - - Gets this test's child tests - - The list of child tests - - - - Gets a count of test cases represented by - or contained under this test. - - - - - - The arguments to use in creating the fixture - - - - - Set to true to suppress sorting this suite's contents - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Initializes a new instance of the class. - - The ITypeInfo for the type that represents the suite. - - - - Gets a string representing the type of test - - - - - - ParameterizedMethodSuite holds a collection of individual - TestMethods with their arguments applied. - - - - - Construct from a MethodInfo - - - - - - Gets a string representing the type of test - - - - - - SetUpFixture extends TestSuite and supports - Setup and TearDown methods. - - - - - Initializes a new instance of the class. - - The type. - - - - TestAssembly is a TestSuite that represents the execution - of tests in a managed assembly. - - - - - Initializes a new instance of the class - specifying the Assembly and the path from which it was loaded. - - The assembly this test represents. - The path used to load the assembly. - - - - Initializes a new instance of the class - for a path which could not be loaded. - - The path used to load the assembly. - - - - Gets the Assembly represented by this instance. - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - TestFixture is a surrogate for a user test fixture class, - containing one or more tests. - - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - The TestMethod class represents a Test implemented as a method. - - - - - The ParameterSet used to create this test method - - - - - Initializes a new instance of the class. - - The method to be used as a test. - - - - Initializes a new instance of the class. - - The method to be used as a test. - The suite or fixture to which the new test will be added - - - - Overridden to return a TestCaseResult. - - A TestResult for this test. - - - - Returns a TNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Returns the name of the method - - - - - ThreadUtility provides a set of static methods convenient - for working with threads. - - - - - Do our best to Kill a thread - - The thread to kill - - - - Do our best to kill a thread, passing state info - - The thread to kill - Info for the ThreadAbortException handler - - - - TypeHelper provides static methods that operate on Types. - - - - - A special value, which is used to indicate that BestCommonType() method - was unable to find a common type for the specified arguments. - - - - - Gets the display name for a Type as used by NUnit. - - The Type for which a display name is needed. - The display name for the Type - - - - Gets the display name for a Type as used by NUnit. - - The Type for which a display name is needed. - The arglist provided. - The display name for the Type - - - - Returns the best fit for a common type to be used in - matching actual arguments to a methods Type parameters. - - The first type. - The second type. - Either type1 or type2, depending on which is more general. - - - - Determines whether the specified type is numeric. - - The type to be examined. - - true if the specified type is numeric; otherwise, false. - - - - - Convert an argument list to the required parameter types. - Currently, only widening numeric conversions are performed. - - An array of args to be converted - A ParameterInfo[] whose types will be used as targets - - - - Determines whether this instance can deduce type args for a generic type from the supplied arguments. - - The type to be examined. - The arglist. - The type args to be used. - - true if this the provided args give sufficient information to determine the type args to be used; otherwise, false. - - - - - Gets the _values for an enumeration, using Enum.GetTypes - where available, otherwise through reflection. - - - - - - - Gets the ids of the _values for an enumeration, - using Enum.GetNames where available, otherwise - through reflection. - - - - - - - The TypeWrapper class wraps a Type so it may be used in - a platform-independent manner. - - - - - Construct a TypeWrapper for a specified Type. - - - - - Returns true if the Type wrapped is T - - - - - Get the display name for this type - - - - - Get the display name for an object of this type, constructed with the specified args. - - - - - Returns a new ITypeInfo representing an instance of this generic Type using the supplied Type arguments - - - - - Returns a Type representing a generic type definition from which this Type can be constructed. - - - - - Returns an array of custom attributes of the specified type applied to this type - - - - - Returns a value indicating whether the type has an attribute of the specified type. - - - - - - - - Returns a flag indicating whether this type has a method with an attribute of the specified type. - - - - - - - Returns an array of IMethodInfos for methods of this Type - that match the specified flags. - - - - - Returns a value indicating whether this Type has a public constructor taking the specified argument Types. - - - - - Construct an object of this Type, using the specified arguments. - - - - - Override ToString() so that error messages in NUnit's own tests make sense - - - - - Gets the underlying Type on which this TypeWrapper is based. - - - - - Gets the base type of this type as an ITypeInfo - - - - - Gets the Name of the Type - - - - - Gets the FullName of the Type - - - - - Gets the assembly in which the type is declared - - - - - Gets the namespace of the Type - - - - - Gets a value indicating whether the type is abstract. - - - - - Gets a value indicating whether the Type is a generic Type - - - - - Gets a value indicating whether the Type has generic parameters that have not been replaced by specific Types. - - - - - Gets a value indicating whether the Type is a generic Type definition - - - - - Gets a value indicating whether the type is sealed. - - - - - Gets a value indicating whether this type represents a static class. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable to the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable to the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that tests whether the path provided - is the same as an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is a subpath of the expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the actual value falls - inclusively within a specified range. - - from must be less than or equal to true - Inclusive beginning of the range. Must be less than or equal to to. - Inclusive end of the range. Must be greater than or equal to from. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether an object graph is serializable in binary format. - - - - - Returns a constraint that tests whether an object graph is serializable in xml format. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - The Iz class is a synonym for Is intended for use in VB, - which regards Is as a keyword. - - - - - The List class is a helper class with properties and methods - that supply a number of constraints used with lists and collections. - - - - - List.Map returns a ListMapper, which can be used to map - the original collection to another collection. - - - - - - - ListMapper is used to transform a collection used as an actual argument - producing another collection to be used in the assertion. - - - - - Construct a ListMapper based on a collection - - The collection to be transformed - - - - Produces a collection containing all the _values of a property - - The collection of property _values - - - - - The SpecialValue enum is used to represent TestCase arguments - that cannot be used as arguments to an Attribute. - - - - - Null represents a null value, which cannot be used as an - argument to an attriute under .NET 1.x - - - - - Basic Asserts on strings. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - - - - Asserts that a string is not found within another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - - - - Asserts that two strings are not equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that two strings are not equal, without regard to case. - - The expected string - The actual string - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - - - - The TestCaseData class represents a set of arguments - and other parameter info to be used for a parameterized - test case. It is derived from TestCaseParameters and adds a - fluent syntax for use in initializing the test case. - - - - - Initializes a new instance of the class. - - The arguments. - - - - Initializes a new instance of the class. - - The argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - The third argument. - - - - Sets the expected result for the test - - The expected result - A modified TestCaseData - - - - Sets the name of the test case - - The modified TestCaseData instance - - - - Sets the description for the test case - being constructed. - - The description. - The modified TestCaseData instance. - - - - Applies a category to the test - - - - - - - Applies a named property to the test - - - - - - - - Applies a named property to the test - - - - - - - - Applies a named property to the test - - - - - - - - Marks the test case as explicit. - - - - - Marks the test case as explicit, specifying the reason. - - - - - Ignores this TestCase, specifying the reason. - - The reason. - - - - - Provide the context information of the current test. - This is an adapter for the internal ExecutionContext - class, hiding the internals from the user test. - - - - - Construct a TestContext for an ExecutionContext - - The ExecutionContext to adapt - - - Write the string representation of a boolean value to the current result - - - Write a char to the current result - - - Write a char array to the current result - - - Write the string representation of a double to the current result - - - Write the string representation of an Int32 value to the current result - - - Write the string representation of an Int64 value to the current result - - - Write the string representation of a decimal value to the current result - - - Write the string representation of an object to the current result - - - Write the string representation of a Single value to the current result - - - Write a string to the current result - - - Write the string representation of a UInt32 value to the current result - - - Write the string representation of a UInt64 value to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a line terminator to the current result - - - Write the string representation of a boolean value to the current result followed by a line terminator - - - Write a char to the current result followed by a line terminator - - - Write a char array to the current result followed by a line terminator - - - Write the string representation of a double to the current result followed by a line terminator - - - Write the string representation of an Int32 value to the current result followed by a line terminator - - - Write the string representation of an Int64 value to the current result followed by a line terminator - - - Write the string representation of a decimal value to the current result followed by a line terminator - - - Write the string representation of an object to the current result followed by a line terminator - - - Write the string representation of a Single value to the current result followed by a line terminator - - - Write a string to the current result followed by a line terminator - - - Write the string representation of a UInt32 value to the current result followed by a line terminator - - - Write the string representation of a UInt64 value to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - - Get the current test context. This is created - as needed. The user may save the context for - use within a test, but it should not be used - outside the test for which it is created. - - - - - Gets a TextWriter that will send output to the current test result. - - - - - Get a representation of the current test. - - - - - Gets a Representation of the TestResult for the current test. - - - - - Gets the directory containing the current test assembly. - - - - - Gets the directory to be used for outputting files created - by this test run. - - - - - Gets the random generator. - - - The random generator. - - - - - TestAdapter adapts a Test for consumption by - the user test code. - - - - - Construct a TestAdapter for a Test - - The Test to be adapted - - - - Gets the unique Id of a test - - - - - The name of the test, which may or may not be - the same as the method name. - - - - - The name of the method representing the test. - - - - - The FullName of the test - - - - - The ClassName of the test - - - - - The properties of the test. - - - - - ResultAdapter adapts a TestResult for consumption by - the user test code. - - - - - Construct a ResultAdapter for a TestResult - - The TestResult to be adapted - - - - Gets a ResultState representing the outcome of the test. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - The TestFixtureData class represents a set of arguments - and other parameter info to be used for a parameterized - fixture. It is derived from TestFixtureParameters and adds a - fluent syntax for use in initializing the fixture. - - - - - Initializes a new instance of the class. - - The arguments. - - - - Initializes a new instance of the class. - - The argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - The third argument. - - - - Marks the test fixture as explicit. - - - - - Marks the test fixture as explicit, specifying the reason. - - - - - Ignores this TestFixture, specifying the reason. - - The reason. - - - - - Helper class with properties and methods that supply - constraints that operate on exceptions. - - - - - Creates a constraint specifying the exact type of exception expected - - - - - Creates a constraint specifying the exact type of exception expected - - - - - Creates a constraint specifying the type of exception expected - - - - - Creates a constraint specifying the type of exception expected - - - - - Creates a constraint specifying an expected exception - - - - - Creates a constraint specifying an exception with a given InnerException - - - - - Creates a constraint specifying an expected TargetInvocationException - - - - - Creates a constraint specifying an expected ArgumentException - - - - - Creates a constraint specifying an expected ArgumentNUllException - - - - - Creates a constraint specifying an expected InvalidOperationException - - - - - Creates a constraint specifying that no exception is thrown - - - - - Represents the result of running a single test case. - - - - - Construct a TestCaseResult based on a TestMethod - - A TestMethod to which the result applies. - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Represents the result of running a test suite - - - - - Construct a TestSuiteResult base on a TestSuite - - The TestSuite to which the result applies - - - - Add a child result - - The child result to be added - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - ExactCountConstraint applies another constraint to each - item in a collection, succeeding only if a specified - number of items succeed. - - - - - Construct an ExactCountConstraint on top of an existing constraint - - - - - - - Apply the item constraint to each item in the collection, - succeeding only if the expected number of items pass. - - - - - - - Represents a constraint that succeeds if the specified - count of members of a collection match a base constraint. - - - - - Construct an ExactCountOperator for a specified count - - The expected count - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - none of them succeed. - - - - - ExceptionTypeConstraint is a special version of ExactTypeConstraint - used to provided detailed info about the exception thrown in - an error message. - - - - - Constructs an ExceptionTypeConstraint - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - diff --git a/packages/NUnit.3.0.0/lib/portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10/nunit.framework.dll b/packages/NUnit.3.0.0/lib/portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10/nunit.framework.dll deleted file mode 100644 index 5e721f0be..000000000 Binary files a/packages/NUnit.3.0.0/lib/portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10/nunit.framework.dll and /dev/null differ diff --git a/packages/NUnit.3.0.0/lib/portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10/nunit.framework.xml b/packages/NUnit.3.0.0/lib/portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10/nunit.framework.xml deleted file mode 100644 index cfc59be9b..000000000 --- a/packages/NUnit.3.0.0/lib/portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10/nunit.framework.xml +++ /dev/null @@ -1,15231 +0,0 @@ - - - - nunit.framework - - - - - AssemblyHelper provides static methods for working - with assemblies. - - - - - Gets the AssemblyName of an assembly. - - The assembly - An AssemblyName - - - - Loads an assembly given a string, which is the AssemblyName - - - - - - - Env is a static class that provides some of the features of - System.Environment that are not available under all runtimes - - - - - The newline sequence in the current environment. - - - - - Path to the 'My Documents' folder - - - - - Directory used for file output if not specified on commandline. - - - - - Class used to guard against unexpected argument values - or operations by throwing an appropriate exception. - - - - - Throws an exception if an argument is null - - The value to be tested - The name of the argument - - - - Throws an exception if a string argument is null or empty - - The value to be tested - The name of the argument - - - - Throws an ArgumentOutOfRangeException if the specified condition is not met. - - The condition that must be met - The exception message to be used - The name of the argument - - - - Throws an ArgumentException if the specified condition is not met. - - The condition that must be met - The exception message to be used - The name of the argument - - - - Throws an InvalidOperationException if the specified condition is not met. - - The condition that must be met - The exception message to be used - - - - Interface for logging within the engine - - - - - Logs the specified message at the error level. - - The message. - - - - Logs the specified message at the error level. - - The message. - The arguments. - - - - Logs the specified message at the warning level. - - The message. - - - - Logs the specified message at the warning level. - - The message. - The arguments. - - - - Logs the specified message at the info level. - - The message. - - - - Logs the specified message at the info level. - - The message. - The arguments. - - - - Logs the specified message at the debug level. - - The message. - - - - Logs the specified message at the debug level. - - The message. - The arguments. - - - - InternalTrace provides facilities for tracing the execution - of the NUnit framework. Tests and classes under test may make use - of Console writes, System.Diagnostics.Trace or various loggers and - NUnit itself traps and processes each of them. For that reason, a - separate internal trace is needed. - - Note: - InternalTrace uses a global lock to allow multiple threads to write - trace messages. This can easily make it a bottleneck so it must be - used sparingly. Keep the trace Level as low as possible and only - insert InternalTrace writes where they are needed. - TODO: add some buffering and a separate writer thread as an option. - TODO: figure out a way to turn on trace in specific classes only. - - - - - Initialize the internal trace using a provided TextWriter and level - - A TextWriter - The InternalTraceLevel - - - - Get a named Logger - - - - - - Get a logger named for a particular Type. - - - - - Gets a flag indicating whether the InternalTrace is initialized - - - - - InternalTraceLevel is an enumeration controlling the - level of detailed presented in the internal log. - - - - - Use the default settings as specified by the user. - - - - - Do not display any trace messages - - - - - Display Error messages only - - - - - Display Warning level and higher messages - - - - - Display informational and higher messages - - - - - Display debug messages and higher - i.e. all messages - - - - - Display debug messages and higher - i.e. all messages - - - - - A trace listener that writes to a separate file per domain - and process using it. - - - - - Construct an InternalTraceWriter that writes to a - TextWriter provided by the caller. - - - - - - Writes a character to the text string or stream. - - The character to write to the text stream. - - - - Writes a string to the text string or stream. - - The string to write. - - - - Writes a string followed by a line terminator to the text string or stream. - - The string to write. If is null, only the line terminator is written. - - - - Releases the unmanaged resources used by the and optionally releases the managed resources. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - Clears all buffers for the current writer and causes any buffered data to be written to the underlying device. - - - - - Returns the character encoding in which the output is written. - - The character encoding in which the output is written. - - - - Provides internal logging to the NUnit framework - - - - - Initializes a new instance of the class. - - The name. - The log level. - The writer where logs are sent. - - - - Logs the message at error level. - - The message. - - - - Logs the message at error level. - - The message. - The message arguments. - - - - Logs the message at warm level. - - The message. - - - - Logs the message at warning level. - - The message. - The message arguments. - - - - Logs the message at info level. - - The message. - - - - Logs the message at info level. - - The message. - The message arguments. - - - - Logs the message at debug level. - - The message. - - - - Logs the message at debug level. - - The message. - The message arguments. - - - - PackageSettings is a static class containing constant values that - are used as keys in setting up a TestPackage. These values are used in - the engine and framework. Setting values may be a string, int or bool. - - - - - Flag (bool) indicating whether tests are being debugged. - - - - - The InternalTraceLevel for this run. Values are: "Default", - "Off", "Error", "Warning", "Info", "Debug", "Verbose". - Default is "Off". "Debug" and "Verbose" are synonyms. - - - - - Full path of the directory to be used for work and result files. - This path is provided to tests by the frameowrk TestContext. - - - - - The name of the config to use in loading a project. - If not specified, the first config found is used. - - - - - Bool indicating whether the engine should determine the private - bin path by examining the paths to all the tests. Defaults to - true unless PrivateBinPath is specified. - - - - - The ApplicationBase to use in loading the tests. If not - specified, and each assembly has its own process, then the - location of the assembly is used. For multiple assemblies - in a single process, the closest common root directory is used. - - - - - Path to the config file to use in running the tests. - - - - - Bool flag indicating whether a debugger should be launched at agent - startup. Used only for debugging NUnit itself. - - - - - Indicates how to load tests across AppDomains. Values are: - "Default", "None", "Single", "Multiple". Default is "Multiple" - if more than one assembly is loaded in a process. Otherwise, - it is "Single". - - - - - The private binpath used to locate assemblies. Directory paths - is separated by a semicolon. It's an error to specify this and - also set AutoBinPath to true. - - - - - The maximum number of test agents permitted to run simultneously. - Ignored if the ProcessModel is not set or defaulted to Multiple. - - - - - Indicates how to allocate assemblies to processes. Values are: - "Default", "Single", "Separate", "Multiple". Default is "Multiple" - for more than one assembly, "Separate" for a single assembly. - - - - - Indicates the desired runtime to use for the tests. Values - are strings like "net-4.5", "mono-4.0", etc. Default is to - use the target framework for which an assembly was built. - - - - - Bool flag indicating that the test should be run in a 32-bit process - on a 64-bit system. By default, NUNit runs in a 64-bit process on - a 64-bit system. Ignored if set on a 32-bit system. - - - - - Indicates that test runners should be disposed after the tests are executed - - - - - Bool flag indicating that the test assemblies should be shadow copied. - Defaults to false. - - - - - Integer value in milliseconds for the default timeout value - for test cases. If not specified, there is no timeout except - as specified by attributes on the tests themselves. - - - - - A TextWriter to which the internal trace will be sent. - - - - - A list of tests to be loaded. - - - - - The number of test threads to run for the assembly. If set to - 1, a single queue is used. If set to 0, tests are executed - directly, without queuing. - - - - - The random seed to be used for this assembly. If specified - as the value reported from a prior run, the framework should - generate identical random values for tests as were used for - that run, provided that no change has been made to the test - assembly. Default is a random value itself. - - - - - If true, execution stops after the first error or failure. - - - - - If true, use of the event queue is suppressed and test events are synchronous. - - - - - The different targets a test action attribute can be applied to - - - - - Default target, which is determined by where the action attribute is attached - - - - - Target a individual test case - - - - - Target a suite of test cases - - - - - DefaultTestAssemblyBuilder loads a single assembly and builds a TestSuite - containing test fixtures present in the assembly. - - - - - The ITestAssemblyBuilder interface is implemented by a class - that is able to build a suite of tests given an assembly or - an assembly filename. - - - - - Build a suite of tests from a provided assembly - - The assembly from which tests are to be built - A dictionary of options to use in building the suite - A TestSuite containing the tests found in the assembly - - - - Build a suite of tests given the filename of an assembly - - The filename of the assembly from which tests are to be built - A dictionary of options to use in building the suite - A TestSuite containing the tests found in the assembly - - - - The default suite builder used by the test assembly builder. - - - - - Initializes a new instance of the class. - - - - - Build a suite of tests from a provided assembly - - The assembly from which tests are to be built - A dictionary of options to use in building the suite - - A TestSuite containing the tests found in the assembly - - - - - Build a suite of tests given the filename of an assembly - - The filename of the assembly from which tests are to be built - A dictionary of options to use in building the suite - - A TestSuite containing the tests found in the assembly - - - - - FrameworkController provides a facade for use in loading, browsing - and running tests without requiring a reference to the NUnit - framework. All calls are encapsulated in constructors for - this class and its nested classes, which only require the - types of the Common Type System as arguments. - - The controller supports four actions: Load, Explore, Count and Run. - They are intended to be called by a driver, which should allow for - proper sequencing of calls. Load must be called before any of the - other actions. The driver may support other actions, such as - reload on run, by combining these calls. - - - - - A MarshalByRefObject that lives forever - - - - - Construct a FrameworkController using the default builder and runner. - - The AssemblyName or path to the test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - - - - Construct a FrameworkController using the default builder and runner. - - The test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - - - - Construct a FrameworkController, specifying the types to be used - for the runner and builder. This constructor is provided for - purposes of development. - - The full AssemblyName or the path to the test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - The Type of the test runner - The Type of the test builder - - - - Construct a FrameworkController, specifying the types to be used - for the runner and builder. This constructor is provided for - purposes of development. - - The test assembly - A prefix used for all test ids created under this controller. - A Dictionary of settings to use in loading and running the tests - The Type of the test runner - The Type of the test builder - - - - Inserts settings element - - Target node - Settings dictionary - The new node - - - - Gets the ITestAssemblyBuilder used by this controller instance. - - The builder. - - - - Gets the ITestAssemblyRunner used by this controller instance. - - The runner. - - - - Gets the AssemblyName or the path for which this FrameworkController was created - - - - - Gets the Assembly for which this - - - - - Gets a dictionary of settings for the FrameworkController - - - - - FrameworkControllerAction is the base class for all actions - performed against a FrameworkController. - - - - - LoadTestsAction loads a test into the FrameworkController - - - - - LoadTestsAction loads the tests in an assembly. - - The controller. - The callback handler. - - - - ExploreTestsAction returns info about the tests in an assembly - - - - - Initializes a new instance of the class. - - The controller for which this action is being performed. - Filter used to control which tests are included (NYI) - The callback handler. - - - - CountTestsAction counts the number of test cases in the loaded TestSuite - held by the FrameworkController. - - - - - Construct a CountsTestAction and perform the count of test cases. - - A FrameworkController holding the TestSuite whose cases are to be counted - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - RunTestsAction runs the loaded TestSuite held by the FrameworkController. - - - - - Construct a RunTestsAction and run all tests in the loaded TestSuite. - - A FrameworkController holding the TestSuite to run - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - RunAsyncAction initiates an asynchronous test run, returning immediately - - - - - Construct a RunAsyncAction and run all tests in the loaded TestSuite. - - A FrameworkController holding the TestSuite to run - A string containing the XML representation of the filter to use - A callback handler used to report results - - - - StopRunAction stops an ongoing run. - - - - - Construct a StopRunAction and stop any ongoing run. If no - run is in process, no error is raised. - - The FrameworkController for which a run is to be stopped. - True the stop should be forced, false for a cooperative stop. - >A callback handler used to report results - A forced stop will cause threads and processes to be killed as needed. - - - - The ITestAssemblyRunner interface is implemented by classes - that are able to execute a suite of tests loaded - from an assembly. - - - - - Loads the tests found in an Assembly, returning an - indication of whether or not the load succeeded. - - File name of the assembly to load - Dictionary of options to use in loading the test - An ITest representing the loaded tests - - - - Loads the tests found in an Assembly, returning an - indication of whether or not the load succeeded. - - The assembly to load - Dictionary of options to use in loading the test - An ITest representing the loaded tests - - - - Count Test Cases using a filter - - The filter to apply - The number of test cases found - - - - Run selected tests and return a test result. The test is run synchronously, - and the listener interface is notified as it progresses. - - Interface to receive ITestListener notifications. - A test filter used to select tests to be run - - - - Run selected tests asynchronously, notifying the listener interface as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - - - Wait for the ongoing run to complete. - - Time to wait in milliseconds - True if the run completed, otherwise false - - - - Signal any test run that is in process to stop. Return without error if no test is running. - - If true, kill any test-running threads - - - - Gets the tree of loaded tests, or null if - no tests have been loaded. - - - - - Gets the tree of test results, if the test - run is completed, otherwise null. - - - - - Indicates whether a test has been loaded - - - - - Indicates whether a test is currently running - - - - - Indicates whether a test run is complete - - - - - Implementation of ITestAssemblyRunner - - - - - Initializes a new instance of the class. - - The builder. - - - - Loads the tests found in an Assembly - - File name of the assembly to load - Dictionary of option settings for loading the assembly - True if the load was successful - - - - Loads the tests found in an Assembly - - The assembly to load - Dictionary of option settings for loading the assembly - True if the load was successful - - - - Count Test Cases using a filter - - The filter to apply - The number of test cases found - - - - Run selected tests and return a test result. The test is run synchronously, - and the listener interface is notified as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - - - - Run selected tests asynchronously, notifying the listener interface as it progresses. - - Interface to receive EventListener notifications. - A test filter used to select tests to be run - - RunAsync is a template method, calling various abstract and - virtual methods to be overridden by derived classes. - - - - - Wait for the ongoing run to complete. - - Time to wait in milliseconds - True if the run completed, otherwise false - - - - Initiate the test run. - - - - - Signal any test run that is in process to stop. Return without error if no test is running. - - If true, kill any test-running threads - - - - Create the initial TestExecutionContext used to run tests - - The ITestListener specified in the RunAsync call - - - - Handle the the Completed event for the top level work item - - - - - The tree of tests that was loaded by the builder - - - - - The test result, if a run has completed - - - - - Indicates whether a test is loaded - - - - - Indicates whether a test is running - - - - - Indicates whether a test run is complete - - - - - Our settings, specified when loading the assembly - - - - - The top level WorkItem created for the assembly as a whole - - - - - The TestExecutionContext for the top level WorkItem - - - - - The Assert class contains a collection of static methods that - implement the most common assertions used in NUnit. - - - - - Verifies that the first int is greater than the second - int. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first int is greater than the second - int. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - - - - We don't actually want any instances of this object, but some people - like to inherit from it to add other static methods. Hence, the - protected constructor disallows any instances of this object. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - The message to initialize the with. - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - - - - Throws an with the message and arguments - that are passed in. This is used by the other Assert functions. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This is used by the other Assert functions. - - The message to initialize the with. - - - - Throws an . - This is used by the other Assert functions. - - - - - Throws an with the message and arguments - that are passed in. This causes the test to be reported as ignored. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This causes the test to be reported as ignored. - - The message to initialize the with. - - - - Throws an . - This causes the test to be reported as ignored. - - - - - Throws an with the message and arguments - that are passed in. This causes the test to be reported as inconclusive. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This causes the test to be reported as inconclusive. - - The message to initialize the with. - - - - Throws an . - This causes the test to be reported as Inconclusive. - - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - - - - Helper for Assert.AreEqual(double expected, double actual, ...) - allowing code generation to work consistently. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestDelegate - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestDelegate - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - A TestDelegate - - - - Verifies that a delegate does not throw an exception - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate does not throw an exception. - - A TestDelegate - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - - This method is provided for use by VB developers needing to test - the value of properties with private setters. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Delegate used by tests that execute code and - capture any thrown exception. - - - - - AssertionHelper is an optional base class for user tests, - allowing the use of shorter ids for constraints and - asserts and avoiding conflict with the definition of - , from which it inherits much of its - behavior, in certain mock object frameworks. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that fails if the actual - value matches the pattern supplied as an argument. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically to - . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically to . - - The evaluated condition - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Returns a ListMapper based on a collection. - - The original collection - - - - - Provides static methods to express the assumptions - that must be met for a test to give a meaningful - result. If an assumption is not met, the test - should produce an inconclusive result. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the - method throws an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - A lambda that returns a Boolean - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Provides the Author of a test or test fixture. - - - - - PropertyAttribute is used to attach information to a test as a name/value pair.. - - - - - The abstract base class for all custom attributes defined by NUnit. - - - - - Default constructor - - - - - The IApplyToTest interface is implemented by self-applying - attributes that modify the state of a test in some way. - - - - - Modifies a test as defined for the specific attribute. - - The test to modify - - - - Construct a PropertyAttribute with a name and string value - - The name of the property - The property value - - - - Construct a PropertyAttribute with a name and int value - - The name of the property - The property value - - - - Construct a PropertyAttribute with a name and double value - - The name of the property - The property value - - - - Constructor for derived classes that set the - property dictionary directly. - - - - - Constructor for use by derived classes that use the - name of the type as the property name. Derived classes - must ensure that the Type of the property value is - a standard type supported by the BCL. Any custom - types will cause a serialization Exception when - in the client. - - - - - Modifies a test by adding properties to it. - - The test to modify - - - - Gets the property dictionary for this attribute - - - - - Initializes a new instance of the class. - - The name of the author. - - - - Initializes a new instance of the class. - - The name of the author. - The email address of the author. - - - - Attribute used to apply a category to a test - - - - - The name of the category - - - - - Construct attribute for a given category based on - a name. The name may not contain the characters ',', - '+', '-' or '!'. However, this is not checked in the - constructor since it would cause an error to arise at - as the test was loaded without giving a clear indication - of where the problem is located. The error is handled - in NUnitFramework.cs by marking the test as not - runnable. - - The name of the category - - - - Protected constructor uses the Type name as the name - of the category. - - - - - Modifies a test by adding a category to it. - - The test to modify - - - - The name of the category - - - - - Marks a test to use a combinatorial join of any argument - data provided. Since this is the default, the attribute is - optional. - - - - - Marks a test to use a particular CombiningStrategy to join - any parameter data provided. Since this is the default, the - attribute is optional. - - - - - The ITestBuilder interface is exposed by a class that knows how to - build one or more TestMethods from a MethodInfo. In general, it is exposed - by an attribute, which has additional information available to provide - the necessary test parameters to distinguish the test cases built. - - - - - Build one or more TestMethods from the provided MethodInfo. - - The method to be used as a test - The TestSuite to which the method will be added - A TestMethod object - - - - Construct a CombiningStrategyAttribute incorporating an object - that implements ICombiningStrategy. - - Combining strategy to be used - - - - Construct a CombiningStrategyAttribute incorporating an object - that implements ICombiningStrategy. This constructor is provided - for CLS compliance. - - Combining strategy to be used - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Modify the test by adding the name of the combining strategy - to the properties. - - The test to modify - - - - Default constructor - - - - - CultureAttribute is used to mark a test fixture or an - individual method as applying to a particular Culture only. - - - - - Abstract base for Attributes that are used to include tests - in the test run based on environmental settings. - - - - - Constructor with no included items specified, for use - with named property syntax. - - - - - Constructor taking one or more included items - - Comma-delimited list of included items - - - - Name of the item that is needed in order for - a test to run. Multiple items may be given, - separated by a comma. - - - - - Name of the item to be excluded. Multiple items - may be given, separated by a comma. - - - - - The reason for including or excluding the test - - - - - Constructor with no cultures specified, for use - with named property syntax. - - - - - Constructor taking one or more cultures - - Comma-deliminted list of cultures - - - - Causes a test to be skipped if this CultureAttribute is not satisfied. - - The test to modify - - - - Tests to determine if the current culture is supported - based on the properties of this attribute. - - True, if the current culture is supported - - - - Test to determine if the a particular culture or comma- - delimited set of cultures is in use. - - Name of the culture or comma-separated list of culture ids - True if the culture is in use on the system - - - - Test to determine if one of a collection of cultures - is being used currently. - - - - - - - The abstract base class for all data-providing attributes - defined by NUnit. Used to select all data sources for a - method, class or parameter. - - - - - Default constructor - - - - - Used to mark a field for use as a datapoint when executing a theory - within the same fixture that requires an argument of the field's Type. - - - - - Used to mark a field, property or method providing a set of datapoints to - be used in executing any theories within the same fixture that require an - argument of the Type provided. The data source may provide an array of - the required Type or an . - Synonymous with DatapointSourceAttribute. - - - - - Used to mark a field, property or method providing a set of datapoints to - be used in executing any theories within the same fixture that require an - argument of the Type provided. The data source may provide an array of - the required Type or an . - Synonymous with DatapointsAttribute. - - - - - Attribute used to provide descriptive text about a - test case or fixture. - - - - - Construct a description Attribute - - The text of the description - - - - ExplicitAttribute marks a test or test fixture so that it will - only be run if explicitly executed from the gui or command line - or if it is included by use of a filter. The test will not be - run simply because an enclosing suite is run. - - - - - Default constructor - - - - - Constructor with a reason - - The reason test is marked explicit - - - - Modifies a test by marking it as explicit. - - The test to modify - - - - Attribute used to mark a test that is to be ignored. - Ignored tests result in a warning message when the - tests are run. - - - - - Constructs the attribute giving a reason for ignoring the test - - The reason for ignoring the test - - - - Modifies a test by marking it as Ignored. - - The test to modify - - - - The date in the future to stop ignoring the test as a string in UTC time. - For example for a date and time, "2014-12-25 08:10:00Z" or for just a date, - "2014-12-25". If just a date is given, the Ignore will expire at midnight UTC. - - - Once the ignore until date has passed, the test will be marked - as runnable. Tests with an ignore until date will have an IgnoreUntilDate - property set which will appear in the test results. - - The string does not contain a valid string representation of a date and time. - - - - LevelOfParallelismAttribute is used to set the number of worker threads - that may be allocated by the framework for running tests. - - - - - Construct a LevelOfParallelismAttribute. - - The number of worker threads to be created by the framework. - - - - Summary description for MaxTimeAttribute. - - - - - Objects implementing this interface are used to wrap - the entire test, including SetUp and TearDown. - - - - - ICommandWrapper is implemented by attributes and other - objects able to wrap a TestCommand with another command. - - - Attributes or other objects should implement one of the - derived interfaces, rather than this one, since they - indicate in which part of the command chain the wrapper - should be applied. - - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - Construct a MaxTimeAttribute, given a time in milliseconds. - - The maximum elapsed time in milliseconds - - - - Attribute used to identify a method that is called once - to perform setup before any child tests are run. - - - - - Attribute used to identify a method that is called once - after all the child tests have run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Marks a test to use a pairwise join of any argument - data provided. Arguments will be combined in such a - way that all possible pairs of arguments are used. - - - - - Default constructor - - - - - ParallelizableAttribute is used to mark tests that may be run in parallel. - - - - - The IApplyToContext interface is implemented by attributes - that want to make changes to the execution context before - a test is run. - - - - - Apply changes to the execution context - - The execution context - - - - Construct a ParallelizableAttribute using default ParallelScope.Self. - - - - - Construct a ParallelizableAttribute with a specified scope. - - The ParallelScope associated with this attribute. - - - - Modify the context to be used for child tests - - The current TestExecutionContext - - - - The ParallelScope enumeration permits specifying the degree to - which a test and its descendants may be run in parallel. - - - - - No Parallelism is permitted - - - - - The test itself may be run in parallel with others at the same level - - - - - Descendants of the test may be run in parallel with one another - - - - - Descendants of the test down to the level of TestFixtures may be run in parallel - - - - - RandomAttribute is used to supply a set of random _values - to a single parameter of a parameterized test. - - - - - The IParameterDataSource interface is implemented by types - that can provide data for a test method parameter. - - - - - Gets an enumeration of data items for use as arguments - for a test method parameter. - - The parameter for which data is needed - An enumeration containing individual data items - - - - Construct a random set of values appropriate for the Type of the - parameter on which the attribute appears, specifying only the count. - - - - - - Construct a set of ints within a specified range - - - - - Construct a set of unsigned ints within a specified range - - - - - Construct a set of longs within a specified range - - - - - Construct a set of unsigned longs within a specified range - - - - - Construct a set of shorts within a specified range - - - - - Construct a set of unsigned shorts within a specified range - - - - - Construct a set of doubles within a specified range - - - - - Construct a set of floats within a specified range - - - - - Construct a set of bytes within a specified range - - - - - Construct a set of sbytes within a specified range - - - - - Get the collection of _values to be used as arguments. - - - - - RangeAttribute is used to supply a range of _values to an - individual parameter of a parameterized test. - - - - - ValuesAttribute is used to provide literal arguments for - an individual parameter of a test. - - - - - The collection of data to be returned. Must - be set by any derived attribute classes. - We use an object[] so that the individual - elements may have their type changed in GetData - if necessary - - - - - Constructs for use with an Enum parameter. Will pass every enum - value in to the test. - - - - - Construct with one argument - - - - - - Construct with two arguments - - - - - - - Construct with three arguments - - - - - - - - Construct with an array of arguments - - - - - - Get the collection of _values to be used as arguments - - - - - Construct a range of ints using default step of 1 - - - - - - - Construct a range of ints specifying the step size - - - - - - - - Construct a range of unsigned ints using default step of 1 - - - - - - - Construct a range of unsigned ints specifying the step size - - - - - - - - Construct a range of longs using a default step of 1 - - - - - - - Construct a range of longs - - - - - - - - Construct a range of unsigned longs using default step of 1 - - - - - - - Construct a range of unsigned longs specifying the step size - - - - - - - - Construct a range of doubles - - - - - - - - Construct a range of floats - - - - - - - - RepeatAttribute may be applied to test case in order - to run it multiple times. - - - - - Construct a RepeatAttribute - - The number of times to run the test - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - The test command for the RepeatAttribute - - - - - TODO: Documentation needed for class - - - - - TestCommand is the abstract base class for all test commands - in the framework. A TestCommand represents a single stage in - the execution of a test, e.g.: SetUp/TearDown, checking for - Timeout, verifying the returned result from a method, etc. - - TestCommands may decorate other test commands so that the - execution of a lower-level command is nested within that - of a higher level command. All nested commands are executed - synchronously, as a single unit. Scheduling test execution - on separate threads is handled at a higher level, using the - task dispatcher. - - - - - Construct a TestCommand for a test. - - The test to be executed - - - - Runs the test in a specified context, returning a TestResult. - - The TestExecutionContext to be used for running the test. - A TestResult - - - - Gets the test associated with this command. - - - - TODO: Documentation needed for field - - - - TODO: Documentation needed for constructor - - - - - - Initializes a new instance of the class. - - The inner command. - The number of repetitions - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - RepeatAttribute may be applied to test case in order - to run it multiple times. - - - - - Construct a RepeatAttribute - - The number of times to run the test - - - - Wrap a command and return the result. - - The command to be wrapped - The wrapped command - - - - The test command for the RetryAttribute - - - - - Initializes a new instance of the class. - - The inner command. - The number of repetitions - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - Marks a test to use a Sequential join of any argument - data provided. Arguments will be combined into test cases, - taking the next value of each argument until all are used. - - - - - Default constructor - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - SetUpFixtureAttribute is used to identify a SetUpFixture - - - - - The IFixtureBuilder interface is exposed by a class that knows how to - build a TestFixture from one or more Types. In general, it is exposed - by an attribute, but may be implemented in a helper class used by the - attribute in some cases. - - - - - Build one or more TestFixtures from type provided. At least one - non-null TestSuite must always be returned, since the method is - generally called because the user has marked the target class as - a fixture. If something prevents the fixture from being used, it - will be returned nonetheless, labelled as non-runnable. - - The type info of the fixture to be used. - A TestSuite object or one derived from TestSuite. - - - - Build a SetUpFixture from type provided. Normally called for a Type - on which the attribute has been placed. - - The type info of the fixture to be used. - A SetUpFixture object as a TestSuite. - - - - Attribute used to identify a method that is called - immediately after each test is run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Provide actions to execute before and after tests. - - - - - When implemented by an attribute, this interface implemented to provide actions to execute before and after tests. - - - - - Executed before each test is run - - The test that is going to be run. - - - - Executed after each test is run - - The test that has just been run. - - - - Provides the target for the action attribute - - The target for the action attribute - - - - Executed before each test is run - - The test that is going to be run. - - - - Executed after each test is run - - The test that has just been run. - - - - Provides the target for the action attribute - - - - - Adding this attribute to a method within a - class makes the method callable from the NUnit test runner. There is a property - called Description which is optional which you can provide a more detailed test - description. This class cannot be inherited. - - - - [TestFixture] - public class Fixture - { - [Test] - public void MethodToTest() - {} - - [Test(Description = "more detailed description")] - public void TestDescriptionMethod() - {} - } - - - - - - The ISimpleTestBuilder interface is exposed by a class that knows how to - build a single TestMethod from a suitable MethodInfo Types. In general, - it is exposed by an attribute, but may be implemented in a helper class - used by the attribute in some cases. - - - - - Build a TestMethod from the provided MethodInfo. - - The method to be used as a test - The TestSuite to which the method will be added - A TestMethod object - - - - IImplyFixture is an empty marker interface used by attributes like - TestAttribute that cause the class where they are used to be treated - as a TestFixture even without a TestFixtureAttribute. - - Marker interfaces are not usually considered a good practice, but - we use it here to avoid cluttering the attribute hierarchy with - classes that don't contain any extra implementation. - - - - - Modifies a test by adding a description, if not already set. - - The test to modify - - - - Construct a TestMethod from a given method. - - The method for which a test is to be constructed. - The suite to which the test will be added. - A TestMethod - - - - Descriptive text for this test - - - - - The author of this test - - - - - The type that this test is testing - - - - - Gets or sets the expected result. - - The result. - - - - Returns true if an expected result has been set - - - - - TestCaseAttribute is used to mark parameterized test cases - and provide them with their arguments. - - - - - The ITestCaseData interface is implemented by a class - that is able to return complete testcases for use by - a parameterized test method. - - - - - The ITestData interface is implemented by a class that - represents a single instance of a parameterized test. - - - - - Gets the name to be used for the test - - - - - Gets the RunState for this test case. - - - - - Gets the argument list to be provided to the test - - - - - Gets the property dictionary for the test case - - - - - Gets the expected result of the test case - - - - - Returns true if an expected result has been set - - - - - Construct a TestCaseAttribute with a list of arguments. - This constructor is not CLS-Compliant - - - - - - Construct a TestCaseAttribute with a single argument - - - - - - Construct a TestCaseAttribute with a two arguments - - - - - - - Construct a TestCaseAttribute with a three arguments - - - - - - - - Performs several special conversions allowed by NUnit in order to - permit arguments with types that cannot be used in the constructor - of an Attribute such as TestCaseAttribute or to simplify their use. - - The arguments to be converted - The ParameterInfo array for the method - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Gets or sets the name of the test. - - The name of the test. - - - - Gets or sets the RunState of this test case. - - - - - Gets the list of arguments to a test case - - - - - Gets the properties of the test case - - - - - Gets or sets the expected result. - - The result. - - - - Returns true if the expected result has been set - - - - - Gets or sets the description. - - The description. - - - - The author of this test - - - - - The type that this test is testing - - - - - Gets or sets the reason for ignoring the test - - - - - Gets or sets a value indicating whether this is explicit. - - - true if explicit; otherwise, false. - - - - - Gets or sets the reason for not running the test. - - The reason. - - - - Gets or sets the ignore reason. When set to a non-null - non-empty value, the test is marked as ignored. - - The ignore reason. - - - - Gets and sets the category for this test case. - May be a comma-separated list of categories. - - - - - TestCaseSourceAttribute indicates the source to be used to - provide test cases for a test method. - - - - - Construct with the name of the method, property or field that will provide data - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Construct with a Type - - The type that will provide data - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The IMethod for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - Returns a set of ITestCaseDataItems for use as arguments - to a parameterized test method. - - The method for which data is needed. - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - Gets or sets the category associated with every fixture created from - this attribute. May be a single category or a comma-separated list. - - - - - TestFixtureAttribute is used to mark a class that represents a TestFixture. - - - - - The ITestCaseData interface is implemented by a class - that is able to return the data required to create an - instance of a parameterized test fixture. - - - - - Get the TypeArgs if separately set - - - - - Default constructor - - - - - Construct with a object[] representing a set of arguments. - In .NET 2.0, the arguments may later be separated into - type arguments and constructor arguments. - - - - - - Build a fixture from type provided. Normally called for a Type - on which the attribute has been placed. - - The type info of the fixture to be used. - A an IEnumerable holding one TestFixture object. - - - - Gets or sets the name of the test. - - The name of the test. - - - - Gets or sets the RunState of this test fixture. - - - - - The arguments originally provided to the attribute - - - - - Properties pertaining to this fixture - - - - - Get or set the type arguments. If not set - explicitly, any leading arguments that are - Types are taken as type arguments. - - - - - Descriptive text for this fixture - - - - - The author of this fixture - - - - - The type that this fixture is testing - - - - - Gets or sets the ignore reason. May set RunState as a side effect. - - The ignore reason. - - - - Gets or sets the reason for not running the fixture. - - The reason. - - - - Gets or sets the ignore reason. When set to a non-null - non-empty value, the test is marked as ignored. - - The ignore reason. - - - - Gets or sets a value indicating whether this is explicit. - - - true if explicit; otherwise, false. - - - - - Gets and sets the category for this fixture. - May be a comma-separated list of categories. - - - - - Attribute used to identify a method that is - called before any tests in a fixture are run. - - - - - TestCaseSourceAttribute indicates the source to be used to - provide test fixture instances for a test class. - - - - - Error message string is public so the tests can use it - - - - - Construct with the name of the method, property or field that will provide data - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Construct with a Type - - The type that will provide data - - - - Construct one or more TestFixtures from a given Type, - using available parameter data. - - The TypeInfo for which fixures are to be constructed. - One or more TestFixtures as TestSuite - - - - Returns a set of ITestFixtureData items for use as arguments - to a parameterized test fixture. - - The type for which data is needed. - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - Gets or sets the category associated with every fixture created from - this attribute. May be a single category or a comma-separated list. - - - - - Attribute used to identify a method that is called after - all the tests in a fixture have run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Indicates which class the test or test fixture is testing - - - - - Initializes a new instance of the class. - - The type that is being tested. - - - - Initializes a new instance of the class. - - The type that is being tested. - - - - Adding this attribute to a method within a - class makes the method callable from the NUnit test runner. There is a property - called Description which is optional which you can provide a more detailed test - description. This class cannot be inherited. - - - - [TestFixture] - public class Fixture - { - [Test] - public void MethodToTest() - {} - - [Test(Description = "more detailed description")] - public void TestDescriptionMethod() - {} - } - - - - - - Construct one or more TestMethods from a given MethodInfo, - using available parameter data. - - The MethodInfo for which tests are to be constructed. - The suite to which the tests will be added. - One or more TestMethods - - - - ValueSourceAttribute indicates the source to be used to - provide data for one parameter of a test method. - - - - - Construct with the name of the factory - for use with languages - that don't support params arrays. - - The name of a static method, property or field that will provide data. - - - - Construct with a Type and name - for use with languages - that don't support params arrays. - - The Type that will provide data - The name of a static method, property or field that will provide data. - - - - Gets an enumeration of data items for use as arguments - for a test method parameter. - - The parameter for which data is needed - - An enumeration containing individual data items - - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - A set of Assert methods operating on one or more collections - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable containing objects to be considered - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the superset does not contain the subset - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - - - - Asserts that the superset does not contain the subset - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the superset contains the subset. - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - - - - Asserts that the superset contains the subset. - - The IEnumerable subset to be considered - The IEnumerable superset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the subset does not contain the superset - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - - - - Asserts that the subset does not contain the superset - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that the subset contains the superset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - - - - Asserts that the subset contains the superset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array,list or other collection is empty - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array,list or other collection is empty - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - - - - Provides a platform-independent methods for getting attributes - for use by AttributeConstraint and AttributeExistsConstraint. - - - - - Gets the custom attributes from the given object. - - Portable libraries do not have an ICustomAttributeProvider, so we need to cast to each of - it's direct subtypes and try to get attributes off those instead. - The actual. - Type of the attribute. - if set to true [inherit]. - A list of the given attribute on the given object. - - - - Specifies flags that control binding and the way in which the search for members - and types is conducted by reflection. - - - - - Specifies no binding flag. - - - - - Specifies that only members declared at the level of the supplied type's hierarchy - should be considered. Inherited members are not considered. - - - - - Specifies that instance members are to be included in the search. - - - - - Specifies that static members are to be included in the search. - - - - - Specifies that public members are to be included in the search. - - - - - Specifies that non-public members are to be included in the search. - - - - - Specifies that public and protected static members up the hierarchy should be - returned. Private static members in inherited classes are not returned. Static - members include fields, methods, events, and properties. Nested types are not - returned. - - - - - A shim of the .NET interface for platforms that do not support it. - Used to indicate that a control can be the target of a callback event on the server. - - - - - Processes a callback event that targets a control. - - - - - - Returns the results of a callback event that targets a control. - - - - - - Some path based methods that we need even in the Portable framework which - does not have the System.IO.Path class - - - - - Windows directory separator - - - - - Alternate directory separator - - - - - A volume separator character. - - - - - Get the file name and extension of the specified path string. - - The path string from which to obtain the file name and extension. - The filename as a . If the last character of is a directory or volume separator character, this method returns . If is null, this method returns null. - - - - Provides NUnit specific extensions to aid in Reflection - across multiple frameworks - - - This version of the class allows direct calls on Type on - those platforms that would normally require use of - GetTypeInfo(). - - - - - Returns an array of generic arguments for the give type - - - - - - - Gets the constructor with the given parameter types - - - - - - - - Gets the constructors for a type - - - - - - - - - - - - - - - - - - - - - - - Gets declared or inherited interfaces on this type - - - - - - - Gets the member on a given type by name. BindingFlags ARE IGNORED. - - - - - - - - - Gets all members on a given type. BindingFlags ARE IGNORED. - - - - - - - - Gets field of the given name on the type - - - - - - - - Gets property of the given name on the type - - - - - - - - Gets property of the given name on the type - - - - - - - - - Gets the method with the given name and parameter list - - - - - - - - Gets the method with the given name and parameter list - - - - - - - - - Gets the method with the given name and parameter list - - - - - - - - - Gets public methods on the given type - - - - - - - Gets methods on a type - - - - - - - - Determines if one type can be implicitly converted from another - - - - - - - - Extensions to the various MemberInfo derived classes - - - - - Returns the get method for the given property - - - - - - - - Returns an array of custom attributes of the specified type applied to this member - - Portable throws an argument exception if T does not - derive from Attribute. NUnit uses interfaces to find attributes, thus - this method - - - - Returns an array of custom attributes of the specified type applied to this parameter - - - - - Returns an array of custom attributes of the specified type applied to this assembly - - - - - Extensions for Assembly that are not available in portable - - - - - DNX does not have a version of GetCustomAttributes on Assembly that takes an inherit - parameter since it doesn't make sense on Assemblies. This version just ignores the - inherit parameter. - - The assembly - The type of attribute you are looking for - Ignored - - - - - Gets the types in a given assembly - - - - - - - A shim of the .NET attribute for platforms that do not support it. - - - - - This class is a System.Diagnostics.Stopwatch on operating systems that support it. On those that don't, - it replicates the functionality at the resolution supported. - - - - - Gets the current number of ticks in the timer mechanism. - - - If the Stopwatch class uses a high-resolution performance counter, GetTimestamp returns the current - value of that counter. If the Stopwatch class uses the system timer, GetTimestamp returns the current - DateTime.Ticks property of the DateTime.Now instance. - - A long integer representing the tick counter value of the underlying timer mechanism. - - - - Stops time interval measurement and resets the elapsed time to zero. - - - - - Starts, or resumes, measuring elapsed time for an interval. - - - - - Initializes a new Stopwatch instance, sets the elapsed time property to zero, and starts measuring elapsed time. - - A Stopwatch that has just begun measuring elapsed time. - - - - Stops measuring elapsed time for an interval. - - - - - Returns a string that represents the current object. - - - A string that represents the current object. - - - - - Gets the total elapsed time measured by the current instance, in milliseconds. - - - - - Gets a value indicating whether the Stopwatch timer is running. - - - - - Gets the frequency of the timer as the number of ticks per second. - - - - - Indicates whether the timer is based on a high-resolution performance counter. - - - - - AllItemsConstraint applies another constraint to each - item in a collection, succeeding if they all succeed. - - - - - Abstract base class used for prefixes - - - - - The Constraint class is the base of all built-in constraints - within NUnit. It provides the operator overloads used to combine - constraints. - - - - - Interface for all constraints - - - - - The IResolveConstraint interface is implemented by all - complete and resolvable constraints and expressions. - - - - - Return the top-level constraint for this expression - - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - The default implementation simply dereferences the value but - derived classes may override it to provide for delayed processing. - - A reference to the value to be tested - A ConstraintResult - - - - The display name of this Constraint for use by ToString(). - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Arguments provided to this Constraint, for use in - formatting the description. - - - - - The ConstraintBuilder holding this constraint - - - - - Construct a constraint with optional arguments - - Arguments to be saved - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Test whether the constraint is satisfied by a given reference. - The default implementation simply dereferences the value but - derived classes may override it to provide for delayed processing. - - A reference to the value to be tested - A ConstraintResult - - - - Default override of ToString returns the constraint DisplayName - followed by any arguments within angle brackets. - - - - - - Returns the string representation of this constraint - - - - - This operator creates a constraint that is satisfied only if both - argument constraints are satisfied. - - - - - This operator creates a constraint that is satisfied if either - of the argument constraints is satisfied. - - - - - This operator creates a constraint that is satisfied if the - argument constraint is not satisfied. - - - - - Resolves any pending operators and returns the resolved constraint. - - - - - The display name of this Constraint for use by ToString(). - The default value is the name of the constraint with - trailing "Constraint" removed. Derived classes may set - this to another name in their constructors. - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Arguments provided to this Constraint, for use in - formatting the description. - - - - - The ConstraintBuilder holding this constraint - - - - - Returns a ConstraintExpression by appending And - to the current constraint. - - - - - Returns a ConstraintExpression by appending And - to the current constraint. - - - - - Returns a ConstraintExpression by appending Or - to the current constraint. - - - - - The base constraint - - - - - Prefix used in forming the constraint description - - - - - Construct given a base constraint - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Construct an AllItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - failing if any item fails. - - - - - - - AndConstraint succeeds only if both members succeed. - - - - - BinaryConstraint is the abstract base of all constraints - that combine two other constraints in some fashion. - - - - - The first constraint being combined - - - - - The second constraint being combined - - - - - Construct a BinaryConstraint from two other constraints - - The first constraint - The second constraint - - - - Create an AndConstraint from two other constraints - - The first constraint - The second constraint - - - - Apply both member constraints to an actual value, succeeding - succeeding only if both of them succeed. - - The actual value - True if the constraints both succeeded - - - - Gets text describing a constraint - - - - - Contain the result of matching a against an actual value. - - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - The status of the new ConstraintResult. - - - - Constructs a for a particular . - - The Constraint to which this result applies. - The actual value to which the Constraint was applied. - If true, applies a status of Success to the result, otherwise Failure. - - - - Write the failure message to the MessageWriter provided - as an argument. The default implementation simply passes - the result and the actual value to the writer, which - then displays the constraint description and the value. - - Constraints that need to provide additional details, - such as where the error occured can override this. - - The MessageWriter on which to display the message - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - The actual value that was passed to the method. - - - - - Gets and sets the ResultStatus for this result. - - - - - True if actual value meets the Constraint criteria otherwise false. - - - - - Display friendly name of the constraint. - - - - - Description of the constraint may be affected by the state the constraint had - when was performed against the actual value. - - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - AssignableFromConstraint is used to test that an object - can be assigned from a given Type. - - - - - TypeConstraint is the abstract base for constraints - that take a Type as their expected value. - - - - - The expected Type used by the constraint - - - - - The type of the actual argument to which the constraint was applied - - - - - Construct a TypeConstraint for a given Type - - The expected type for the constraint - Prefix used in forming the constraint description - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - Construct an AssignableFromConstraint for the type provided - - - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - AssignableToConstraint is used to test that an object - can be assigned to a given Type. - - - - - Construct an AssignableToConstraint for the type provided - - - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - AttributeConstraint tests that a specified attribute is present - on a Type or other provider and that the value of the attribute - satisfies some other constraint. - - - - - Constructs an AttributeConstraint for a specified attribute - Type and base constraint. - - - - - - - Determines whether the Type or other provider has the - expected attribute and if its value matches the - additional constraint specified. - - - - - Returns a string representation of the constraint. - - - - - AttributeExistsConstraint tests for the presence of a - specified attribute on a Type. - - - - - Constructs an AttributeExistsConstraint for a specific attribute Type - - - - - - Tests whether the object provides the expected attribute. - - A Type, MethodInfo, or other ICustomAttributeProvider - True if the expected attribute is present, otherwise false - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionConstraint is the abstract base class for - constraints that operate on collections. - - - - - Construct an empty CollectionConstraint - - - - - Construct a CollectionConstraint - - - - - - Determines whether the specified enumerable is empty. - - The enumerable. - - true if the specified enumerable is empty; otherwise, false. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Protected method to be implemented by derived classes - - - - - - - CollectionContainsConstraint is used to test whether a collection - contains an expected object as a member. - - - - - CollectionItemsEqualConstraint is the abstract base class for all - collection constraints that apply some notion of item equality - as a part of their operation. - - - - - Construct an empty CollectionConstraint - - - - - Construct a CollectionConstraint - - - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Compares two collection members for equality - - - - - Return a new CollectionTally for use in making tests - - The collection to be included in the tally - - - - Flag the constraint to ignore case and return self. - - - - - Construct a CollectionContainsConstraint - - - - - - Test whether the expected item is contained in the collection - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Gets the expected object - - - - - CollectionEquivalentConstraint is used to determine whether two - collections are equivalent. - - - - - Construct a CollectionEquivalentConstraint - - - - - - Test whether two collections are equivalent - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionOrderedConstraint is used to test whether a collection is ordered. - - - - - Construct a CollectionOrderedConstraint - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use a and returns self. - - - - - Modifies the constraint to test ordering by the value of - a specified property and returns self. - - - - - Test whether the collection is ordered - - - - - - - Returns the string representation of the constraint. - - - - - - If used performs a reverse comparison - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionSubsetConstraint is used to determine whether - one collection is a subset of another - - - - - Construct a CollectionSubsetConstraint - - The collection that the actual value is expected to be a subset of - - - - Test whether the actual collection is a subset of - the expected collection provided. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionSupersetConstraint is used to determine whether - one collection is a superset of another - - - - - Construct a CollectionSupersetConstraint - - The collection that the actual value is expected to be a superset of - - - - Test whether the actual collection is a superset of - the expected collection provided. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - CollectionTally counts (tallies) the number of - occurrences of each object in one or more enumerations. - - - - - Construct a CollectionTally object from a comparer and a collection - - - - - Try to remove an object from the tally - - The object to remove - True if successful, false if the object was not found - - - - Try to remove a set of objects from the tally - - The objects to remove - True if successful, false if any object was not found - - - - The number of objects remaining in the tally - - - - - ComparisonAdapter class centralizes all comparisons of - _values in NUnit, adapting to the use of any provided - , - or . - - - - - Returns a ComparisonAdapter that wraps an - - - - - Returns a ComparisonAdapter that wraps an - - - - - Returns a ComparisonAdapter that wraps a - - - - - Compares two objects - - - - - Gets the default ComparisonAdapter, which wraps an - NUnitComparer object. - - - - - Construct a ComparisonAdapter for an - - - - - Compares two objects - - - - - - - - Construct a default ComparisonAdapter - - - - - ComparerAdapter extends and - allows use of an or - to actually perform the comparison. - - - - - Construct a ComparisonAdapter for an - - - - - Compare a Type T to an object - - - - - Construct a ComparisonAdapter for a - - - - - Compare a Type T to an object - - - - - Abstract base class for constraints that compare _values to - determine if one is greater than, equal to or less than - the other. - - - - - The value against which a comparison is to be made - - - - - If true, less than returns success - - - - - if true, equal returns success - - - - - if true, greater than returns success - - - - - ComparisonAdapter to be used in making the comparison - - - - - Initializes a new instance of the class. - - The value against which to make a comparison. - if set to true less succeeds. - if set to true equal succeeds. - if set to true greater succeeds. - String used in describing the constraint. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Modifies the constraint to use an and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Modifies the constraint to use an and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Modifies the constraint to use a and returns self - - The comparer used for comparison tests - A constraint modified to use the given comparer - - - - Delegate used to delay evaluation of the actual value - to be used in evaluating a constraint - - - - - ConstraintBuilder maintains the stacks that are used in - processing a ConstraintExpression. An OperatorStack - is used to hold operators that are waiting for their - operands to be reorganized. a ConstraintStack holds - input constraints as well as the results of each - operator applied. - - - - - Initializes a new instance of the class. - - - - - Appends the specified operator to the expression by first - reducing the operator stack and then pushing the new - operator on the stack. - - The operator to push. - - - - Appends the specified constraint to the expression by pushing - it on the constraint stack. - - The constraint to push. - - - - Sets the top operator right context. - - The right context. - - - - Reduces the operator stack until the topmost item - precedence is greater than or equal to the target precedence. - - The target precedence. - - - - Resolves this instance, returning a Constraint. If the Builder - is not currently in a resolvable state, an exception is thrown. - - The resolved constraint - - - - Gets a value indicating whether this instance is resolvable. - - - true if this instance is resolvable; otherwise, false. - - - - - OperatorStack is a type-safe stack for holding ConstraintOperators - - - - - Initializes a new instance of the class. - - The ConstraintBuilder using this stack. - - - - Pushes the specified operator onto the stack. - - The operator to put onto the stack. - - - - Pops the topmost operator from the stack. - - The topmost operator on the stack - - - - Gets a value indicating whether this is empty. - - true if empty; otherwise, false. - - - - Gets the topmost operator without modifying the stack. - - - - - ConstraintStack is a type-safe stack for holding Constraints - - - - - Initializes a new instance of the class. - - The ConstraintBuilder using this stack. - - - - Pushes the specified constraint. As a side effect, - the constraint's Builder field is set to the - ConstraintBuilder owning this stack. - - The constraint to put onto the stack - - - - Pops this topmost constraint from the stack. - As a side effect, the constraint's Builder - field is set to null. - - The topmost contraint on the stack - - - - Gets a value indicating whether this is empty. - - true if empty; otherwise, false. - - - - ConstraintExpression represents a compound constraint in the - process of being constructed from a series of syntactic elements. - - Individual elements are appended to the expression as they are - reorganized. When a constraint is appended, it is returned as the - value of the operation so that modifiers may be applied. However, - any partially built expression is attached to the constraint for - later resolution. When an operator is appended, the partial - expression is returned. If it's a self-resolving operator, then - a ResolvableConstraintExpression is returned. - - - - - The ConstraintBuilder holding the elements recognized so far - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class passing in a ConstraintBuilder, which may be pre-populated. - - The builder. - - - - Returns a string representation of the expression as it - currently stands. This should only be used for testing, - since it has the side-effect of resolving the expression. - - - - - - Appends an operator to the expression and returns the - resulting expression itself. - - - - - Appends a self-resolving operator to the expression and - returns a new ResolvableConstraintExpression. - - - - - Appends a constraint to the expression and returns that - constraint, which is associated with the current state - of the expression being built. Note that the constraint - is not reduced at this time. For example, if there - is a NotOperator on the stack we don't reduce and - return a NotConstraint. The original constraint must - be returned because it may support modifiers that - are yet to be applied. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns the constraint provided as an argument - used to allow custom - custom constraints to easily participate in the syntax. - - - - - Returns the constraint provided as an argument - used to allow custom - custom constraints to easily participate in the syntax. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - With is currently a NOP - reserved for future use. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - ConstraintStatus represents the status of a ConstraintResult - returned by a Constraint being applied to an actual value. - - - - - The status has not yet been set - - - - - The constraint succeeded - - - - - The constraint failed - - - - - An error occured in applying the constraint (reserved for future use) - - - - - ContainsConstraint tests a whether a string contains a substring - or a collection contains an object. It postpones the decision of - which test to use until the type of the actual argument is known. - This allows testing whether a string is contained in a collection - or as a substring of another string using the same syntax. - - - - - Initializes a new instance of the class. - - The _expected. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Flag the constraint to ignore case and return self. - - - - - DictionaryContainsKeyConstraint is used to test whether a dictionary - contains an expected object as a key. - - - - - Construct a DictionaryContainsKeyConstraint - - - - - - Test whether the expected key is contained in the dictionary - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - DictionaryContainsValueConstraint is used to test whether a dictionary - contains an expected object as a value. - - - - - Construct a DictionaryContainsValueConstraint - - - - - - Test whether the expected value is contained in the dictionary - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyCollectionConstraint tests whether a collection is empty. - - - - - Check that the collection is empty - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyConstraint tests a whether a string or collection is empty, - postponing the decision about which test is applied until the - type of the actual argument is known. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EmptyStringConstraint tests whether a string is empty. - - - - - StringConstraint is the abstract base for constraints - that operate on strings. It supports the IgnoreCase - modifier for string operations. - - - - - The expected value - - - - - Indicates whether tests should be case-insensitive - - - - - Description of this constraint - - - - - Constructs a StringConstraint without an expected value - - - - - Constructs a StringConstraint given an expected value - - The expected value - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Test whether the constraint is satisfied by a given string - - The string to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Modify the constraint to ignore case in matching. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - EndsWithConstraint can test whether a string ends - with an expected substring. - - - - - Initializes a new instance of the class. - - The expected string - - - - Test whether the constraint is matched by the actual value. - This is a template method, which calls the IsMatch method - of the derived class. - - - - - - - EqualConstraint is able to compare an actual value with the - expected value provided in its constructor. Two objects are - considered equal if both are null, or if both have the same - value. NUnit has special semantics for some object types. - - - - - NUnitEqualityComparer used to test equality. - - - - - Initializes a new instance of the class. - - The expected value. - - - - Flag the constraint to use a tolerance when determining equality. - - Tolerance value to be used - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Gets the tolerance for this comparison. - - - The tolerance. - - - - - Gets a value indicating whether to compare case insensitive. - - - true if comparing case insensitive; otherwise, false. - - - - - Gets a value indicating whether or not to clip strings. - - - true if set to clip strings otherwise, false. - - - - - Gets the failure points. - - - The failure points. - - - - - Flag the constraint to ignore case and return self. - - - - - Flag the constraint to suppress string clipping - and return self. - - - - - Flag the constraint to compare arrays as collections - and return self. - - - - - Flags the constraint to include - property in comparison of two values. - - - Using this modifier does not allow to use the - constraint modifier. - - - - - Switches the .Within() modifier to interpret its tolerance as - a distance in representable _values (see remarks). - - Self. - - Ulp stands for "unit in the last place" and describes the minimum - amount a given value can change. For any integers, an ulp is 1 whole - digit. For floating point _values, the accuracy of which is better - for smaller numbers and worse for larger numbers, an ulp depends - on the size of the number. Using ulps for comparison of floating - point results instead of fixed tolerances is safer because it will - automatically compensate for the added inaccuracy of larger numbers. - - - - - Switches the .Within() modifier to interpret its tolerance as - a percentage that the actual _values is allowed to deviate from - the expected value. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in days. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in hours. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in minutes. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in seconds. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in milliseconds. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in clock ticks. - - Self - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - The EqualConstraintResult class is tailored for formatting - and displaying the result of an EqualConstraint. - - - - - Construct an EqualConstraintResult - - - - - Write a failure message. Overridden to provide custom - failure messages for EqualConstraint. - - The MessageWriter to write to - - - - Display the failure information for two collections that did not match. - - The MessageWriter on which to display - The expected collection. - The actual collection - The depth of this failure in a set of nested collections - - - - Displays a single line showing the types and sizes of the expected - and actual collections or arrays. If both are identical, the value is - only shown once. - - The MessageWriter on which to display - The expected collection or array - The actual collection or array - The indentation level for the message line - - - - Displays a single line showing the point in the expected and actual - arrays at which the comparison failed. If the arrays have different - structures or dimensions, both _values are shown. - - The MessageWriter on which to display - The expected array - The actual array - Index of the failure point in the underlying collections - The indentation level for the message line - - - - Display the failure information for two IEnumerables that did not match. - - The MessageWriter on which to display - The expected enumeration. - The actual enumeration - The depth of this failure in a set of nested collections - - - - EqualityAdapter class handles all equality comparisons - that use an , - or a . - - - - - Compares two objects, returning true if they are equal - - - - - Returns true if the two objects can be compared by this adapter. - The base adapter cannot handle IEnumerables except for strings. - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps an . - - - - - Returns an that wraps a . - - - - - that wraps an . - - - - - Returns true if the two objects can be compared by this adapter. - Generic adapter requires objects of the specified type. - - - - - that wraps an . - - - - - ExactCountConstraint applies another constraint to each - item in a collection, succeeding only if a specified - number of items succeed. - - - - - Construct an ExactCountConstraint on top of an existing constraint - - - - - - - Apply the item constraint to each item in the collection, - succeeding only if the expected number of items pass. - - - - - - - ExactTypeConstraint is used to test that an object - is of the exact type provided in the constructor - - - - - Construct an ExactTypeConstraint for a given Type - - The expected Type. - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - ExceptionTypeConstraint is a special version of ExactTypeConstraint - used to provided detailed info about the exception thrown in - an error message. - - - - - Constructs an ExceptionTypeConstraint - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - FalseConstraint tests that the actual value is false - - - - - Initializes a new instance of the class. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - Helper routines for working with floating point numbers - - - The floating point comparison code is based on this excellent article: - http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm - - - "ULP" means Unit in the Last Place and in the context of this library refers to - the distance between two adjacent floating point numbers. IEEE floating point - numbers can only represent a finite subset of natural numbers, with greater - accuracy for smaller numbers and lower accuracy for very large numbers. - - - If a comparison is allowed "2 ulps" of deviation, that means the _values are - allowed to deviate by up to 2 adjacent floating point _values, which might be - as low as 0.0000001 for small numbers or as high as 10.0 for large numbers. - - - - - Compares two floating point _values for equality - First floating point value to be compared - Second floating point value t be compared - - Maximum number of representable floating point _values that are allowed to - be between the left and the right floating point _values - - True if both numbers are equal or close to being equal - - - Floating point _values can only represent a finite subset of natural numbers. - For example, the _values 2.00000000 and 2.00000024 can be stored in a float, - but nothing inbetween them. - - - This comparison will count how many possible floating point _values are between - the left and the right number. If the number of possible _values between both - numbers is less than or equal to maxUlps, then the numbers are considered as - being equal. - - - Implementation partially follows the code outlined here: - http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ - - - - - Compares two double precision floating point _values for equality - First double precision floating point value to be compared - Second double precision floating point value t be compared - - Maximum number of representable double precision floating point _values that are - allowed to be between the left and the right double precision floating point _values - - True if both numbers are equal or close to being equal - - - Double precision floating point _values can only represent a limited series of - natural numbers. For example, the _values 2.0000000000000000 and 2.0000000000000004 - can be stored in a double, but nothing inbetween them. - - - This comparison will count how many possible double precision floating point - _values are between the left and the right number. If the number of possible - _values between both numbers is less than or equal to maxUlps, then the numbers - are considered as being equal. - - - Implementation partially follows the code outlined here: - http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ - - - - - - Reinterprets the memory contents of a floating point value as an integer value - - - Floating point value whose memory contents to reinterpret - - - The memory contents of the floating point value interpreted as an integer - - - - - Reinterprets the memory contents of a double precision floating point - value as an integer value - - - Double precision floating point value whose memory contents to reinterpret - - - The memory contents of the double precision floating point value - interpreted as an integer - - - - - Reinterprets the memory contents of an integer as a floating point value - - Integer value whose memory contents to reinterpret - - The memory contents of the integer value interpreted as a floating point value - - - - - Reinterprets the memory contents of an integer value as a double precision - floating point value - - Integer whose memory contents to reinterpret - - The memory contents of the integer interpreted as a double precision - floating point value - - - - Union of a floating point variable and an integer - - - The union's value as a floating point variable - - - The union's value as an integer - - - The union's value as an unsigned integer - - - Union of a double precision floating point variable and a long - - - The union's value as a double precision floating point variable - - - The union's value as a long - - - The union's value as an unsigned long - - - - Tests whether a value is greater than the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - Tests whether a value is greater than or equal to the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - InstanceOfTypeConstraint is used to test that an object - is of the same type provided or derived from it. - - - - - Construct an InstanceOfTypeConstraint for the type provided - - The expected Type - - - - Apply the constraint to an actual value, returning true if it succeeds - - The actual argument - True if the constraint succeeds, otherwise false. - - - - Tests whether a value is less than the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - Tests whether a value is less than or equal to the value supplied to its constructor - - - - - Initializes a new instance of the class. - - The expected value. - - - - MessageWriter is the abstract base for classes that write - constraint descriptions and messages in some form. The - class has separate methods for writing various components - of a message, allowing implementations to tailor the - presentation as needed. - - - - - Construct a MessageWriter given a culture - - - - - Method to write single line message with optional args, usually - written to precede the general failure message. - - The message to be written - Any arguments used in formatting the message - - - - Method to write single line message with optional args, usually - written to precede the general failure message, at a givel - indentation level. - - The indentation level of the message - The message to be written - Any arguments used in formatting the message - - - - Display Expected and Actual lines for a constraint. This - is called by MessageWriter's default implementation of - WriteMessageTo and provides the generic two-line display. - - The failing constraint result - - - - Display Expected and Actual lines for given _values. This - method may be called by constraints that need more control over - the display of actual and expected _values than is provided - by the default implementation. - - The expected value - The actual value causing the failure - - - - Display Expected and Actual lines for given _values, including - a tolerance value on the Expected line. - - The expected value - The actual value causing the failure - The tolerance within which the test was made - - - - Display the expected and actual string _values on separate lines. - If the mismatch parameter is >=0, an additional line is displayed - line containing a caret that points to the mismatch point. - - The expected string value - The actual string value - The point at which the strings don't match or -1 - If true, case is ignored in locating the point where the strings differ - If true, the strings should be clipped to fit the line - - - - Writes the text for an actual value. - - The actual value. - - - - Writes the text for a generalized value. - - The value. - - - - Writes the text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Abstract method to get the max line length - - - - - Static methods used in creating messages - - - - - Static string used when strings are clipped - - - - - Formatting strings used for expected and actual _values - - - - - Formats text to represent a generalized value. - - The value - The formatted text - - - - Formats text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Returns the representation of a type as used in NUnitLite. - This is the same as Type.ToString() except for arrays, - which are displayed with their declared sizes. - - - - - - - Converts any control characters in a string - to their escaped representation. - - The string to be converted - The converted string - - - - Return the a string representation for a set of indices into an array - - Array of indices for which a string is needed - - - - Get an array of indices representing the point in a collection or - array corresponding to a single int index into the collection. - - The collection to which the indices apply - Index in the collection - Array of indices - - - - Clip a string to a given length, starting at a particular offset, returning the clipped - string with ellipses representing the removed parts - - The string to be clipped - The maximum permitted length of the result string - The point at which to start clipping - The clipped string - - - - Clip the expected and actual strings in a coordinated fashion, - so that they may be displayed together. - - - - - - - - - Shows the position two strings start to differ. Comparison - starts at the start index. - - The expected string - The actual string - The index in the strings at which comparison should start - Boolean indicating whether case should be ignored - -1 if no mismatch found, or the index where mismatch found - - - - NaNConstraint tests that the actual value is a double or float NaN - - - - - Test that the actual value is an NaN - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - NoItemConstraint applies another constraint to each - item in a collection, failing if any of them succeeds. - - - - - Construct a SomeItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - failing if any item fails. - - - - - - - NotConstraint negates the effect of some other constraint - - - - - Initializes a new instance of the class. - - The base constraint to be negated. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for if the base constraint fails, false if it succeeds - - - - NullConstraint tests that the actual value is null - - - - - Initializes a new instance of the class. - - - - - Applies the constraint to an actual value, returning a ConstraintResult. - - The value to be tested - A ConstraintResult - - - - The Numerics class contains common operations on numeric _values. - - - - - Checks the type of the object, returning true if - the object is a numeric type. - - The object to check - true if the object is a numeric type - - - - Checks the type of the object, returning true if - the object is a floating point numeric type. - - The object to check - true if the object is a floating point numeric type - - - - Checks the type of the object, returning true if - the object is a fixed point numeric type. - - The object to check - true if the object is a fixed point numeric type - - - - Test two numeric _values for equality, performing the usual numeric - conversions and using a provided or default tolerance. If the tolerance - provided is Empty, this method may set it to a default tolerance. - - The expected value - The actual value - A reference to the tolerance in effect - True if the _values are equal - - - - Compare two numeric _values, performing the usual numeric conversions. - - The expected value - The actual value - The relationship of the _values to each other - - - - NUnitComparer encapsulates NUnit's default behavior - in comparing two objects. - - - - - Compares two objects - - - - - - - - Returns the default NUnitComparer. - - - - - NUnitEqualityComparer encapsulates NUnit's handling of - equality tests between objects. - - - - - If true, all string comparisons will ignore case - - - - - If true, arrays will be treated as collections, allowing - those of different dimensions to be compared - - - - - Comparison objects used in comparisons for some constraints. - - - - - List of points at which a failure occurred. - - - - - Compares two objects for equality within a tolerance. - - - - - Helper method to compare two arrays - - - - - Returns the default NUnitEqualityComparer - - - - - Gets and sets a flag indicating whether case should - be ignored in determining equality. - - - - - Gets and sets a flag indicating that arrays should be - compared as collections, without regard to their shape. - - - - - Gets the list of external comparers to be used to - test for equality. They are applied to members of - collections, in place of NUnit's own logic. - - - - - Gets the list of failure points for the last Match performed. - The list consists of objects to be interpreted by the caller. - This generally means that the caller may only make use of - objects it has placed on the list at a particular depthy. - - - - - Flags the comparer to include - property in comparison of two values. - - - Using this modifier does not allow to use the - modifier. - - - - - FailurePoint class represents one point of failure - in an equality test. - - - - - The location of the failure - - - - - The expected value - - - - - The actual value - - - - - Indicates whether the expected value is valid - - - - - Indicates whether the actual value is valid - - - - - Represents a constraint that succeeds if all the - members of a collection match a base constraint. - - - - - Abstract base for operators that indicate how to - apply a constraint to items in a collection. - - - - - PrefixOperator takes a single constraint and modifies - it's action in some way. - - - - - The ConstraintOperator class is used internally by a - ConstraintBuilder to represent an operator that - modifies or combines constraints. - - Constraint operators use left and right precedence - _values to determine whether the top operator on the - stack should be reduced before pushing a new operator. - - - - - The precedence value used when the operator - is about to be pushed to the stack. - - - - - The precedence value used when the operator - is on the top of the stack. - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - The syntax element preceding this operator - - - - - The syntax element following this operator - - - - - The precedence value used when the operator - is about to be pushed to the stack. - - - - - The precedence value used when the operator - is on the top of the stack. - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Returns the constraint created by applying this - prefix to another constraint. - - - - - - - Constructs a CollectionOperator - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - they all succeed. - - - - - Operator that requires both it's arguments to succeed - - - - - Abstract base class for all binary operators - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Abstract method that produces a constraint by applying - the operator to its left and right constraint arguments. - - - - - Gets the left precedence of the operator - - - - - Gets the right precedence of the operator - - - - - Construct an AndOperator - - - - - Apply the operator to produce an AndConstraint - - - - - Operator that tests for the presence of a particular attribute - on a type and optionally applies further tests to the attribute. - - - - - Abstract base class for operators that are able to reduce to a - constraint whether or not another syntactic element follows. - - - - - Construct an AttributeOperator for a particular Type - - The Type of attribute tested - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - Represents a constraint that succeeds if the specified - count of members of a collection match a base constraint. - - - - - Construct an ExactCountOperator for a specified count - - The expected count - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - none of them succeed. - - - - - Represents a constraint that succeeds if none of the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - none of them succeed. - - - - - Negates the test of the constraint it wraps. - - - - - Constructs a new NotOperator - - - - - Returns a NotConstraint applied to its argument. - - - - - Operator that requires at least one of it's arguments to succeed - - - - - Construct an OrOperator - - - - - Apply the operator to produce an OrConstraint - - - - - Operator used to test for the presence of a named Property - on an object and optionally apply further tests to the - value of that property. - - - - - Constructs a PropOperator for a particular named property - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Gets the name of the property to which the operator applies - - - - - Represents a constraint that succeeds if any of the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - any of them succeed. - - - - - Operator that tests that an exception is thrown and - optionally applies further tests to the exception. - - - - - Construct a ThrowsOperator - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - Represents a constraint that simply wraps the - constraint provided as an argument, without any - further functionality, but which modifies the - order of evaluation because of its precedence. - - - - - Constructor for the WithOperator - - - - - Returns a constraint that wraps its argument - - - - - OrConstraint succeeds if either member succeeds - - - - - Create an OrConstraint from two other constraints - - The first constraint - The second constraint - - - - Apply the member constraints to an actual value, succeeding - succeeding as soon as one of them succeeds. - - The actual value - True if either constraint succeeded - - - - Gets text describing a constraint - - - - - Predicate constraint wraps a Predicate in a constraint, - returning success if the predicate is true. - - - - - Construct a PredicateConstraint from a predicate - - - - - Determines whether the predicate succeeds when applied - to the actual value. - - - - - Gets text describing a constraint - - - - - PropertyConstraint extracts a named property and uses - its value as the actual value for a chained constraint. - - - - - Initializes a new instance of the class. - - The name. - The constraint to apply to the property. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - - PropertyExistsConstraint tests that a named property - exists on the object provided through Match. - - Originally, PropertyConstraint provided this feature - in addition to making optional tests on the value - of the property. The two constraints are now separate. - - - - - Initializes a new instance of the class. - - The name of the property. - - - - Test whether the property exists for a given object - - The object to be tested - True for success, false for failure - - - - Returns the string representation of the constraint. - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - RangeConstraint tests whether two _values are within a - specified range. - - - - - Initializes a new instance of the class. - - from must be less than or equal to true - Inclusive beginning of the range. Must be less than or equal to to. - Inclusive end of the range. Must be greater than or equal to from. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use an and returns self. - - - - - Modifies the constraint to use a and returns self. - - - - - Gets text describing a constraint - - - - - RegexConstraint can test whether a string matches - the pattern provided. - - - - - Initializes a new instance of the class. - - The pattern. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - ResolvableConstraintExpression is used to represent a compound - constraint being constructed at a point where the last operator - may either terminate the expression or may have additional - qualifying constraints added to it. - - It is used, for example, for a Property element or for - an Exception element, either of which may be optionally - followed by constraints that apply to the property or - exception. - - - - - Create a new instance of ResolvableConstraintExpression - - - - - Create a new instance of ResolvableConstraintExpression, - passing in a pre-populated ConstraintBuilder. - - - - - Resolve the current expression to a Constraint - - - - - Appends an And Operator to the expression - - - - - Appends an Or operator to the expression. - - - - - ReusableConstraint wraps a constraint expression after - resolving it so that it can be reused consistently. - - - - - Construct a ReusableConstraint from a constraint expression - - The expression to be resolved and reused - - - - Converts a constraint to a ReusableConstraint - - The constraint to be converted - A ReusableConstraint - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Return the top-level constraint for this expression - - - - - - SameAsConstraint tests whether an object is identical to - the object passed to its constructor - - - - - Initializes a new instance of the class. - - The expected object. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - SomeItemsConstraint applies another constraint to each - item in a collection, succeeding if any of them succeeds. - - - - - Construct a SomeItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - succeeding if any item succeeds. - - - - - - - StartsWithConstraint can test whether a string starts - with an expected substring. - - - - - Initializes a new instance of the class. - - The expected string - - - - Test whether the constraint is matched by the actual value. - This is a template method, which calls the IsMatch method - of the derived class. - - - - - - - SubstringConstraint can test whether a string contains - the expected substring. - - - - - Initializes a new instance of the class. - - The expected. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - ThrowsConstraint is used to test the exception thrown by - a delegate by applying a constraint to it. - - - - - Initializes a new instance of the class, - using a constraint to be applied to the exception. - - A constraint to apply to the caught exception. - - - - Executes the code of the delegate and captures any exception. - If a non-null base constraint was provided, it applies that - constraint to the exception. - - A delegate representing the code to be tested - True if an exception is thrown and the constraint succeeds, otherwise false - - - - Converts an ActualValueDelegate to a TestDelegate - before calling the primary overload. - - - - - - - Get the actual exception thrown - used by Assert.Throws. - - - - - Gets text describing a constraint - - - - - Write the actual value for a failing constraint test to a - MessageWriter. This override only handles the special message - used when an exception is expected but none is thrown. - - The writer on which the actual value is displayed - - - - ThrowsExceptionConstraint tests that an exception has - been thrown, without any further tests. - - - - - Executes the code and returns success if an exception is thrown. - - A delegate representing the code to be tested - True if an exception is thrown, otherwise false - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - ThrowsNothingConstraint tests that a delegate does not - throw an exception. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True if no exception is thrown, otherwise false - - - - Applies the constraint to an ActualValueDelegate that returns - the value to be tested. The default implementation simply evaluates - the delegate but derived classes may override it to provide for - delayed processing. - - An ActualValueDelegate - A ConstraintResult - - - - Gets text describing a constraint - - - - - The Tolerance class generalizes the notion of a tolerance - within which an equality test succeeds. Normally, it is - used with numeric types, but it can be used with any - type that supports taking a difference between two - objects and comparing that difference to a value. - - - - - Constructs a linear tolerance of a specified amount - - - - - Constructs a tolerance given an amount and - - - - - Tests that the current Tolerance is linear with a - numeric value, throwing an exception if it is not. - - - - - Returns a default Tolerance object, equivalent to - specifying an exact match unless - is set, in which case, the - will be used. - - - - - Returns an empty Tolerance object, equivalent to - specifying an exact match even if - is set. - - - - - Gets the for the current Tolerance - - - - - Gets the value of the current Tolerance instance. - - - - - Returns a new tolerance, using the current amount as a percentage. - - - - - Returns a new tolerance, using the current amount in Ulps - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of days. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of hours. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of minutes. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of seconds. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of milliseconds. - - - - - Returns a new tolerance with a as the amount, using - the current amount as a number of clock ticks. - - - - - Returns true if the current tolerance has not been set or is using the . - - - - - Modes in which the tolerance value for a comparison can be interpreted. - - - - - The tolerance was created with a value, without specifying - how the value would be used. This is used to prevent setting - the mode more than once and is generally changed to Linear - upon execution of the test. - - - - - The tolerance is used as a numeric range within which - two compared _values are considered to be equal. - - - - - Interprets the tolerance as the percentage by which - the two compared _values my deviate from each other. - - - - - Compares two _values based in their distance in - representable numbers. - - - - - TrueConstraint tests that the actual value is true - - - - - Initializes a new instance of the class. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - UniqueItemsConstraint tests whether all the items in a - collection are unique. - - - - - Check that all items are unique. - - - - - - - The Description of what this constraint tests, for - use in messages and in the ConstraintResult. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new DictionaryContainsKeyConstraint checking for the - presence of a particular key in the dictionary. - - - - - Returns a new DictionaryContainsValueConstraint checking for the - presence of a particular value in the dictionary. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Thrown when an assertion failed. - - - - - Abstract base for Exceptions that terminate a test and provide a ResultState. - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Gets the ResultState provided by this exception - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Gets the ResultState provided by this exception - - - - - Thrown when an assertion failed. - - - - - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Gets the ResultState provided by this exception - - - - - Thrown when a test executes inconclusively. - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Gets the ResultState provided by this exception - - - - - Thrown when an assertion failed. - - - - - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Gets the ResultState provided by this exception - - - - - GlobalSettings is a place for setting default _values used - by the framework in performing asserts. - - - - - Default tolerance for floating point equality - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - CombiningStrategy is the abstract base for classes that - know how to combine values provided for individual test - parameters to create a set of test cases. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - Objects implementing this interface are used to wrap - the TestMethodCommand itself. They apply after SetUp - has been run and before TearDown. - - - - - Any ITest that implements this interface is at a level that the implementing - class should be disposed at the end of the test run - - - - - The IMethodInfo class is used to encapsulate information - about a method in a platform-independent manner. - - - - - The IReflectionInfo interface is implemented by NUnit wrapper objects that perform reflection. - - - - - Returns an array of custom attributes of the specified type applied to this object - - - - - Returns a value indicating whether an attribute of the specified type is defined on this object. - - - - - Gets the parameters of the method. - - - - - - Returns the Type arguments of a generic method or the Type parameters of a generic method definition. - - - - - Replaces the type parameters of the method with the array of types provided and returns a new IMethodInfo. - - The type arguments to be used - A new IMethodInfo with the type arguments replaced - - - - Invokes the method, converting any TargetInvocationException to an NUnitException. - - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - Gets the Type from which this method was reflected. - - - - - Gets the MethodInfo for this method. - - - - - Gets the name of the method. - - - - - Gets a value indicating whether the method is abstract. - - - - - Gets a value indicating whether the method is public. - - - - - Gets a value indicating whether the method contains unassigned generic type parameters. - - - - - Gets a value indicating whether the method is a generic method. - - - - - Gets a value indicating whether the MethodInfo represents the definition of a generic method. - - - - - Gets the return Type of the method. - - - - - The IDataPointProvider interface is used by extensions - that provide data for a single test parameter. - - - - - Determine whether any data is available for a parameter. - - An IParameterInfo representing one - argument to a parameterized test - True if any data is available, otherwise false. - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - An IParameterInfo representing one - argument to a parameterized test - An IEnumerable providing the required data - - - - The IParameterInfo interface is an abstraction of a .NET parameter. - - - - - Gets a value indicating whether the parameter is optional - - - - - Gets an IMethodInfo representing the method for which this is a parameter - - - - - Gets the underlying .NET ParameterInfo - - - - - Gets the Type of the parameter - - - - - A PropertyBag represents a collection of name/value pairs - that allows duplicate entries with the same key. Methods - are provided for adding a new pair as well as for setting - a key to a single value. All keys are strings but _values - may be of any type. Null _values are not permitted, since - a null entry represents the absence of the key. - - The entries in a PropertyBag are of two kinds: those that - take a single value and those that take multiple _values. - However, the PropertyBag has no knowledge of which entries - fall into each category and the distinction is entirely - up to the code using the PropertyBag. - - When working with multi-valued properties, client code - should use the Add method to add name/value pairs and - indexing to retrieve a list of all _values for a given - key. For example: - - bag.Add("Tag", "one"); - bag.Add("Tag", "two"); - Assert.That(bag["Tag"], - Is.EqualTo(new string[] { "one", "two" })); - - When working with single-valued propeties, client code - should use the Set method to set the value and Get to - retrieve the value. The GetSetting methods may also be - used to retrieve the value in a type-safe manner while - also providing default. For example: - - bag.Set("Priority", "low"); - bag.Set("Priority", "high"); // replaces value - Assert.That(bag.Get("Priority"), - Is.EqualTo("high")); - Assert.That(bag.GetSetting("Priority", "low"), - Is.EqualTo("high")); - - - - - An object implementing IXmlNodeBuilder is able to build - an XML representation of itself and any children. - - - - - Returns a TNode representing the current object. - - If true, children are included where applicable - A TNode representing the result - - - - Returns a TNode representing the current object after - adding it as a child of the supplied parent node. - - The parent node. - If true, children are included, where applicable - - - - - Adds a key/value pair to the property bag - - The key - The value - - - - Sets the value for a key, removing any other - _values that are already in the property set. - - - - - - - Gets a single value for a key, using the first - one if multiple _values are present and returning - null if the value is not found. - - - - - Gets a flag indicating whether the specified key has - any entries in the property set. - - The key to be checked - True if their are _values present, otherwise false - - - - Gets or sets the list of _values for a particular key - - The key for which the _values are to be retrieved or set - - - - Gets a collection containing all the keys in the property set - - - - - The ISuiteBuilder interface is exposed by a class that knows how to - build a suite from one or more Types. - - - - - Examine the type and determine if it is suitable for - this builder to use in building a TestSuite. - - Note that returning false will cause the type to be ignored - in loading the tests. If it is desired to load the suite - but label it as non-runnable, ignored, etc., then this - method must return true. - - The type of the fixture to be used - True if the type can be used to build a TestSuite - - - - Build a TestSuite from type provided. - - The type of the fixture to be used - A TestSuite - - - - Common interface supported by all representations - of a test. Only includes informational fields. - The Run method is specifically excluded to allow - for data-only representations of a test. - - - - - Gets the id of the test - - - - - Gets the name of the test - - - - - Gets the fully qualified name of the test - - - - - Gets the name of the class containing this test. Returns - null if the test is not associated with a class. - - - - - Gets the name of the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the Type of the test fixture, if applicable, or - null if no fixture type is associated with this test. - - - - - Gets an IMethod for the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the RunState of the test, indicating whether it can be run. - - - - - Count of the test cases ( 1 if this is a test case ) - - - - - Gets the properties of the test - - - - - Gets the parent test, if any. - - The parent test or null if none exists. - - - - Returns true if this is a test suite - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets a fixture object for running this test. - - - - - The ITestCaseBuilder interface is exposed by a class that knows how to - build a test case from certain methods. - - - This interface is not the same as the ITestCaseBuilder interface in NUnit 2.x. - We have reused the name because the two products don't interoperate at all. - - - - - Examine the method and determine if it is suitable for - this builder to use in building a TestCase to be - included in the suite being populated. - - Note that returning false will cause the method to be ignored - in loading the tests. If it is desired to load the method - but label it as non-runnable, ignored, etc., then this - method must return true. - - The test method to examine - The suite being populated - True is the builder can use this method - - - - Build a TestCase from the provided MethodInfo for - inclusion in the suite being constructed. - - The method to be used as a test case - The test suite being populated, or null - A TestCase or null - - - - Interface to be implemented by filters applied to tests. - The filter applies when running the test, after it has been - loaded, since this is the only time an ITest exists. - - - - - Determine if a particular test passes the filter criteria. Pass - may examine the parents and/or descendants of a test, depending - on the semantics of the particular filter - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - The ITestListener interface is used internally to receive - notifications of significant events while a test is being - run. The events are propagated to clients by means of an - AsyncCallback. NUnit extensions may also monitor these events. - - - - - Called when a test has just started - - The test that is starting - - - - Called when a test has finished - - The result of the test - - - - The ITestResult interface represents the result of a test. - - - - - Gets the ResultState of the test result, which - indicates the success or failure of the test. - - - - - Gets the name of the test result - - - - - Gets the full name of the test result - - - - - Gets the elapsed time for running the test in seconds - - - - - Gets or sets the time the test started running. - - - - - Gets or sets the time the test finished running. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. Not available in - the Compact Framework 1.0. - - - - - Gets the number of asserts executed - when running the test and all its children. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Indicates whether this result has any child results. - Accessing HasChildren should not force creation of the - Children collection in classes implementing this interface. - - - - - Gets the the collection of child results. - - - - - Gets the Test to which this result applies. - - - - - Gets any text output written to this result. - - - - - The ITypeInfo interface is an abstraction of a .NET Type - - - - - Returns true if the Type wrapped is equal to the argument - - - - - Get the display name for this typeInfo. - - - - - Get the display name for an oject of this type, constructed with specific arguments - - - - - Returns a Type representing a generic type definition from which this Type can be constructed. - - - - - Returns a new ITypeInfo representing an instance of this generic Type using the supplied Type arguments - - - - - Returns a value indicating whether this type has a method with a specified public attribute - - - - - Returns an array of IMethodInfos for methods of this Type - that match the specified flags. - - - - - Returns a value indicating whether this Type has a public constructor taking the specified argument Types. - - - - - Construct an object of this Type, using the specified arguments. - - - - - Gets the underlying Type on which this ITypeInfo is based - - - - - Gets the base type of this type as an ITypeInfo - - - - - Gets the Name of the Type - - - - - Gets the FullName of the Type - - - - - Gets the assembly in which the type is declared - - - - - Gets the Namespace of the Type - - - - - Gets a value indicating whether the type is abstract. - - - - - Gets a value indicating whether the Type is a generic Type - - - - - Gets a value indicating whether the Type has generic parameters that have not been replaced by specific Types. - - - - - Gets a value indicating whether the Type is a generic Type definition - - - - - Gets a value indicating whether the type is sealed. - - - - - Gets a value indicating whether this type is a static class. - - - - - The ResultState class represents the outcome of running a test. - It contains two pieces of information. The Status of the test - is an enum indicating whether the test passed, failed, was - skipped or was inconclusive. The Label provides a more - detailed breakdown for use by client runners. - - - - - Initializes a new instance of the class. - - The TestStatus. - - - - Initializes a new instance of the class. - - The TestStatus. - The label. - - - - Initializes a new instance of the class. - - The TestStatus. - The stage at which the result was produced - - - - Initializes a new instance of the class. - - The TestStatus. - The label. - The stage at which the result was produced - - - - The result is inconclusive - - - - - The test has been skipped. - - - - - The test has been ignored. - - - - - The test was skipped because it is explicit - - - - - The test succeeded - - - - - The test failed - - - - - The test encountered an unexpected exception - - - - - The test was cancelled by the user - - - - - The test was not runnable. - - - - - A suite failed because one or more child tests failed or had errors - - - - - A suite failed in its OneTimeSetUp - - - - - A suite had an unexpected exception in its OneTimeSetUp - - - - - A suite had an unexpected exception in its OneTimeDown - - - - - Get a new ResultState, which is the same as the current - one but with the FailureSite set to the specified value. - - The FailureSite to use - A new ResultState - - - - Determines whether the specified , is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Returns a hash code for this instance. - - - A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Gets the TestStatus for the test. - - The status. - - - - Gets the label under which this test result is - categorized, if any. - - - - - Gets the stage of test execution in which - the failure or other result took place. - - - - - The FailureSite enum indicates the stage of a test - in which an error or failure occurred. - - - - - Failure in the test itself - - - - - Failure in the SetUp method - - - - - Failure in the TearDown method - - - - - Failure of a parent test - - - - - Failure of a child test - - - - - The RunState enum indicates whether a test can be executed. - - - - - The test is not runnable. - - - - - The test is runnable. - - - - - The test can only be run explicitly - - - - - The test has been skipped. This value may - appear on a Test when certain attributes - are used to skip the test. - - - - - The test has been ignored. May appear on - a Test, when the IgnoreAttribute is used. - - - - - The TestStatus enum indicates the result of running a test - - - - - The test was inconclusive - - - - - The test has skipped - - - - - The test succeeded - - - - - The test failed - - - - - TNode represents a single node in the XML representation - of a Test or TestResult. It replaces System.Xml.XmlNode and - System.Xml.Linq.XElement, providing a minimal set of methods - for operating on the XML in a platform-independent manner. - - - - - Constructs a new instance of TNode - - The name of the node - - - - Constructs a new instance of TNode with a value - - The name of the node - The text content of the node - - - - Constructs a new instance of TNode with a value - - The name of the node - The text content of the node - Flag indicating whether to use CDATA when writing the text - - - - Create a TNode from it's XML text representation - - The XML text to be parsed - A TNode - - - - Adds a new element as a child of the current node and returns it. - - The element name. - The newly created child element - - - - Adds a new element with a value as a child of the current node and returns it. - - The element name - The text content of the new element - The newly created child element - - - - Adds a new element with a value as a child of the current node and returns it. - The value will be output using a CDATA section. - - The element name - The text content of the new element - The newly created child element - - - - Adds an attribute with a specified name and value to the XmlNode. - - The name of the attribute. - The value of the attribute. - - - - Finds a single descendant of this node matching an xpath - specification. The format of the specification is - limited to what is needed by NUnit and its tests. - - - - - - - Finds all descendants of this node matching an xpath - specification. The format of the specification is - limited to what is needed by NUnit and its tests. - - - - - Writes the XML representation of the node to an XmlWriter - - - - - - Gets the name of the node - - - - - Gets the value of the node - - - - - Gets a flag indicating whether the value should be output using CDATA. - - - - - Gets the dictionary of attributes - - - - - Gets a list of child nodes - - - - - Gets the first ChildNode - - - - - Gets the XML representation of this node. - - - - - Class used to represent a list of XmlResults - - - - - Class used to represent the attributes of a node - - - - - Gets or sets the value associated with the specified key. - Overridden to return null if attribute is not found. - - The key. - Value of the attribute or null - - - - Waits for pending asynchronous operations to complete, if appropriate, - and returns a proper result of the invocation by unwrapping task results - - The raw result of the method invocation - The unwrapped result, if necessary - - - - CombinatorialStrategy creates test cases by using all possible - combinations of the parameter data. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - Provides data from fields marked with the DatapointAttribute or the - DatapointsAttribute. - - - - - Determine whether any data is available for a parameter. - - A ParameterInfo representing one - argument to a parameterized test - - True if any data is available, otherwise false. - - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - A ParameterInfo representing one - argument to a parameterized test - - An IEnumerable providing the required data - - - - - Built-in SuiteBuilder for all types of test classes. - - - - - Checks to see if the provided Type is a fixture. - To be considered a fixture, it must be a non-abstract - class with one or more attributes implementing the - IFixtureBuilder interface or one or more methods - marked as tests. - - The fixture type to check - True if the fixture can be built, false if not - - - - Build a TestSuite from TypeInfo provided. - - The fixture type to build - A TestSuite built from that type - - - - We look for attributes implementing IFixtureBuilder at one level - of inheritance at a time. Attributes on base classes are not used - unless there are no fixture builder attributes at all on the derived - class. This is by design. - - The type being examined for attributes - A list of the attributes found. - - - - Class to build ether a parameterized or a normal NUnitTestMethod. - There are four cases that the builder must deal with: - 1. The method needs no params and none are provided - 2. The method needs params and they are provided - 3. The method needs no params but they are provided in error - 4. The method needs params but they are not provided - This could have been done using two different builders, but it - turned out to be simpler to have just one. The BuildFrom method - takes a different branch depending on whether any parameters are - provided, but all four cases are dealt with in lower-level methods - - - - - Determines if the method can be used to build an NUnit test - test method of some kind. The method must normally be marked - with an identifying attribute for this to be true. - - Note that this method does not check that the signature - of the method for validity. If we did that here, any - test methods with invalid signatures would be passed - over in silence in the test run. Since we want such - methods to be reported, the check for validity is made - in BuildFrom rather than here. - - An IMethodInfo for the method being used as a test method - True if the builder can create a test case from this method - - - - Build a Test from the provided MethodInfo. Depending on - whether the method takes arguments and on the availability - of test case data, this method may return a single test - or a group of tests contained in a ParameterizedMethodSuite. - - The method for which a test is to be built - A Test representing one or more method invocations - - - - Determines if the method can be used to build an NUnit test - test method of some kind. The method must normally be marked - with an identifying attribute for this to be true. - - Note that this method does not check that the signature - of the method for validity. If we did that here, any - test methods with invalid signatures would be passed - over in silence in the test run. Since we want such - methods to be reported, the check for validity is made - in BuildFrom rather than here. - - An IMethodInfo for the method being used as a test method - The test suite being built, to which the new test would be added - True if the builder can create a test case from this method - - - - Build a Test from the provided MethodInfo. Depending on - whether the method takes arguments and on the availability - of test case data, this method may return a single test - or a group of tests contained in a ParameterizedMethodSuite. - - The method for which a test is to be built - The test fixture being populated, or null - A Test representing one or more method invocations - - - - Builds a ParameterizedMethodSuite containing individual test cases. - - The method for which a test is to be built. - The list of test cases to include. - A ParameterizedMethodSuite populated with test cases - - - - Build a simple, non-parameterized TestMethod for this method. - - The MethodInfo for which a test is to be built - The test suite for which the method is being built - A TestMethod. - - - - Class that can build a tree of automatic namespace - suites from a group of fixtures. - - - - - NamespaceDictionary of all test suites we have created to represent - namespaces. Used to locate namespace parent suites for fixtures. - - - - - The root of the test suite being created by this builder. - - - - - Initializes a new instance of the class. - - The root suite. - - - - Adds the specified fixtures to the tree. - - The fixtures to be added. - - - - Adds the specified fixture to the tree. - - The fixture to be added. - - - - Gets the root entry in the tree created by the NamespaceTreeBuilder. - - The root suite. - - - - NUnitTestCaseBuilder is a utility class used by attributes - that build test cases. - - - - - Constructs an - - - - - Builds a single NUnitTestMethod, either as a child of the fixture - or as one of a set of test cases under a ParameterizedTestMethodSuite. - - The MethodInfo from which to construct the TestMethod - The suite or fixture to which the new test will be added - The ParameterSet to be used, or null - - - - - Helper method that checks the signature of a TestMethod and - any supplied parameters to determine if the test is valid. - - Currently, NUnitTestMethods are required to be public, - non-abstract methods, either static or instance, - returning void. They may take arguments but the _values must - be provided or the TestMethod is not considered runnable. - - Methods not meeting these criteria will be marked as - non-runnable and the method will return false in that case. - - The TestMethod to be checked. If it - is found to be non-runnable, it will be modified. - Parameters to be used for this test, or null - True if the method signature is valid, false if not - - The return value is no longer used internally, but is retained - for testing purposes. - - - - - NUnitTestFixtureBuilder is able to build a fixture given - a class marked with a TestFixtureAttribute or an unmarked - class containing test methods. In the first case, it is - called by the attribute and in the second directly by - NUnitSuiteBuilder. - - - - - Build a TestFixture from type provided. A non-null TestSuite - must always be returned, since the method is generally called - because the user has marked the target class as a fixture. - If something prevents the fixture from being used, it should - be returned nonetheless, labelled as non-runnable. - - An ITypeInfo for the fixture to be used. - A TestSuite object or one derived from TestSuite. - - - - Overload of BuildFrom called by tests that have arguments. - Builds a fixture using the provided type and information - in the ITestFixtureData object. - - The TypeInfo for which to construct a fixture. - An object implementing ITestFixtureData or null. - - - - - Method to add test cases to the newly constructed fixture. - - The fixture to which cases should be added - - - - Method to create a test case from a MethodInfo and add - it to the fixture being built. It first checks to see if - any global TestCaseBuilder addin wants to build the - test case. If not, it uses the internal builder - collection maintained by this fixture builder. - - The default implementation has no test case builders. - Derived classes should add builders to the collection - in their constructor. - - The method for which a test is to be created - The test suite being built. - A newly constructed Test - - - - PairwiseStrategy creates test cases by combining the parameter - data so that all possible pairs of data items are used. - - - - The number of test cases that cover all possible pairs of test function - parameters values is significantly less than the number of test cases - that cover all possible combination of test function parameters values. - And because different studies show that most of software failures are - caused by combination of no more than two parameters, pairwise testing - can be an effective ways to test the system when it's impossible to test - all combinations of parameters. - - - The PairwiseStrategy code is based on "jenny" tool by Bob Jenkins: - http://burtleburtle.net/bob/math/jenny.html - - - - - - Gets the test cases generated by this strategy instance. - - A set of test cases. - - - - FleaRand is a pseudo-random number generator developed by Bob Jenkins: - http://burtleburtle.net/bob/rand/talksmall.html#flea - - - - - Initializes a new instance of the FleaRand class. - - The seed. - - - - FeatureInfo represents coverage of a single value of test function - parameter, represented as a pair of indices, Dimension and Feature. In - terms of unit testing, Dimension is the index of the test parameter and - Feature is the index of the supplied value in that parameter's list of - sources. - - - - - Initializes a new instance of FeatureInfo class. - - Index of a dimension. - Index of a feature. - - - - A FeatureTuple represents a combination of features, one per test - parameter, which should be covered by a test case. In the - PairwiseStrategy, we are only trying to cover pairs of features, so the - tuples actually may contain only single feature or pair of features, but - the algorithm itself works with triplets, quadruples and so on. - - - - - Initializes a new instance of FeatureTuple class for a single feature. - - Single feature. - - - - Initializes a new instance of FeatureTuple class for a pair of features. - - First feature. - Second feature. - - - - TestCase represents a single test case covering a list of features. - - - - - Initializes a new instance of TestCaseInfo class. - - A number of features in the test case. - - - - PairwiseTestCaseGenerator class implements an algorithm which generates - a set of test cases which covers all pairs of possible values of test - function. - - - - The algorithm starts with creating a set of all feature tuples which we - will try to cover (see method). This set - includes every single feature and all possible pairs of features. We - store feature tuples in the 3-D collection (where axes are "dimension", - "feature", and "all combinations which includes this feature"), and for - every two feature (e.g. "A" and "B") we generate both ("A", "B") and - ("B", "A") pairs. This data structure extremely reduces the amount of - time needed to calculate coverage for a single test case (this - calculation is the most time-consuming part of the algorithm). - - - Then the algorithm picks one tuple from the uncovered tuple, creates a - test case that covers this tuple, and then removes this tuple and all - other tuples covered by this test case from the collection of uncovered - tuples. - - - Picking a tuple to cover - - - There are no any special rules defined for picking tuples to cover. We - just pick them one by one, in the order they were generated. - - - Test generation - - - Test generation starts from creating a completely random test case which - covers, nevertheless, previously selected tuple. Then the algorithm - tries to maximize number of tuples which this test covers. - - - Test generation and maximization process repeats seven times for every - selected tuple and then the algorithm picks the best test case ("seven" - is a magic number which provides good results in acceptable time). - - Maximizing test coverage - - To maximize tests coverage, the algorithm walks thru the list of mutable - dimensions (mutable dimension is a dimension that are not included in - the previously selected tuple). Then for every dimension, the algorithm - walks thru the list of features and checks if this feature provides - better coverage than randomly selected feature, and if yes keeps this - feature. - - - This process repeats while it shows progress. If the last iteration - doesn't improve coverage, the process ends. - - - In addition, for better results, before start every iteration, the - algorithm "scrambles" dimensions - so for every iteration dimension - probes in a different order. - - - - - - Creates a set of test cases for specified dimensions. - - - An array which contains information about dimensions. Each element of - this array represents a number of features in the specific dimension. - - - A set of test cases. - - - - - ParameterDataProvider supplies individual argument _values for - single parameters using attributes derived from DataAttribute. - - - - - Determine whether any data is available for a parameter. - - A ParameterInfo representing one - argument to a parameterized test - - True if any data is available, otherwise false. - - - - - Return an IEnumerable providing data for use with the - supplied parameter. - - An IParameterInfo representing one - argument to a parameterized test - - An IEnumerable providing the required data - - - - - SequentialStrategy creates test cases by using all of the - parameter data sources in parallel, substituting null - when any of them run out of data. - - - - - Gets the test cases generated by the CombiningStrategy. - - The test cases. - - - - ContextSettingsCommand applies specified changes to the - TestExecutionContext prior to running a test. No special - action is needed after the test runs, since the prior - context will be restored automatically. - - - - - The CommandStage enumeration represents the defined stages - of execution for a series of TestCommands. The int _values - of the enum are used to apply decorators in the proper - order. Lower _values are applied first and are therefore - "closer" to the actual test execution. - - - No CommandStage is defined for actual invocation of the test or - for creation of the context. Execution may be imagined as - proceeding from the bottom of the list upwards, with cleanup - after the test running in the opposite order. - - - - - Use an application-defined default value. - - - - - Make adjustments needed before and after running - the raw test - that is, after any SetUp has run - and before TearDown. - - - - - Run SetUp and TearDown for the test. This stage is used - internally by NUnit and should not normally appear - in user-defined decorators. - - - - - Make adjustments needed before and after running - the entire test - including SetUp and TearDown. - - - - - TODO: Documentation needed for class - - - - - Initializes a new instance of the class. - - The inner command. - The max time allowed in milliseconds - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext - - The context in which the test should run. - A TestResult - - - - OneTimeSetUpCommand runs any one-time setup methods for a suite, - constructing the user test object if necessary. - - - - - Constructs a OneTimeSetUpCommand for a suite - - The suite to which the command applies - A SetUpTearDownList for use by the command - A List of TestActionItems to be run after Setup - - - - Overridden to run the one-time setup for a suite. - - The TestExecutionContext to be used. - A TestResult - - - - OneTimeTearDownCommand performs any teardown actions - specified for a suite and calls Dispose on the user - test object, if any. - - - - - Construct a OneTimeTearDownCommand - - The test suite to which the command applies - A SetUpTearDownList for use by the command - A List of TestActionItems to be run before teardown. - - - - Overridden to run the teardown methods specified on the test. - - The TestExecutionContext to be used. - A TestResult - - - - SetUpTearDownCommand runs any SetUp methods for a suite, - runs the test and then runs any TearDown methods. - - - - - Initializes a new instance of the class. - - The inner command. - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - SetUpTearDownItem holds the setup and teardown methods - for a single level of the inheritance hierarchy. - - - - - Construct a SetUpTearDownNode - - A list of setup methods for this level - A list teardown methods for this level - - - - Run SetUp on this level. - - The execution context to use for running. - - - - Run TearDown for this level. - - - - - - Returns true if this level has any methods at all. - This flag is used to discard levels that do nothing. - - - - - TODO: Documentation needed for class - - - - - Initializes a new instance of the class. - - The test being skipped. - - - - Overridden to simply set the CurrentResult to the - appropriate Skipped state. - - The execution context for the test - A TestResult - - - - TestActionCommand runs the BeforeTest actions for a test, - then runs the test and finally runs the AfterTestActions. - - - - - Initializes a new instance of the class. - - The inner command. - - - - Runs the test, saving a TestResult in the supplied TestExecutionContext. - - The context in which the test should run. - A TestResult - - - - TestActionItem represents a single execution of an - ITestAction. It is used to track whether the BeforeTest - method has been called and suppress calling the - AfterTest method if it has not. - - - - - Construct a TestActionItem - - The ITestAction to be included - - - - Run the BeforeTest method of the action and remember that it has been run. - - The test to which the action applies - - - - Run the AfterTest action, but only if the BeforeTest - action was actually run. - - The test to which the action applies - - - - TestMethodCommand is the lowest level concrete command - used to run actual test cases. - - - - - Initializes a new instance of the class. - - The test. - - - - Runs the test, saving a TestResult in the execution context, as - well as returning it. If the test has an expected result, it - is asserts on that value. Since failed tests and errors throw - an exception, this command must be wrapped in an outer command, - will handle that exception and records the failure. This role - is usually played by the SetUpTearDown command. - - The execution context - - - - TheoryResultCommand adjusts the result of a Theory so that - it fails if all the results were inconclusive. - - - - - Constructs a TheoryResultCommand - - The command to be wrapped by this one - - - - Overridden to call the inner command and adjust the result - in case all chlid results were inconclusive. - - - - - - - CultureDetector is a helper class used by NUnit to determine - whether a test should be run based on the current culture. - - - - - Default constructor uses the current culture. - - - - - Construct a CultureDetector for a particular culture for testing. - - The culture to be used - - - - Test to determine if one of a collection of cultures - is being used currently. - - - - - - - Tests to determine if the current culture is supported - based on a culture attribute. - - The attribute to examine - - - - - Test to determine if the a particular culture or comma- - delimited set of cultures is in use. - - Name of the culture or comma-separated list of culture ids - True if the culture is in use on the system - - - - Return the last failure reason. Results are not - defined if called before IsSupported( Attribute ) - is called. - - - - - ExceptionHelper provides static methods for working with exceptions - - - - - Builds up a message, using the Message field of the specified exception - as well as any InnerExceptions. - - The exception. - A combined message string. - - - - Builds up a message, using the Message field of the specified exception - as well as any InnerExceptions. - - The exception. - A combined stack trace. - - - - Gets the stack trace of the exception. - - The exception. - A string representation of the stack trace. - - - - A utility class to create TestCommands - - - - - Gets the command to be executed before any of - the child tests are run. - - A TestCommand - - - - Gets the command to be executed after all of the - child tests are run. - - A TestCommand - - - - Creates a test command for use in running this test. - - - - - - Creates a command for skipping a test. The result returned will - depend on the test RunState. - - - - - Builds the set up tear down list. - - Type of the fixture. - Type of the set up attribute. - Type of the tear down attribute. - A list of SetUpTearDownItems - - - - A CompositeWorkItem represents a test suite and - encapsulates the execution of the suite as well - as all its child tests. - - - - - A WorkItem may be an individual test case, a fixture or - a higher level grouping of tests. All WorkItems inherit - from the abstract WorkItem class, which uses the template - pattern to allow derived classes to perform work in - whatever way is needed. - - A WorkItem is created with a particular TestExecutionContext - and is responsible for re-establishing that context in the - current thread before it begins or resumes execution. - - - - - Creates a work item. - - The test for which this WorkItem is being created. - The filter to be used in selecting any child Tests. - - - - - Construct a WorkItem for a particular test. - - The test that the WorkItem will run - - - - Initialize the TestExecutionContext. This must be done - before executing the WorkItem. - - - Originally, the context was provided in the constructor - but delaying initialization of the context until the item - is about to be dispatched allows changes in the parent - context during OneTimeSetUp to be reflected in the child. - - The TestExecutionContext to use - - - - Execute the current work item, including any - child work items. - - - - - Method that performs actually performs the work. It should - set the State to WorkItemState.Complete when done. - - - - - Method called by the derived class when all work is complete - - - - - Event triggered when the item is complete - - - - - Gets the current state of the WorkItem - - - - - The test being executed by the work item - - - - - The execution context - - - - - The test actions to be performed before and after this test - - - - - The test result - - - - - Construct a CompositeWorkItem for executing a test suite - using a filter to select child tests. - - The TestSuite to be executed - A filter used to select child tests - - - - Method that actually performs the work. Overridden - in CompositeWorkItem to do setup, run all child - items and then do teardown. - - - - - A simplified implementation of .NET 4 CountdownEvent - for use in earlier versions of .NET. Only the methods - used by NUnit are implemented. - - - - - Construct a CountdownEvent - - The initial count - - - - Decrement the count by one - - - - - Block the thread until the count reaches zero - - - - - Gets the initial count established for the CountdownEvent - - - - - Gets the current count remaining for the CountdownEvent - - - - - An IWorkItemDispatcher handles execution of work items. - - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and used when stopping the run. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - A SimpleWorkItem represents a single test case and is - marked as completed immediately upon execution. This - class is also used for skipped or ignored test suites. - - - - - Construct a simple work item for a test. - - The test to be executed - The filter used to select this test - - - - Method that performs actually performs the work. - - - - - SimpleWorkItemDispatcher handles execution of WorkItems by - directly executing them. It is provided so that a dispatcher - is always available in the context, thereby simplifying the - code needed to run child tests. - - - - - Dispatch a single work item for execution. The first - work item dispatched is saved as the top-level - work item and a thread is created on which to - run it. Subsequent calls come from the top level - item or its descendants on the proper thread. - - The item to dispatch - - - - Cancel the ongoing run completely. - If no run is in process, the call has no effect. - - - - - TextMessageWriter writes constraint descriptions and messages - in displayable form as a text stream. It tailors the display - of individual message components to form the standard message - format of NUnit assertion failure messages. - - - - - Prefix used for the expected value line of a message - - - - - Prefix used for the actual value line of a message - - - - - Length of a message prefix - - - - - Construct a TextMessageWriter - - - - - Construct a TextMessageWriter, specifying a user message - and optional formatting arguments. - - - - - - - Method to write single line message with optional args, usually - written to precede the general failure message, at a given - indentation level. - - The indentation level of the message - The message to be written - Any arguments used in formatting the message - - - - Display Expected and Actual lines for a constraint. This - is called by MessageWriter's default implementation of - WriteMessageTo and provides the generic two-line display. - - The result of the constraint that failed - - - - Display Expected and Actual lines for given _values. This - method may be called by constraints that need more control over - the display of actual and expected _values than is provided - by the default implementation. - - The expected value - The actual value causing the failure - - - - Display Expected and Actual lines for given _values, including - a tolerance value on the expected line. - - The expected value - The actual value causing the failure - The tolerance within which the test was made - - - - Display the expected and actual string _values on separate lines. - If the mismatch parameter is >=0, an additional line is displayed - line containing a caret that points to the mismatch point. - - The expected string value - The actual string value - The point at which the strings don't match or -1 - If true, case is ignored in string comparisons - If true, clip the strings to fit the max line length - - - - Writes the text for an actual value. - - The actual value. - - - - Writes the text for a generalized value. - - The value. - - - - Writes the text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Write the generic 'Expected' line for a constraint - - The constraint that failed - - - - Write the generic 'Expected' line for a given value - - The expected value - - - - Write the generic 'Expected' line for a given value - and tolerance. - - The expected value - The tolerance within which the test was made - - - - Write the generic 'Actual' line for a constraint - - The ConstraintResult for which the actual value is to be written - - - - Write the generic 'Actual' line for a given value - - The actual value causing a failure - - - - Gets or sets the maximum line length for this writer - - - - - The current state of a work item - - - - - Ready to run or continue - - - - - Work Item is executing - - - - - Complete - - - - - Combines multiple filters so that a test must pass all - of them in order to pass this filter. - - - - - A base class for multi-part filters - - - - - Interface to be implemented by filters applied to tests. - The filter applies when running the test, after it has been - loaded, since this is the only time an ITest exists. - - - - - Unique Empty filter. - - - - - Determine if a particular test passes the filter criteria. The default - implementation checks the test itself, its parents and any descendants. - - Derived classes may override this method or any of the Match methods - to change the behavior of the filter. - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - Determine whether the test itself matches the filter criteria, without - examining either parents or descendants. This is overridden by each - different type of filter to perform the necessary tests. - - The test to which the filter is applied - True if the filter matches the any parent of the test - - - - Determine whether any ancestor of the test matches the filter criteria - - The test to which the filter is applied - True if the filter matches the an ancestor of the test - - - - Determine whether any descendant of the test matches the filter criteria. - - The test to be matched - True if at least one descendant matches the filter criteria - - - - Create a TestFilter instance from an xml representation. - - - - - Create a TestFilter from it's TNode representation - - - - - Adds an XML node - - True if recursive - The added XML node - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Indicates whether this is the EmptyFilter - - - - - Indicates whether this is a top-level filter, - not contained in any other filter. - - - - - Nested class provides an empty filter - one that always - returns true when called. It never matches explicitly. - - - - - Constructs an empty CompositeFilter - - - - - Constructs a CompositeFilter from an array of filters - - - - - - Adds a filter to the list of filters - - The filter to be added - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Return a list of the composing filters. - - - - - Gets the element name - - Element name - - - - Constructs an empty AndFilter - - - - - Constructs an AndFilter from an array of filters - - - - - - Checks whether the AndFilter is matched by a test - - The test to be matched - True if all the component filters pass, otherwise false - - - - Checks whether the AndFilter is matched by a test - - The test to be matched - True if all the component filters match, otherwise false - - - - Gets the element name - - Element name - - - - CategoryFilter is able to select or exclude tests - based on their categories. - - - - - - ValueMatchFilter selects tests based on some value, which - is expected to be contained in the test. - - - - - Construct a ValueMatchFilter for a single value. - - The value to be included. - - - - Match the input provided by the derived class - - The value to be matchedT - True for a match, false otherwise. - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Returns the value matched by the filter - used for testing - - - - - Indicates whether the value is a regular expression - - - - - Gets the element name - - Element name - - - - Construct a CategoryFilter using a single category name - - A category name - - - - Check whether the filter matches a test - - The test to be matched - - - - - Gets the element name - - Element name - - - - ClassName filter selects tests based on the class FullName - - - - - Construct a FullNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - FullName filter selects tests based on their FullName - - - - - Construct a FullNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - IdFilter selects tests based on their id - - - - - Construct an IdFilter for a single value - - The id the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - FullName filter selects tests based on their FullName - - - - - Construct a MethodNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - NotFilter negates the operation of another filter - - - - - Construct a not filter on another filter - - The filter to be negated - - - - Determine if a particular test passes the filter criteria. The default - implementation checks the test itself, its parents and any descendants. - - Derived classes may override this method or any of the Match methods - to change the behavior of the filter. - - The test to which the filter is applied - True if the test passes the filter, otherwise false - - - - Check whether the filter matches a test - - The test to be matched - True if it matches, otherwise false - - - - Determine if a test matches the filter expicitly. That is, it must - be a direct match of the test itself or one of it's children. - - The test to which the filter is applied - True if the test matches the filter explicityly, otherwise false - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Gets the base filter - - - - - Combines multiple filters so that a test must pass one - of them in order to pass this filter. - - - - - Constructs an empty OrFilter - - - - - Constructs an AndFilter from an array of filters - - - - - - Checks whether the OrFilter is matched by a test - - The test to be matched - True if any of the component filters pass, otherwise false - - - - Checks whether the OrFilter is matched by a test - - The test to be matched - True if any of the component filters match, otherwise false - - - - Gets the element name - - Element name - - - - PropertyFilter is able to select or exclude tests - based on their properties. - - - - - - Construct a PropertyFilter using a property name and expected value - - A property name - The expected value of the property - - - - Check whether the filter matches a test - - The test to be matched - - - - - Adds an XML node - - Parent node - True if recursive - The added XML node - - - - Gets the element name - - Element name - - - - TestName filter selects tests based on their Name - - - - - Construct a TestNameFilter for a single name - - The name the filter will recognize. - - - - Match a test against a single value. - - - - - Gets the element name - - Element name - - - - GenericMethodHelper is able to deduce the Type arguments for - a generic method from the actual arguments provided. - - - - - Construct a GenericMethodHelper for a method - - MethodInfo for the method to examine - - - - Return the type argments for the method, deducing them - from the arguments actually provided. - - The arguments to the method - An array of type arguments. - - - - InvalidTestFixtureException is thrown when an appropriate test - fixture constructor using the provided arguments cannot be found. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The message. - - - - Initializes a new instance of the class. - - The message. - The inner. - - - - InvalidTestFixtureException is thrown when an appropriate test - fixture constructor using the provided arguments cannot be found. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The message. - - - - Initializes a new instance of the class. - - The message. - The inner. - - - - The MethodWrapper class wraps a MethodInfo so that it may - be used in a platform-independent manner. - - - - - Construct a MethodWrapper for a Type and a MethodInfo. - - - - - Construct a MethodInfo for a given Type and method name. - - - - - Gets the parameters of the method. - - - - - - Returns the Type arguments of a generic method or the Type parameters of a generic method definition. - - - - - Replaces the type parameters of the method with the array of types provided and returns a new IMethodInfo. - - The type arguments to be used - A new IMethodInfo with the type arguments replaced - - - - Returns an array of custom attributes of the specified type applied to this method - - - - - Gets a value indicating whether one or more attributes of the spcified type are defined on the method. - - - - - Invokes the method, converting any TargetInvocationException to an NUnitException. - - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - Override ToString() so that error messages in NUnit's own tests make sense - - - - - Gets the Type from which this method was reflected. - - - - - Gets the MethodInfo for this method. - - - - - Gets the name of the method. - - - - - Gets a value indicating whether the method is abstract. - - - - - Gets a value indicating whether the method is public. - - - - - Gets a value indicating whether the method contains unassigned generic type parameters. - - - - - Gets a value indicating whether the method is a generic method. - - - - - Gets a value indicating whether the MethodInfo represents the definition of a generic method. - - - - - Gets the return Type of the method. - - - - - Thrown when an assertion failed. Here to preserve the inner - exception and hence its stack trace. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The error message that explains - the reason for the exception - - - - Initializes a new instance of the class. - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - The ParameterWrapper class wraps a ParameterInfo so that it may - be used in a platform-independent manner. - - - - - Construct a ParameterWrapper for a given method and parameter - - - - - - - Returns an array of custom attributes of the specified type applied to this method - - - - - Gets a value indicating whether one or more attributes of the specified type are defined on the parameter. - - - - - Gets a value indicating whether the parameter is optional - - - - - Gets an IMethodInfo representing the method for which this is a parameter. - - - - - Gets the underlying ParameterInfo - - - - - Gets the Type of the parameter - - - - - A PropertyBag represents a collection of name value pairs - that allows duplicate entries with the same key. Methods - are provided for adding a new pair as well as for setting - a key to a single value. All keys are strings but _values - may be of any type. Null _values are not permitted, since - a null entry represents the absence of the key. - - - - - Adds a key/value pair to the property set - - The key - The value - - - - Sets the value for a key, removing any other - _values that are already in the property set. - - - - - - - Gets a single value for a key, using the first - one if multiple _values are present and returning - null if the value is not found. - - - - - - - Gets a flag indicating whether the specified key has - any entries in the property set. - - The key to be checked - - True if their are _values present, otherwise false - - - - - Returns an XmlNode representating the current PropertyBag. - - Not used - An XmlNode representing the PropertyBag - - - - Returns an XmlNode representing the PropertyBag after - adding it as a child of the supplied parent node. - - The parent node. - Not used - - - - - Gets a collection containing all the keys in the property set - - - - - - Gets or sets the list of _values for a particular key - - - - - The PropertyNames class provides static constants for the - standard property ids that NUnit uses on tests. - - - - - The FriendlyName of the AppDomain in which the assembly is running - - - - - The selected strategy for joining parameter data into test cases - - - - - The process ID of the executing assembly - - - - - The stack trace from any data provider that threw - an exception. - - - - - The reason a test was not run - - - - - The author of the tests - - - - - The ApartmentState required for running the test - - - - - The categories applying to a test - - - - - The Description of a test - - - - - The number of threads to be used in running tests - - - - - The maximum time in ms, above which the test is considered to have failed - - - - - The ParallelScope associated with a test - - - - - The number of times the test should be repeated - - - - - Indicates that the test should be run on a separate thread - - - - - The culture to be set for a test - - - - - The UI culture to be set for a test - - - - - The type that is under test - - - - - The timeout value for the test - - - - - The test will be ignored until the given date - - - - - Randomizer returns a set of random _values in a repeatable - way, to allow re-running of tests if necessary. It extends - the .NET Random class, providing random values for a much - wider range of types. - - The class is used internally by the framework to generate - test case data and is also exposed for use by users through - the TestContext.Random property. - - - For consistency with the underlying Random Type, methods - returning a single value use the prefix "Next..." Those - without an argument return a non-negative value up to - the full positive range of the Type. Overloads are provided - for specifying a maximum or a range. Methods that return - arrays or strings use the prefix "Get..." to avoid - confusion with the single-value methods. - - - - - Default characters for random functions. - - Default characters are the English alphabet (uppercase & lowercase), arabic numerals, and underscore - - - - Get a Randomizer for a particular member, returning - one that has already been created if it exists. - This ensures that the same _values are generated - each time the tests are reloaded. - - - - - Get a randomizer for a particular parameter, returning - one that has already been created if it exists. - This ensures that the same values are generated - each time the tests are reloaded. - - - - - Create a new Randomizer using the next seed - available to ensure that each randomizer gives - a unique sequence of values. - - - - - - Default constructor - - - - - Construct based on seed value - - - - - - Returns a random unsigned int. - - - - - Returns a random unsigned int less than the specified maximum. - - - - - Returns a random unsigned int within a specified range. - - - - - Returns a non-negative random short. - - - - - Returns a non-negative random short less than the specified maximum. - - - - - Returns a non-negative random short within a specified range. - - - - - Returns a random unsigned short. - - - - - Returns a random unsigned short less than the specified maximum. - - - - - Returns a random unsigned short within a specified range. - - - - - Returns a random long. - - - - - Returns a random long less than the specified maximum. - - - - - Returns a non-negative random long within a specified range. - - - - - Returns a random ulong. - - - - - Returns a random ulong less than the specified maximum. - - - - - Returns a non-negative random long within a specified range. - - - - - Returns a random Byte - - - - - Returns a random Byte less than the specified maximum. - - - - - Returns a random Byte within a specified range - - - - - Returns a random SByte - - - - - Returns a random sbyte less than the specified maximum. - - - - - Returns a random sbyte within a specified range - - - - - Returns a random bool - - - - - Returns a random bool based on the probablility a true result - - - - - Returns a random double between 0.0 and the specified maximum. - - - - - Returns a random double within a specified range. - - - - - Returns a random float. - - - - - Returns a random float between 0.0 and the specified maximum. - - - - - Returns a random float within a specified range. - - - - - Returns a random enum value of the specified Type as an object. - - - - - Returns a random enum value of the specified Type. - - - - - Generate a random string based on the characters from the input string. - - desired length of output string. - string representing the set of characters from which to construct the resulting string - A random string of arbitrary length - - - - Generate a random string based on the characters from the input string. - - desired length of output string. - A random string of arbitrary length - Uses DefaultStringChars as the input character set - - - - Generate a random string based on the characters from the input string. - - A random string of the default length - Uses DefaultStringChars as the input character set - - - - Returns a random decimal. - - - - - Returns a random decimal between positive zero and the specified maximum. - - - - - Returns a random decimal within a specified range, which is not - permitted to exceed decimal.MaxVal in the current implementation. - - - A limitation of this implementation is that the range from min - to max must not exceed decimal.MaxVal. - - - - - Initial seed used to create randomizers for this run - - - - - Helper methods for inspecting a type by reflection. - - Many of these methods take ICustomAttributeProvider as an - argument to avoid duplication, even though certain attributes can - only appear on specific types of members, like MethodInfo or Type. - - In the case where a type is being examined for the presence of - an attribute, interface or named member, the Reflect methods - operate with the full name of the member being sought. This - removes the necessity of the caller having a reference to the - assembly that defines the item being sought and allows the - NUnit core to inspect assemblies that reference an older - version of the NUnit framework. - - - - - Examine a fixture type and return an array of methods having a - particular attribute. The array is order with base methods first. - - The type to examine - The attribute Type to look for - Specifies whether to search the fixture type inheritance chain - The array of methods found - - - - Examine a fixture type and return true if it has a method with - a particular attribute. - - The type to examine - The attribute Type to look for - True if found, otherwise false - - - - Invoke the default constructor on a Type - - The Type to be constructed - An instance of the Type - - - - Invoke a constructor on a Type with arguments - - The Type to be constructed - Arguments to the constructor - An instance of the Type - - - - Returns an array of types from an array of objects. - Used because the compact framework doesn't support - Type.GetTypeArray() - - An array of objects - An array of Types - - - - Invoke a parameterless method returning void on an object. - - A MethodInfo for the method to be invoked - The object on which to invoke the method - - - - Invoke a method, converting any TargetInvocationException to an NUnitException. - - A MethodInfo for the method to be invoked - The object on which to invoke the method - The argument list for the method - The return value from the invoked method - - - - Represents the result of running a single test case. - - - - - The TestResult class represents the result of a test. - - - - - The minimum duration for tests - - - - - Error message for when child tests have errors - - - - - Error message for when child tests are ignored - - - - - List of child results - - - - - Construct a test result given a Test - - The test to be used - - - - Returns the Xml representation of the result. - - If true, descendant results are included - An XmlNode representing the result - - - - Adds the XML representation of the result as a child of the - supplied parent node.. - - The parent node. - If true, descendant results are included - - - - - Adds a child result to this result, setting this result's - ResultState to Failure if the child result failed. - - The result to be added - - - - Set the result of the test - - The ResultState to use in the result - - - - Set the result of the test - - The ResultState to use in the result - A message associated with the result state - - - - Set the result of the test - - The ResultState to use in the result - A message associated with the result state - Stack trace giving the location of the command - - - - Set the test result based on the type of exception thrown - - The exception that was thrown - - - - Set the test result based on the type of exception thrown - - The exception that was thrown - THe FailureSite to use in the result - - - - RecordTearDownException appends the message and stacktrace - from an exception arising during teardown of the test - to any previously recorded information, so that any - earlier failure information is not lost. Note that - calling Assert.Ignore, Assert.Inconclusive, etc. during - teardown is treated as an error. If the current result - represents a suite, it may show a teardown error even - though all contained tests passed. - - The Exception to be recorded - - - - Adds a reason element to a node and returns it. - - The target node. - The new reason element. - - - - Adds a failure element to a node and returns it. - - The target node. - The new failure element. - - - - Gets the test with which this result is associated. - - - - - Gets the ResultState of the test result, which - indicates the success or failure of the test. - - - - - Gets the name of the test result - - - - - Gets the full name of the test result - - - - - Gets or sets the elapsed time for running the test in seconds - - - - - Gets or sets the time the test started running. - - - - - Gets or sets the time the test finished running. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. - - - - - Gets or sets the count of asserts executed - when running the test. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Indicates whether this result has any child results. - Test HasChildren before accessing Children to avoid - the creation of an empty collection. - - - - - Gets the collection of child results. - - - - - Gets a TextWriter, which will write output to be included in the result. - - - - - Gets any text output written to this result. - - - - - Construct a TestCaseResult based on a TestMethod - - A TestMethod to which the result applies. - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - Represents the result of running a test suite - - - - - Construct a TestSuiteResult base on a TestSuite - - The TestSuite to which the result applies - - - - Add a child result - - The child result to be added - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - StackFilter class is used to remove internal NUnit - entries from a stack trace so that the resulting - trace provides better information about the test. - - - - - Filters a raw stack trace and returns the result. - - The original stack trace - A filtered stack trace - - - - Provides methods to support legacy string comparison methods. - - - - - Compares two strings for equality, ignoring case if requested. - - The first string. - The second string.. - if set to true, the case of the letters in the strings is ignored. - Zero if the strings are equivalent, a negative number if strA is sorted first, a positive number if - strB is sorted first - - - - Compares two strings for equality, ignoring case if requested. - - The first string. - The second string.. - if set to true, the case of the letters in the strings is ignored. - True if the strings are equivalent, false if not. - - - - The TestCaseParameters class encapsulates method arguments and - other selected parameters needed for constructing - a parameterized test case. - - - - - TestParameters is the abstract base class for all classes - that know how to provide data for constructing a test. - - - - - Default Constructor creates an empty parameter set - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a ParameterSet from an object implementing ITestData - - - - - - Applies ParameterSet _values to the test itself. - - A test. - - - - The RunState for this set of parameters. - - - - - The arguments to be used in running the test, - which must match the method signature. - - - - - A name to be used for this test case in lieu - of the standard generated name containing - the argument list. - - - - - Gets the property dictionary for this test - - - - - The original arguments provided by the user, - used for display purposes. - - - - - The expected result to be returned - - - - - Default Constructor creates an empty parameter set - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a ParameterSet from an object implementing ITestCaseData - - - - - - The expected result of the test, which - must match the method return type. - - - - - Gets a value indicating whether an expected result was specified. - - - - - Helper class used to save and restore certain static or - singleton settings in the environment that affect tests - or which might be changed by the user tests. - - An internal class is used to hold settings and a stack - of these objects is pushed and popped as Save and Restore - are called. - - - - - Link to a prior saved context - - - - - Indicates that a stop has been requested - - - - - The event listener currently receiving notifications - - - - - The number of assertions for the current test - - - - - The current culture - - - - - The current UI culture - - - - - The current test result - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - An existing instance of TestExecutionContext. - - - - The current context, head of the list of saved contexts. - - - - - Clear the current context. This is provided to - prevent "leakage" of the CallContext containing - the current context back to any runners. - - - - - Record any changes in the environment made by - the test code in the execution context so it - will be passed on to lower level tests. - - - - - Set up the execution environment to match a context. - Note that we may be running on the same thread where the - context was initially created or on a different thread. - - - - - Increments the assert count by one. - - - - - Increments the assert count by a specified amount. - - - - - Gets the current context. - - The current context. - - - - Gets or sets the current test - - - - - The time the current test started execution - - - - - The time the current test started in Ticks - - - - - Gets or sets the current test result - - - - - Gets a TextWriter that will send output to the current test result. - - - - - The current test object - that is the user fixture - object on which tests are being executed. - - - - - Get or set the working directory - - - - - Get or set indicator that run should stop on the first error - - - - - Gets an enum indicating whether a stop has been requested. - - - - - The current test event listener - - - - - The current WorkItemDispatcher - - - - - The ParallelScope to be used by tests running in this context. - For builds with out the parallel feature, it has no effect. - - - - - Gets the RandomGenerator specific to this Test - - - - - Gets the assert count. - - The assert count. - - - - Gets or sets the test case timeout value - - - - - Gets a list of ITestActions set by upstream tests - - - - - Saves or restores the CurrentCulture - - - - - Saves or restores the CurrentUICulture - - - - - Enumeration indicating whether the tests are - running normally or being cancelled. - - - - - Running normally with no stop requested - - - - - A graceful stop has been requested - - - - - A forced stop has been requested - - - - - The TestCaseParameters class encapsulates method arguments and - other selected parameters needed for constructing - a parameterized test case. - - - - - Default Constructor creates an empty parameter set - - - - - Construct a non-runnable ParameterSet, specifying - the provider exception that made it invalid. - - - - - Construct a parameter set with a list of arguments - - - - - - Construct a ParameterSet from an object implementing ITestCaseData - - - - - - Type arguments used to create a generic fixture instance - - - - - TestListener provides an implementation of ITestListener that - does nothing. It is used only through its NULL property. - - - - - Called when a test has just started - - The test that is starting - - - - Called when a test case has finished - - The result of the test - - - - Construct a new TestListener - private so it may not be used. - - - - - Get a listener that does nothing - - - - - TestNameGenerator is able to create test names according to - a coded pattern. - - - - - Construct a TestNameGenerator - - The pattern used by this generator. - - - - Get the display name for a TestMethod and it's arguments - - A TestMethod - The display name - - - - Get the display name for a TestMethod and it's arguments - - A TestMethod - Arguments to be used - The display name - - - - Get the display name for a MethodInfo - - A MethodInfo - The display name - - - - Get the display name for a method with args - - A MethodInfo - Argument list for the method - The display name - - - - TestProgressReporter translates ITestListener events into - the async callbacks that are used to inform the client - software about the progress of a test run. - - - - - Initializes a new instance of the class. - - The callback handler to be used for reporting progress. - - - - Called when a test has just started - - The test that is starting - - - - Called when a test has finished. Sends a result summary to the callback. - to - - The result of the test - - - - Returns the parent test item for the targer test item if it exists - - - parent test item - - - - Makes a string safe for use as an attribute, replacing - characters characters that can't be used with their - corresponding xml representations. - - The string to be used - A new string with the _values replaced - - - - ParameterizedFixtureSuite serves as a container for the set of test - fixtures created from a given Type using various parameters. - - - - - TestSuite represents a composite test, which contains other tests. - - - - - The Test abstract class represents a test within the framework. - - - - - Static value to seed ids. It's started at 1000 so any - uninitialized ids will stand out. - - - - - The SetUp methods. - - - - - The teardown methods - - - - - Constructs a test given its name - - The name of the test - - - - Constructs a test given the path through the - test hierarchy to its parent and a name. - - The parent tests full name - The name of the test - - - - TODO: Documentation needed for constructor - - - - - - Construct a test from a MethodInfo - - - - - - Creates a TestResult for this test. - - A TestResult suitable for this type of test. - - - - Modify a newly constructed test by applying any of NUnit's common - attributes, based on a supplied ICustomAttributeProvider, which is - usually the reflection element from which the test was constructed, - but may not be in some instances. The attributes retrieved are - saved for use in subsequent operations. - - An object deriving from MemberInfo - - - - Modify a newly constructed test by applying any of NUnit's common - attributes, based on a supplied ICustomAttributeProvider, which is - usually the reflection element from which the test was constructed, - but may not be in some instances. The attributes retrieved are - saved for use in subsequent operations. - - An object deriving from MemberInfo - - - - Add standard attributes and members to a test node. - - - - - - - Returns the Xml representation of the test - - If true, include child tests recursively - - - - - Returns an XmlNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Compares this test to another test for sorting purposes - - The other test - Value of -1, 0 or +1 depending on whether the current test is less than, equal to or greater than the other test - - - - Gets or sets the id of the test - - - - - - Gets or sets the name of the test - - - - - Gets or sets the fully qualified name of the test - - - - - - Gets the name of the class containing this test. Returns - null if the test is not associated with a class. - - - - - Gets the name of the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Gets the TypeInfo of the fixture used in running this test - or null if no fixture type is associated with it. - - - - - Gets a MethodInfo for the method implementing this test. - Returns null if the test is not implemented as a method. - - - - - Whether or not the test should be run - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Gets a string representing the type of test. Used as an attribute - value in the XML representation of a test and has no other - function in the framework. - - - - - Gets a count of test cases represented by - or contained under this test. - - - - - Gets the properties for this test - - - - - Returns true if this is a TestSuite - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets the parent as a Test object. - Used by the core to set the parent. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets or sets a fixture object for running this test. - - - - - Static prefix used for ids in this AppDomain. - Set by FrameworkController. - - - - - Gets or Sets the Int value representing the seed for the RandomGenerator - - - - - - Our collection of child tests - - - - - Initializes a new instance of the class. - - The name of the suite. - - - - Initializes a new instance of the class. - - Name of the parent suite. - The name of the suite. - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - Sorts tests under this suite. - - - - - Adds a test to the suite. - - The test. - - - - Overridden to return a TestSuiteResult. - - A TestResult for this test. - - - - Returns an XmlNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Check that setup and teardown methods marked by certain attributes - meet NUnit's requirements and mark the tests not runnable otherwise. - - The attribute type to check for - - - - Gets this test's child tests - - The list of child tests - - - - Gets a count of test cases represented by - or contained under this test. - - - - - - The arguments to use in creating the fixture - - - - - Set to true to suppress sorting this suite's contents - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Initializes a new instance of the class. - - The ITypeInfo for the type that represents the suite. - - - - Gets a string representing the type of test - - - - - - ParameterizedMethodSuite holds a collection of individual - TestMethods with their arguments applied. - - - - - Construct from a MethodInfo - - - - - - Gets a string representing the type of test - - - - - - SetUpFixture extends TestSuite and supports - Setup and TearDown methods. - - - - - Initializes a new instance of the class. - - The type. - - - - TestAssembly is a TestSuite that represents the execution - of tests in a managed assembly. - - - - - Initializes a new instance of the class - specifying the Assembly and the path from which it was loaded. - - The assembly this test represents. - The path used to load the assembly. - - - - Initializes a new instance of the class - for a path which could not be loaded. - - The path used to load the assembly. - - - - Gets the Assembly represented by this instance. - - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - TestFixture is a surrogate for a user test fixture class, - containing one or more tests. - - - - - Initializes a new instance of the class. - - Type of the fixture. - - - - The TestMethod class represents a Test implemented as a method. - - - - - The ParameterSet used to create this test method - - - - - Initializes a new instance of the class. - - The method to be used as a test. - - - - Initializes a new instance of the class. - - The method to be used as a test. - The suite or fixture to which the new test will be added - - - - Overridden to return a TestCaseResult. - - A TestResult for this test. - - - - Returns a TNode representing the current result after - adding it as a child of the supplied parent node. - - The parent node. - If true, descendant results are included - - - - - Gets a bool indicating whether the current test - has any descendant tests. - - - - - Gets this test's child tests - - A list of child tests - - - - Gets the name used for the top-level element in the - XML representation of this test - - - - - Returns the name of the method - - - - - TypeHelper provides static methods that operate on Types. - - - - - A special value, which is used to indicate that BestCommonType() method - was unable to find a common type for the specified arguments. - - - - - Gets the display name for a Type as used by NUnit. - - The Type for which a display name is needed. - The display name for the Type - - - - Gets the display name for a Type as used by NUnit. - - The Type for which a display name is needed. - The arglist provided. - The display name for the Type - - - - Returns the best fit for a common type to be used in - matching actual arguments to a methods Type parameters. - - The first type. - The second type. - Either type1 or type2, depending on which is more general. - - - - Determines whether the specified type is numeric. - - The type to be examined. - - true if the specified type is numeric; otherwise, false. - - - - - Convert an argument list to the required parameter types. - Currently, only widening numeric conversions are performed. - - An array of args to be converted - A ParameterInfo[] whose types will be used as targets - - - - Determines whether this instance can deduce type args for a generic type from the supplied arguments. - - The type to be examined. - The arglist. - The type args to be used. - - true if this the provided args give sufficient information to determine the type args to be used; otherwise, false. - - - - - Gets the _values for an enumeration, using Enum.GetTypes - where available, otherwise through reflection. - - - - - - - Gets the ids of the _values for an enumeration, - using Enum.GetNames where available, otherwise - through reflection. - - - - - - - The TypeWrapper class wraps a Type so it may be used in - a platform-independent manner. - - - - - Construct a TypeWrapper for a specified Type. - - - - - Returns true if the Type wrapped is T - - - - - Get the display name for this type - - - - - Get the display name for an object of this type, constructed with the specified args. - - - - - Returns a new ITypeInfo representing an instance of this generic Type using the supplied Type arguments - - - - - Returns a Type representing a generic type definition from which this Type can be constructed. - - - - - Returns an array of custom attributes of the specified type applied to this type - - - - - Returns a value indicating whether the type has an attribute of the specified type. - - - - - - - - Returns a flag indicating whether this type has a method with an attribute of the specified type. - - - - - - - Returns an array of IMethodInfos for methods of this Type - that match the specified flags. - - - - - Returns a value indicating whether this Type has a public constructor taking the specified argument Types. - - - - - Construct an object of this Type, using the specified arguments. - - - - - Override ToString() so that error messages in NUnit's own tests make sense - - - - - Gets the underlying Type on which this TypeWrapper is based. - - - - - Gets the base type of this type as an ITypeInfo - - - - - Gets the Name of the Type - - - - - Gets the FullName of the Type - - - - - Gets the assembly in which the type is declared - - - - - Gets the namespace of the Type - - - - - Gets a value indicating whether the type is abstract. - - - - - Gets a value indicating whether the Type is a generic Type - - - - - Gets a value indicating whether the Type has generic parameters that have not been replaced by specific Types. - - - - - Gets a value indicating whether the Type is a generic Type definition - - - - - Gets a value indicating whether the type is sealed. - - - - - Gets a value indicating whether this type represents a static class. - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the supplied argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable to the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable to the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a superset of the collection supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the regular expression supplied as an argument. - - - - - Returns a constraint that tests whether the actual value falls - inclusively within a specified range. - - from must be less than or equal to true - Inclusive beginning of the range. Must be less than or equal to to. - Inclusive end of the range. Must be greater than or equal to from. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - The Iz class is a synonym for Is intended for use in VB, - which regards Is as a keyword. - - - - - The List class is a helper class with properties and methods - that supply a number of constraints used with lists and collections. - - - - - List.Map returns a ListMapper, which can be used to map - the original collection to another collection. - - - - - - - ListMapper is used to transform a collection used as an actual argument - producing another collection to be used in the assertion. - - - - - Construct a ListMapper based on a collection - - The collection to be transformed - - - - Produces a collection containing all the _values of a property - - The collection of property _values - - - - - The SpecialValue enum is used to represent TestCase arguments - that cannot be used as arguments to an Attribute. - - - - - Null represents a null value, which cannot be used as an - argument to an attriute under .NET 1.x - - - - - Basic Asserts on strings. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - - - - Asserts that a string is not found within another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - - - - Asserts that two strings are not equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that two strings are not equal, without regard to case. - - The expected string - The actual string - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - - - - The TestCaseData class represents a set of arguments - and other parameter info to be used for a parameterized - test case. It is derived from TestCaseParameters and adds a - fluent syntax for use in initializing the test case. - - - - - Initializes a new instance of the class. - - The arguments. - - - - Initializes a new instance of the class. - - The argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - The third argument. - - - - Sets the expected result for the test - - The expected result - A modified TestCaseData - - - - Sets the name of the test case - - The modified TestCaseData instance - - - - Sets the description for the test case - being constructed. - - The description. - The modified TestCaseData instance. - - - - Applies a category to the test - - - - - - - Applies a named property to the test - - - - - - - - Applies a named property to the test - - - - - - - - Applies a named property to the test - - - - - - - - Marks the test case as explicit. - - - - - Marks the test case as explicit, specifying the reason. - - - - - Ignores this TestCase, specifying the reason. - - The reason. - - - - - Provide the context information of the current test. - This is an adapter for the internal ExecutionContext - class, hiding the internals from the user test. - - - - - Construct a TestContext for an ExecutionContext - - The ExecutionContext to adapt - - - Write the string representation of a boolean value to the current result - - - Write a char to the current result - - - Write a char array to the current result - - - Write the string representation of a double to the current result - - - Write the string representation of an Int32 value to the current result - - - Write the string representation of an Int64 value to the current result - - - Write the string representation of a decimal value to the current result - - - Write the string representation of an object to the current result - - - Write the string representation of a Single value to the current result - - - Write a string to the current result - - - Write the string representation of a UInt32 value to the current result - - - Write the string representation of a UInt64 value to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a formatted string to the current result - - - Write a line terminator to the current result - - - Write the string representation of a boolean value to the current result followed by a line terminator - - - Write a char to the current result followed by a line terminator - - - Write a char array to the current result followed by a line terminator - - - Write the string representation of a double to the current result followed by a line terminator - - - Write the string representation of an Int32 value to the current result followed by a line terminator - - - Write the string representation of an Int64 value to the current result followed by a line terminator - - - Write the string representation of a decimal value to the current result followed by a line terminator - - - Write the string representation of an object to the current result followed by a line terminator - - - Write the string representation of a Single value to the current result followed by a line terminator - - - Write a string to the current result followed by a line terminator - - - Write the string representation of a UInt32 value to the current result followed by a line terminator - - - Write the string representation of a UInt64 value to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - Write a formatted string to the current result followed by a line terminator - - - - Get the current test context. This is created - as needed. The user may save the context for - use within a test, but it should not be used - outside the test for which it is created. - - - - - Gets a TextWriter that will send output to the current test result. - - - - - Get a representation of the current test. - - - - - Gets a Representation of the TestResult for the current test. - - - - - Gets the directory to be used for outputting files created - by this test run. - - - - - Gets the random generator. - - - The random generator. - - - - - TestAdapter adapts a Test for consumption by - the user test code. - - - - - Construct a TestAdapter for a Test - - The Test to be adapted - - - - Gets the unique Id of a test - - - - - The name of the test, which may or may not be - the same as the method name. - - - - - The name of the method representing the test. - - - - - The FullName of the test - - - - - The ClassName of the test - - - - - The properties of the test. - - - - - ResultAdapter adapts a TestResult for consumption by - the user test code. - - - - - Construct a ResultAdapter for a TestResult - - The TestResult to be adapted - - - - Gets a ResultState representing the outcome of the test. - - - - - Gets the message associated with a test - failure or with not running the test - - - - - Gets any stacktrace associated with an - error or failure. - - - - - Gets the number of test cases that failed - when running the test and all its children. - - - - - Gets the number of test cases that passed - when running the test and all its children. - - - - - Gets the number of test cases that were skipped - when running the test and all its children. - - - - - Gets the number of test cases that were inconclusive - when running the test and all its children. - - - - - The TestFixtureData class represents a set of arguments - and other parameter info to be used for a parameterized - fixture. It is derived from TestFixtureParameters and adds a - fluent syntax for use in initializing the fixture. - - - - - Initializes a new instance of the class. - - The arguments. - - - - Initializes a new instance of the class. - - The argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - The third argument. - - - - Marks the test fixture as explicit. - - - - - Marks the test fixture as explicit, specifying the reason. - - - - - Ignores this TestFixture, specifying the reason. - - The reason. - - - - - Helper class with properties and methods that supply - constraints that operate on exceptions. - - - - - Creates a constraint specifying the exact type of exception expected - - - - - Creates a constraint specifying the exact type of exception expected - - - - - Creates a constraint specifying the type of exception expected - - - - - Creates a constraint specifying the type of exception expected - - - - - Creates a constraint specifying an expected exception - - - - - Creates a constraint specifying an exception with a given InnerException - - - - - Creates a constraint specifying an expected TargetInvocationException - - - - - Creates a constraint specifying an expected ArgumentException - - - - - Creates a constraint specifying an expected ArgumentNUllException - - - - - Creates a constraint specifying an expected InvalidOperationException - - - - - Creates a constraint specifying that no exception is thrown - - - - diff --git a/packages/repositories.config b/packages/repositories.config deleted file mode 100644 index 6e2a0572c..000000000 --- a/packages/repositories.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..06e403ebb --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,16 @@ + + + + true + true + false + + + + + + + + + + diff --git a/src/StackExchange.Redis/APITypes/ClientKillFilter.cs b/src/StackExchange.Redis/APITypes/ClientKillFilter.cs new file mode 100644 index 000000000..3d1883549 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/ClientKillFilter.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Net; + +namespace StackExchange.Redis; + +/// +/// Filter determining which Redis clients to kill. +/// +/// +public class ClientKillFilter +{ + /// + /// Filter arguments builder for `CLIENT KILL`. + /// + public ClientKillFilter() { } + + /// + /// The ID of the client to kill. + /// + public long? Id { get; private set; } + + /// + /// The type of client. + /// + public ClientType? ClientType { get; private set; } + + /// + /// The authenticated ACL username. + /// + public string? Username { get; private set; } + + /// + /// The endpoint to kill. + /// + public EndPoint? Endpoint { get; private set; } + + /// + /// The server endpoint to kill. + /// + public EndPoint? ServerEndpoint { get; private set; } + + /// + /// Whether to skip the current connection. + /// + public bool? SkipMe { get; private set; } + + /// + /// Age of connection in seconds. + /// + public long? MaxAgeInSeconds { get; private set; } + + /// + /// Sets client id filter. + /// + /// Id of the client to kill. + public ClientKillFilter WithId(long? id) + { + Id = id; + return this; + } + + /// + /// Sets client type filter. + /// + /// The type of the client. + public ClientKillFilter WithClientType(ClientType? clientType) + { + ClientType = clientType; + return this; + } + + /// + /// Sets the username filter. + /// + /// Authenticated ACL username. + public ClientKillFilter WithUsername(string? username) + { + Username = username; + return this; + } + + /// + /// Set the endpoint filter. + /// + /// The endpoint to kill. + public ClientKillFilter WithEndpoint(EndPoint? endpoint) + { + Endpoint = endpoint; + return this; + } + + /// + /// Set the server endpoint filter. + /// + /// The server endpoint to kill. + public ClientKillFilter WithServerEndpoint(EndPoint? serverEndpoint) + { + ServerEndpoint = serverEndpoint; + return this; + } + + /// + /// Set the skipMe filter (whether to skip the current connection). + /// + /// Whether to skip the current connection. + public ClientKillFilter WithSkipMe(bool? skipMe) + { + SkipMe = skipMe; + return this; + } + + /// + /// Set the MaxAgeInSeconds filter. + /// + /// Age of connection in seconds. + public ClientKillFilter WithMaxAgeInSeconds(long? maxAgeInSeconds) + { + MaxAgeInSeconds = maxAgeInSeconds; + return this; + } + + internal List ToList(bool withReplicaCommands) + { + var parts = new List(15) + { + RedisLiterals.KILL, + }; + if (Id != null) + { + parts.Add(RedisLiterals.ID); + parts.Add(Id.Value); + } + if (ClientType != null) + { + parts.Add(RedisLiterals.TYPE); + switch (ClientType.Value) + { + case Redis.ClientType.Normal: + parts.Add(RedisLiterals.normal); + break; + case Redis.ClientType.Replica: + parts.Add(withReplicaCommands ? RedisLiterals.replica : RedisLiterals.slave); + break; + case Redis.ClientType.PubSub: + parts.Add(RedisLiterals.pubsub); + break; + default: + throw new ArgumentOutOfRangeException(nameof(ClientType)); + } + } + if (Username != null) + { + parts.Add(RedisLiterals.USERNAME); + parts.Add(Username); + } + if (Endpoint != null) + { + parts.Add(RedisLiterals.ADDR); + parts.Add((RedisValue)Format.ToString(Endpoint)); + } + if (ServerEndpoint != null) + { + parts.Add(RedisLiterals.LADDR); + parts.Add((RedisValue)Format.ToString(ServerEndpoint)); + } + if (SkipMe != null) + { + parts.Add(RedisLiterals.SKIPME); + parts.Add(SkipMe.Value ? RedisLiterals.yes : RedisLiterals.no); + } + if (MaxAgeInSeconds != null) + { + parts.Add(RedisLiterals.MAXAGE); + parts.Add(MaxAgeInSeconds); + } + return parts; + } +} diff --git a/src/StackExchange.Redis/APITypes/GeoEntry.cs b/src/StackExchange.Redis/APITypes/GeoEntry.cs new file mode 100644 index 000000000..b9ecb8d5b --- /dev/null +++ b/src/StackExchange.Redis/APITypes/GeoEntry.cs @@ -0,0 +1,76 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// Describes a GeoEntry element with the corresponding value. +/// GeoEntries are stored in redis as SortedSetEntries. +/// +public readonly struct GeoEntry : IEquatable +{ + /// + /// The name of the GeoEntry. + /// + public RedisValue Member { get; } + + /// + /// Describes the longitude and latitude of a GeoEntry. + /// + public GeoPosition Position { get; } + + /// + /// Initializes a GeoEntry value. + /// + /// The longitude position to use. + /// The latitude position to use. + /// The value to store for this position. + public GeoEntry(double longitude, double latitude, RedisValue member) + { + Member = member; + Position = new GeoPosition(longitude, latitude); + } + + /// + /// The longitude of the GeoEntry. + /// + public double Longitude => Position.Longitude; + + /// + /// The latitude of the GeoEntry. + /// + public double Latitude => Position.Latitude; + + /// + /// A "({Longitude},{Latitude})={Member}" string representation of this entry. + /// + public override string ToString() => $"({Longitude},{Latitude})={Member}"; + + /// + public override int GetHashCode() => Position.GetHashCode() ^ Member.GetHashCode(); + + /// + /// Compares two values for equality. + /// + /// The to compare to. + public override bool Equals(object? obj) => obj is GeoEntry geObj && Equals(geObj); + + /// + /// Compares two values for equality. + /// + /// The to compare to. + public bool Equals(GeoEntry other) => this == other; + + /// + /// Compares two values for equality. + /// + /// The first entry to compare. + /// The second entry to compare. + public static bool operator ==(GeoEntry x, GeoEntry y) => x.Position == y.Position && x.Member == y.Member; + + /// + /// Compares two values for non-equality. + /// + /// The first entry to compare. + /// The second entry to compare. + public static bool operator !=(GeoEntry x, GeoEntry y) => x.Position != y.Position || x.Member != y.Member; +} diff --git a/src/StackExchange.Redis/APITypes/GeoPosition.cs b/src/StackExchange.Redis/APITypes/GeoPosition.cs new file mode 100644 index 000000000..6e53b3e32 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/GeoPosition.cs @@ -0,0 +1,77 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// Describes the longitude and latitude of a GeoEntry. +/// +public readonly struct GeoPosition : IEquatable +{ + internal static string GetRedisUnit(GeoUnit unit) => unit switch + { + GeoUnit.Meters => "m", + GeoUnit.Kilometers => "km", + GeoUnit.Miles => "mi", + GeoUnit.Feet => "ft", + _ => throw new ArgumentOutOfRangeException(nameof(unit)), + }; + + /// + /// The Latitude of the GeoPosition. + /// + public double Latitude { get; } + + /// + /// The Longitude of the GeoPosition. + /// + public double Longitude { get; } + + /// + /// Creates a new GeoPosition. + /// + public GeoPosition(double longitude, double latitude) + { + Longitude = longitude; + Latitude = latitude; + } + + /// + /// A "{long} {lat}" string representation of this position. + /// + public override string ToString() => string.Format("{0} {1}", Longitude, Latitude); + + /// + /// See . + /// Diagonals not an issue in the case of lat/long. + /// + /// + /// Diagonals are not an issue in the case of lat/long. + /// + public override int GetHashCode() => Longitude.GetHashCode() ^ Latitude.GetHashCode(); + + /// + /// Compares two values for equality. + /// + /// The to compare to. + public override bool Equals(object? obj) => obj is GeoPosition gpObj && Equals(gpObj); + + /// + /// Compares two values for equality. + /// + /// The to compare to. + public bool Equals(GeoPosition other) => this == other; + + /// + /// Compares two values for equality. + /// + /// The first position to compare. + /// The second position to compare. + public static bool operator ==(GeoPosition x, GeoPosition y) => x.Longitude == y.Longitude && x.Latitude == y.Latitude; + + /// + /// Compares two values for non-equality. + /// + /// The first position to compare. + /// The second position to compare. + public static bool operator !=(GeoPosition x, GeoPosition y) => x.Longitude != y.Longitude || x.Latitude != y.Latitude; +} diff --git a/src/StackExchange.Redis/APITypes/GeoRadiusOptions.cs b/src/StackExchange.Redis/APITypes/GeoRadiusOptions.cs new file mode 100644 index 000000000..d21254fcd --- /dev/null +++ b/src/StackExchange.Redis/APITypes/GeoRadiusOptions.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; + +namespace StackExchange.Redis; + +/// +/// GeoRadius command options. +/// +[Flags] +public enum GeoRadiusOptions +{ + /// + /// No Options. + /// + None = 0, + + /// + /// Redis will return the coordinates of any results. + /// + WithCoordinates = 1, + + /// + /// Redis will return the distance from center for all results. + /// + WithDistance = 2, + + /// + /// Redis will return the geo hash value as an integer. (This is the score in the sorted set). + /// + WithGeoHash = 4, + + /// + /// Populates the commonly used values from the entry (the integer hash is not returned as it is not commonly useful). + /// + Default = WithCoordinates | WithDistance, +} + +internal static class GeoRadiusOptionsExtensions +{ + internal static void AddArgs(this GeoRadiusOptions options, List values) + { + if ((options & GeoRadiusOptions.WithCoordinates) != 0) + { + values.Add(RedisLiterals.WITHCOORD); + } + if ((options & GeoRadiusOptions.WithDistance) != 0) + { + values.Add(RedisLiterals.WITHDIST); + } + if ((options & GeoRadiusOptions.WithGeoHash) != 0) + { + values.Add(RedisLiterals.WITHHASH); + } + } +} diff --git a/src/StackExchange.Redis/APITypes/GeoRadiusResult.cs b/src/StackExchange.Redis/APITypes/GeoRadiusResult.cs new file mode 100644 index 000000000..952ca1625 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/GeoRadiusResult.cs @@ -0,0 +1,48 @@ +namespace StackExchange.Redis; + +/// +/// The result of a GeoRadius command. +/// +public readonly struct GeoRadiusResult +{ + /// + /// Indicate the member being represented. + /// + public override string ToString() => Member.ToString(); + + /// + /// The matched member. + /// + public RedisValue Member { get; } + + /// + /// The distance of the matched member from the center of the geo radius command. + /// + public double? Distance { get; } + + /// + /// The hash value of the matched member as an integer. (The key in the sorted set). + /// + /// Note that this is not the same as the hash returned from GeoHash. + public long? Hash { get; } + + /// + /// The coordinates of the matched member. + /// + public GeoPosition? Position { get; } + + /// + /// Returns a new GeoRadiusResult. + /// + /// The value from the result. + /// The distance from the result. + /// The hash of the result. + /// The GeoPosition of the result. + public GeoRadiusResult(in RedisValue member, double? distance, long? hash, GeoPosition? position) + { + Member = member; + Distance = distance; + Hash = hash; + Position = position; + } +} diff --git a/src/StackExchange.Redis/APITypes/GeoSearchShape.cs b/src/StackExchange.Redis/APITypes/GeoSearchShape.cs new file mode 100644 index 000000000..f2879ccc1 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/GeoSearchShape.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; + +namespace StackExchange.Redis; + +/// +/// A Shape that you can use for a GeoSearch. +/// +public abstract class GeoSearchShape +{ + /// + /// The unit to use for creating the shape. + /// + protected GeoUnit Unit { get; } + + /// + /// The number of shape arguments. + /// + internal abstract int ArgCount { get; } + + /// + /// constructs a . + /// + /// The geography unit to use. + public GeoSearchShape(GeoUnit unit) + { + Unit = unit; + } + + internal abstract void AddArgs(List args); +} + +/// +/// A circle drawn on a map bounding. +/// +public class GeoSearchCircle : GeoSearchShape +{ + private readonly double _radius; + + /// + /// Creates a Shape. + /// + /// The radius of the circle. + /// The distance unit the circle will use, defaults to Meters. + public GeoSearchCircle(double radius, GeoUnit unit = GeoUnit.Meters) : base(unit) + { + _radius = radius; + } + + internal sealed override int ArgCount => 3; + + /// + /// Gets the s for this shape. + /// + internal sealed override void AddArgs(List args) + { + args.Add(RedisLiterals.BYRADIUS); + args.Add(_radius); + args.Add(Unit.ToLiteral()); + } +} + +/// +/// A box drawn on a map. +/// +public class GeoSearchBox : GeoSearchShape +{ + private readonly double _height; + + private readonly double _width; + + /// + /// Initializes a GeoBox. + /// + /// The height of the box. + /// The width of the box. + /// The distance unit the box will use, defaults to Meters. + public GeoSearchBox(double height, double width, GeoUnit unit = GeoUnit.Meters) : base(unit) + { + _height = height; + _width = width; + } + + internal sealed override int ArgCount => 4; + + internal sealed override void AddArgs(List args) + { + args.Add(RedisLiterals.BYBOX); + args.Add(_width); + args.Add(_height); + args.Add(Unit.ToLiteral()); + } +} diff --git a/src/StackExchange.Redis/APITypes/HashEntry.cs b/src/StackExchange.Redis/APITypes/HashEntry.cs new file mode 100644 index 000000000..c985aeb68 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/HashEntry.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace StackExchange.Redis; + +/// +/// Describes a hash-field (a name/value pair). +/// +public readonly struct HashEntry : IEquatable +{ + internal readonly RedisValue name, value; + + /// + /// Initializes a value. + /// + /// The name for this hash entry. + /// The value for this hash entry. + public HashEntry(RedisValue name, RedisValue value) + { + this.name = name; + this.value = value; + } + + /// + /// The name of the hash field. + /// + public RedisValue Name => name; + + /// + /// The value of the hash field. + /// + public RedisValue Value => value; + + /// + /// The name of the hash field. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Please use Name", false)] + public RedisValue Key => name; + + /// + /// Converts to a key/value pair. + /// + /// The to create a from. + public static implicit operator KeyValuePair(HashEntry value) => + new KeyValuePair(value.name, value.value); + + /// + /// Converts from a key/value pair. + /// + /// The to get a from. + public static implicit operator HashEntry(KeyValuePair value) => + new HashEntry(value.Key, value.Value); + + /// + /// A "{name}: {value}" string representation of this entry. + /// + public override string ToString() => name + ": " + value; + + /// + public override int GetHashCode() => name.GetHashCode() ^ value.GetHashCode(); + + /// + /// Compares two values for equality. + /// + /// The to compare to. + public override bool Equals(object? obj) => obj is HashEntry heObj && Equals(heObj); + + /// + /// Compares two values for equality. + /// + /// The to compare to. + public bool Equals(HashEntry other) => name == other.name && value == other.value; + + /// + /// Compares two values for equality. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(HashEntry x, HashEntry y) => x.name == y.name && x.value == y.value; + + /// + /// Compares two values for non-equality. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(HashEntry x, HashEntry y) => x.name != y.name || x.value != y.value; +} diff --git a/src/StackExchange.Redis/APITypes/LCSMatchResult.cs b/src/StackExchange.Redis/APITypes/LCSMatchResult.cs new file mode 100644 index 000000000..fdeea89ff --- /dev/null +++ b/src/StackExchange.Redis/APITypes/LCSMatchResult.cs @@ -0,0 +1,72 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// The result of a LongestCommonSubsequence command with IDX feature. +/// Returns a list of the positions of each sub-match. +/// +public readonly struct LCSMatchResult +{ + internal static LCSMatchResult Null { get; } = new LCSMatchResult(Array.Empty(), 0); + + /// + /// Whether this match result contains any matches. + /// + public bool IsEmpty => LongestMatchLength == 0 && (Matches is null || Matches.Length == 0); + + /// + /// The matched positions of all the sub-matched strings. + /// + public LCSMatch[] Matches { get; } + + /// + /// The length of the longest match. + /// + public long LongestMatchLength { get; } + + /// + /// Returns a new . + /// + /// The matched positions in each string. + /// The length of the match. + internal LCSMatchResult(LCSMatch[] matches, long matchLength) + { + Matches = matches; + LongestMatchLength = matchLength; + } + + /// + /// Represents a sub-match of the longest match. i.e first indexes the matched substring in each string. + /// + public readonly struct LCSMatch + { + /// + /// The first index of the matched substring in the first string. + /// + public long FirstStringIndex { get; } + + /// + /// The first index of the matched substring in the second string. + /// + public long SecondStringIndex { get; } + + /// + /// The length of the match. + /// + public long Length { get; } + + /// + /// Returns a new Match. + /// + /// The first index of the matched substring in the first string. + /// The first index of the matched substring in the second string. + /// The length of the match. + internal LCSMatch(long firstStringIndex, long secondStringIndex, long length) + { + FirstStringIndex = firstStringIndex; + SecondStringIndex = secondStringIndex; + Length = length; + } + } +} diff --git a/src/StackExchange.Redis/APITypes/LatencyHistoryEntry.cs b/src/StackExchange.Redis/APITypes/LatencyHistoryEntry.cs new file mode 100644 index 000000000..003708e6a --- /dev/null +++ b/src/StackExchange.Redis/APITypes/LatencyHistoryEntry.cs @@ -0,0 +1,47 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// A latency entry as reported by the built-in LATENCY HISTORY command. +/// +public readonly struct LatencyHistoryEntry +{ + internal static readonly ResultProcessor ToArray = new Processor(); + + private sealed class Processor : ArrayResultProcessor + { + protected override bool TryParse(in RawResult raw, out LatencyHistoryEntry parsed) + { + if (raw.Resp2TypeArray == ResultType.Array) + { + var items = raw.GetItems(); + if (items.Length >= 2 + && items[0].TryGetInt64(out var timestamp) + && items[1].TryGetInt64(out var duration)) + { + parsed = new LatencyHistoryEntry(timestamp, duration); + return true; + } + } + parsed = default; + return false; + } + } + + /// + /// The time at which this entry was recorded. + /// + public DateTime Timestamp { get; } + + /// + /// The latency recorded for this event. + /// + public int DurationMilliseconds { get; } + + internal LatencyHistoryEntry(long timestamp, long duration) + { + Timestamp = RedisBase.UnixEpoch.AddSeconds(timestamp); + DurationMilliseconds = checked((int)duration); + } +} diff --git a/src/StackExchange.Redis/APITypes/LatencyLatestEntry.cs b/src/StackExchange.Redis/APITypes/LatencyLatestEntry.cs new file mode 100644 index 000000000..d1bc70e42 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/LatencyLatestEntry.cs @@ -0,0 +1,60 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// A latency entry as reported by the built-in LATENCY LATEST command. +/// +public readonly struct LatencyLatestEntry +{ + internal static readonly ResultProcessor ToArray = new Processor(); + + private sealed class Processor : ArrayResultProcessor + { + protected override bool TryParse(in RawResult raw, out LatencyLatestEntry parsed) + { + if (raw.Resp2TypeArray == ResultType.Array) + { + var items = raw.GetItems(); + if (items.Length >= 4 + && items[1].TryGetInt64(out var timestamp) + && items[2].TryGetInt64(out var duration) + && items[3].TryGetInt64(out var maxDuration)) + { + parsed = new LatencyLatestEntry(items[0].GetString()!, timestamp, duration, maxDuration); + return true; + } + } + parsed = default; + return false; + } + } + + /// + /// The name of this event. + /// + public string EventName { get; } + + /// + /// The time at which this entry was recorded. + /// + public DateTime Timestamp { get; } + + /// + /// The latency recorded for this event. + /// + public int DurationMilliseconds { get; } + + /// + /// The max latency recorded for all events. + /// + public int MaxDurationMilliseconds { get; } + + internal LatencyLatestEntry(string eventName, long timestamp, long duration, long maxDuration) + { + EventName = eventName; + Timestamp = RedisBase.UnixEpoch.AddSeconds(timestamp); + DurationMilliseconds = checked((int)duration); + MaxDurationMilliseconds = checked((int)maxDuration); + } +} diff --git a/src/StackExchange.Redis/APITypes/ListPopResult.cs b/src/StackExchange.Redis/APITypes/ListPopResult.cs new file mode 100644 index 000000000..149bed68a --- /dev/null +++ b/src/StackExchange.Redis/APITypes/ListPopResult.cs @@ -0,0 +1,35 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// A contiguous portion of a redis list. +/// +public readonly struct ListPopResult +{ + /// + /// A null ListPopResult, indicating no results. + /// + public static ListPopResult Null { get; } = new ListPopResult(RedisKey.Null, Array.Empty()); + + /// + /// Whether this object is null/empty. + /// + public bool IsNull => Key.IsNull && Values == Array.Empty(); + + /// + /// The key of the list that this set of entries came form. + /// + public RedisKey Key { get; } + + /// + /// The values from the list. + /// + public RedisValue[] Values { get; } + + internal ListPopResult(RedisKey key, RedisValue[] values) + { + Key = key; + Values = values; + } +} diff --git a/src/StackExchange.Redis/APITypes/NameValueEntry.cs b/src/StackExchange.Redis/APITypes/NameValueEntry.cs new file mode 100644 index 000000000..3fbafa86e --- /dev/null +++ b/src/StackExchange.Redis/APITypes/NameValueEntry.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; + +namespace StackExchange.Redis; + +/// +/// Describes a value contained in a stream (a name/value pair). +/// +public readonly struct NameValueEntry : IEquatable +{ + internal readonly RedisValue name, value; + + /// + /// Initializes a value. + /// + /// The name for this entry. + /// The value for this entry. + public NameValueEntry(RedisValue name, RedisValue value) + { + this.name = name; + this.value = value; + } + + /// + /// The name of the field. + /// + public RedisValue Name => name; + + /// + /// The value of the field. + /// + public RedisValue Value => value; + + /// + /// Converts to a key/value pair. + /// + /// The to create a from. + public static implicit operator KeyValuePair(NameValueEntry value) => + new KeyValuePair(value.name, value.value); + + /// + /// Converts from a key/value pair. + /// + /// The to get a from. + public static implicit operator NameValueEntry(KeyValuePair value) => + new NameValueEntry(value.Key, value.Value); + + /// + /// The "{name}: {value}" string representation. + /// + public override string ToString() => name + ": " + value; + + /// + public override int GetHashCode() => name.GetHashCode() ^ value.GetHashCode(); + + /// + /// Compares two values for equality. + /// + /// The to compare to. + public override bool Equals(object? obj) => obj is NameValueEntry heObj && Equals(heObj); + + /// + /// Compares two values for equality. + /// + /// The to compare to. + public bool Equals(NameValueEntry other) => name == other.name && value == other.value; + + /// + /// Compares two values for equality. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(NameValueEntry x, NameValueEntry y) => x.name == y.name && x.value == y.value; + + /// + /// Compares two values for non-equality. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(NameValueEntry x, NameValueEntry y) => x.name != y.name || x.value != y.value; +} diff --git a/src/StackExchange.Redis/APITypes/RedisStream.cs b/src/StackExchange.Redis/APITypes/RedisStream.cs new file mode 100644 index 000000000..fbefa1240 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/RedisStream.cs @@ -0,0 +1,23 @@ +namespace StackExchange.Redis; + +/// +/// Describes a Redis Stream with an associated array of entries. +/// +public readonly struct RedisStream +{ + internal RedisStream(RedisKey key, StreamEntry[] entries) + { + Key = key; + Entries = entries; + } + + /// + /// The key for the stream. + /// + public RedisKey Key { get; } + + /// + /// An array of entries contained within the stream. + /// + public StreamEntry[] Entries { get; } +} diff --git a/src/StackExchange.Redis/APITypes/RedisValueWithExpiry.cs b/src/StackExchange.Redis/APITypes/RedisValueWithExpiry.cs new file mode 100644 index 000000000..c64e9aca9 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/RedisValueWithExpiry.cs @@ -0,0 +1,28 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// Describes a value/expiry pair. +/// +public readonly struct RedisValueWithExpiry +{ + /// + /// Creates a from a and a . + /// + public RedisValueWithExpiry(RedisValue value, TimeSpan? expiry) + { + Value = value; + Expiry = expiry; + } + + /// + /// The expiry of this record. + /// + public TimeSpan? Expiry { get; } + + /// + /// The value of this record. + /// + public RedisValue Value { get; } +} diff --git a/src/StackExchange.Redis/APITypes/SortedSetEntry.cs b/src/StackExchange.Redis/APITypes/SortedSetEntry.cs new file mode 100644 index 000000000..e61dc05ed --- /dev/null +++ b/src/StackExchange.Redis/APITypes/SortedSetEntry.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace StackExchange.Redis; + +/// +/// Describes a sorted-set element with the corresponding value. +/// +public readonly struct SortedSetEntry : IEquatable, IComparable, IComparable +{ + internal readonly RedisValue element; + internal readonly double score; + + /// + /// Initializes a value. + /// + /// The to get an entry for. + /// The redis score for . + public SortedSetEntry(RedisValue element, double score) + { + this.element = element; + this.score = score; + } + + /// + /// The unique element stored in the sorted set. + /// + public RedisValue Element => element; + + /// + /// The score against the element. + /// + public double Score => score; + + /// + /// The score against the element. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Please use Score", false)] + public double Value => score; + + /// + /// The unique element stored in the sorted set. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Please use Element", false)] + public RedisValue Key => element; + + /// + /// Converts to a key/value pair. + /// + /// The to get a for. + public static implicit operator KeyValuePair(SortedSetEntry value) => new KeyValuePair(value.element, value.score); + + /// + /// Converts from a key/value pair. + /// + /// The to get a for. + public static implicit operator SortedSetEntry(KeyValuePair value) => new SortedSetEntry(value.Key, value.Value); + + /// + /// A "{element}: {score}" string representation of the entry. + /// + public override string ToString() => element + ": " + score; + + /// + public override int GetHashCode() => element.GetHashCode() ^ score.GetHashCode(); + + /// + /// Compares two values for equality. + /// + /// The to compare to. + public override bool Equals(object? obj) => obj is SortedSetEntry ssObj && Equals(ssObj); + + /// + /// Compares two values for equality. + /// + /// The to compare to. + public bool Equals(SortedSetEntry other) => score == other.score && element == other.element; + + /// + /// Compares two values by score. + /// + /// The to compare to. + public int CompareTo(SortedSetEntry other) => score.CompareTo(other.score); + + /// + /// Compares two values by score. + /// + /// The to compare to. + public int CompareTo(object? obj) => obj is SortedSetEntry ssObj ? CompareTo(ssObj) : -1; + + /// + /// Compares two values for equality. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(SortedSetEntry x, SortedSetEntry y) => x.score == y.score && x.element == y.element; + + /// + /// Compares two values for non-equality. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(SortedSetEntry x, SortedSetEntry y) => x.score != y.score || x.element != y.element; +} diff --git a/src/StackExchange.Redis/APITypes/SortedSetPopResult.cs b/src/StackExchange.Redis/APITypes/SortedSetPopResult.cs new file mode 100644 index 000000000..dcdc4c01e --- /dev/null +++ b/src/StackExchange.Redis/APITypes/SortedSetPopResult.cs @@ -0,0 +1,35 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// A contiguous portion of a redis sorted set. +/// +public readonly struct SortedSetPopResult +{ + /// + /// A null SortedSetPopResult, indicating no results. + /// + public static SortedSetPopResult Null { get; } = new SortedSetPopResult(RedisKey.Null, Array.Empty()); + + /// + /// Whether this object is null/empty. + /// + public bool IsNull => Key.IsNull && Entries == Array.Empty(); + + /// + /// The key of the sorted set these entries came form. + /// + public RedisKey Key { get; } + + /// + /// The provided entries of the sorted set. + /// + public SortedSetEntry[] Entries { get; } + + internal SortedSetPopResult(RedisKey key, SortedSetEntry[] entries) + { + Key = key; + Entries = entries; + } +} diff --git a/src/StackExchange.Redis/APITypes/StreamAutoClaimIdsOnlyResult.cs b/src/StackExchange.Redis/APITypes/StreamAutoClaimIdsOnlyResult.cs new file mode 100644 index 000000000..114763129 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/StreamAutoClaimIdsOnlyResult.cs @@ -0,0 +1,41 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// Result of the XAUTOCLAIM command with the JUSTID option. +/// +public readonly struct StreamAutoClaimIdsOnlyResult +{ + internal StreamAutoClaimIdsOnlyResult(RedisValue nextStartId, RedisValue[] claimedIds, RedisValue[] deletedIds) + { + NextStartId = nextStartId; + ClaimedIds = claimedIds; + DeletedIds = deletedIds; + } + + /// + /// A null , indicating no results. + /// + public static StreamAutoClaimIdsOnlyResult Null { get; } = new StreamAutoClaimIdsOnlyResult(RedisValue.Null, Array.Empty(), Array.Empty()); + + /// + /// Whether this object is null/empty. + /// + public bool IsNull => NextStartId.IsNull && ClaimedIds == Array.Empty() && DeletedIds == Array.Empty(); + + /// + /// The stream ID to be used in the next call to StreamAutoClaim. + /// + public RedisValue NextStartId { get; } + + /// + /// Array of IDs claimed by the command. + /// + public RedisValue[] ClaimedIds { get; } + + /// + /// Array of message IDs deleted from the stream. + /// + public RedisValue[] DeletedIds { get; } +} diff --git a/src/StackExchange.Redis/APITypes/StreamAutoClaimResult.cs b/src/StackExchange.Redis/APITypes/StreamAutoClaimResult.cs new file mode 100644 index 000000000..09f607f3d --- /dev/null +++ b/src/StackExchange.Redis/APITypes/StreamAutoClaimResult.cs @@ -0,0 +1,41 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// Result of the XAUTOCLAIM command. +/// +public readonly struct StreamAutoClaimResult +{ + internal StreamAutoClaimResult(RedisValue nextStartId, StreamEntry[] claimedEntries, RedisValue[] deletedIds) + { + NextStartId = nextStartId; + ClaimedEntries = claimedEntries; + DeletedIds = deletedIds; + } + + /// + /// A null , indicating no results. + /// + public static StreamAutoClaimResult Null { get; } = new StreamAutoClaimResult(RedisValue.Null, Array.Empty(), Array.Empty()); + + /// + /// Whether this object is null/empty. + /// + public bool IsNull => NextStartId.IsNull && ClaimedEntries == Array.Empty() && DeletedIds == Array.Empty(); + + /// + /// The stream ID to be used in the next call to StreamAutoClaim. + /// + public RedisValue NextStartId { get; } + + /// + /// An array of for the successfully claimed entries. + /// + public StreamEntry[] ClaimedEntries { get; } + + /// + /// An array of message IDs deleted from the stream. + /// + public RedisValue[] DeletedIds { get; } +} diff --git a/src/StackExchange.Redis/APITypes/StreamConsumer.cs b/src/StackExchange.Redis/APITypes/StreamConsumer.cs new file mode 100644 index 000000000..a99778707 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/StreamConsumer.cs @@ -0,0 +1,23 @@ +namespace StackExchange.Redis; + +/// +/// Describes a consumer off a Redis Stream. +/// +public readonly struct StreamConsumer +{ + internal StreamConsumer(RedisValue name, int pendingMessageCount) + { + Name = name; + PendingMessageCount = pendingMessageCount; + } + + /// + /// The name of the consumer. + /// + public RedisValue Name { get; } + + /// + /// The number of messages that have been delivered by not yet acknowledged by the consumer. + /// + public int PendingMessageCount { get; } +} diff --git a/src/StackExchange.Redis/APITypes/StreamConsumerInfo.cs b/src/StackExchange.Redis/APITypes/StreamConsumerInfo.cs new file mode 100644 index 000000000..ab7cd9af1 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/StreamConsumerInfo.cs @@ -0,0 +1,30 @@ +namespace StackExchange.Redis; + +/// +/// Describes a consumer within a consumer group, retrieved using the XINFO CONSUMERS command. . +/// +public readonly struct StreamConsumerInfo +{ + internal StreamConsumerInfo(string name, int pendingMessageCount, long idleTimeInMilliseconds) + { + Name = name; + PendingMessageCount = pendingMessageCount; + IdleTimeInMilliseconds = idleTimeInMilliseconds; + } + + /// + /// The name of the consumer. + /// + public string Name { get; } + + /// + /// The number of pending messages for the consumer. A pending message is one that has been + /// received by the consumer but not yet acknowledged. + /// + public int PendingMessageCount { get; } + + /// + /// The idle time, if any, for the consumer. + /// + public long IdleTimeInMilliseconds { get; } +} diff --git a/src/StackExchange.Redis/APITypes/StreamEntry.cs b/src/StackExchange.Redis/APITypes/StreamEntry.cs new file mode 100644 index 000000000..3f37b9430 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/StreamEntry.cs @@ -0,0 +1,58 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// Describes an entry contained in a Redis Stream. +/// +public readonly struct StreamEntry +{ + /// + /// Creates an stream entry. + /// + public StreamEntry(RedisValue id, NameValueEntry[] values) + { + Id = id; + Values = values; + } + + /// + /// A null stream entry. + /// + public static StreamEntry Null { get; } = new StreamEntry(RedisValue.Null, Array.Empty()); + + /// + /// The ID assigned to the message. + /// + public RedisValue Id { get; } + + /// + /// The values contained within the message. + /// + public NameValueEntry[] Values { get; } + + /// + /// Search for a specific field by name, returning the value. + /// + public RedisValue this[RedisValue fieldName] + { + get + { + var values = Values; + if (values != null) + { + for (int i = 0; i < values.Length; i++) + { + if (values[i].name == fieldName) + return values[i].value; + } + } + return RedisValue.Null; + } + } + + /// + /// Indicates that the Redis Stream Entry is null. + /// + public bool IsNull => Id == RedisValue.Null && Values == Array.Empty(); +} diff --git a/src/StackExchange.Redis/APITypes/StreamGroupInfo.cs b/src/StackExchange.Redis/APITypes/StreamGroupInfo.cs new file mode 100644 index 000000000..a357c400e --- /dev/null +++ b/src/StackExchange.Redis/APITypes/StreamGroupInfo.cs @@ -0,0 +1,48 @@ +namespace StackExchange.Redis; + +/// +/// Describes a consumer group retrieved using the XINFO GROUPS command. . +/// +public readonly struct StreamGroupInfo +{ + internal StreamGroupInfo(string name, int consumerCount, int pendingMessageCount, string? lastDeliveredId, long? entriesRead, long? lag) + { + Name = name; + ConsumerCount = consumerCount; + PendingMessageCount = pendingMessageCount; + LastDeliveredId = lastDeliveredId; + EntriesRead = entriesRead; + Lag = lag; + } + + /// + /// The name of the consumer group. + /// + public string Name { get; } + + /// + /// The number of consumers within the consumer group. + /// + public int ConsumerCount { get; } + + /// + /// The total number of pending messages for the consumer group. A pending message is one that has been + /// received by a consumer but not yet acknowledged. + /// + public int PendingMessageCount { get; } + + /// + /// The Id of the last message delivered to the group. + /// + public string? LastDeliveredId { get; } + + /// + /// Total number of entries the group had read. + /// + public long? EntriesRead { get; } + + /// + /// The number of entries in the range between the group's read entries and the stream's entries. + /// + public long? Lag { get; } +} diff --git a/src/StackExchange.Redis/APITypes/StreamInfo.cs b/src/StackExchange.Redis/APITypes/StreamInfo.cs new file mode 100644 index 000000000..230ea47fb --- /dev/null +++ b/src/StackExchange.Redis/APITypes/StreamInfo.cs @@ -0,0 +1,53 @@ +namespace StackExchange.Redis; + +/// +/// Describes stream information retrieved using the XINFO STREAM command. . +/// +public readonly struct StreamInfo +{ + internal StreamInfo(int length, int radixTreeKeys, int radixTreeNodes, int groups, StreamEntry firstEntry, StreamEntry lastEntry, RedisValue lastGeneratedId) + { + Length = length; + RadixTreeKeys = radixTreeKeys; + RadixTreeNodes = radixTreeNodes; + ConsumerGroupCount = groups; + FirstEntry = firstEntry; + LastEntry = lastEntry; + LastGeneratedId = lastGeneratedId; + } + + /// + /// The number of entries in the stream. + /// + public int Length { get; } + + /// + /// The number of radix tree keys in the stream. + /// + public int RadixTreeKeys { get; } + + /// + /// The number of radix tree nodes in the stream. + /// + public int RadixTreeNodes { get; } + + /// + /// The number of consumers groups in the stream. + /// + public int ConsumerGroupCount { get; } + + /// + /// The first entry in the stream. + /// + public StreamEntry FirstEntry { get; } + + /// + /// The last entry in the stream. + /// + public StreamEntry LastEntry { get; } + + /// + /// The last generated id. + /// + public RedisValue LastGeneratedId { get; } +} diff --git a/src/StackExchange.Redis/APITypes/StreamPendingInfo.cs b/src/StackExchange.Redis/APITypes/StreamPendingInfo.cs new file mode 100644 index 000000000..b55696f8c --- /dev/null +++ b/src/StackExchange.Redis/APITypes/StreamPendingInfo.cs @@ -0,0 +1,35 @@ +namespace StackExchange.Redis; + +/// +/// Describes basic information about pending messages for a consumer group. +/// +public readonly struct StreamPendingInfo +{ + internal StreamPendingInfo(int pendingMessageCount, RedisValue lowestId, RedisValue highestId, StreamConsumer[] consumers) + { + PendingMessageCount = pendingMessageCount; + LowestPendingMessageId = lowestId; + HighestPendingMessageId = highestId; + Consumers = consumers; + } + + /// + /// The number of pending messages. A pending message is a message that has been consumed but not yet acknowledged. + /// + public int PendingMessageCount { get; } + + /// + /// The lowest message ID in the set of pending messages. + /// + public RedisValue LowestPendingMessageId { get; } + + /// + /// The highest message ID in the set of pending messages. + /// + public RedisValue HighestPendingMessageId { get; } + + /// + /// An array of consumers within the consumer group that have pending messages. + /// + public StreamConsumer[] Consumers { get; } +} diff --git a/src/StackExchange.Redis/APITypes/StreamPendingMessageInfo.cs b/src/StackExchange.Redis/APITypes/StreamPendingMessageInfo.cs new file mode 100644 index 000000000..32cbbcc8d --- /dev/null +++ b/src/StackExchange.Redis/APITypes/StreamPendingMessageInfo.cs @@ -0,0 +1,36 @@ +namespace StackExchange.Redis; + +/// +/// Describes properties of a pending message. +/// A pending message is one that has been received by a consumer but has not yet been acknowledged. +/// +public readonly struct StreamPendingMessageInfo +{ + internal StreamPendingMessageInfo(RedisValue messageId, RedisValue consumerName, long idleTimeInMs, int deliveryCount) + { + MessageId = messageId; + ConsumerName = consumerName; + IdleTimeInMilliseconds = idleTimeInMs; + DeliveryCount = deliveryCount; + } + + /// + /// The ID of the pending message. + /// + public RedisValue MessageId { get; } + + /// + /// The consumer that received the pending message. + /// + public RedisValue ConsumerName { get; } + + /// + /// The time that has passed since the message was last delivered to a consumer. + /// + public long IdleTimeInMilliseconds { get; } + + /// + /// The number of times the message has been delivered to a consumer. + /// + public int DeliveryCount { get; } +} diff --git a/src/StackExchange.Redis/APITypes/StreamPosition.cs b/src/StackExchange.Redis/APITypes/StreamPosition.cs new file mode 100644 index 000000000..b58dfd599 --- /dev/null +++ b/src/StackExchange.Redis/APITypes/StreamPosition.cs @@ -0,0 +1,66 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// Describes a pair consisting of the Stream Key and the from which to begin reading a stream. +/// +public struct StreamPosition +{ + /// + /// Read from the beginning of a stream. + /// + public static RedisValue Beginning => StreamConstants.ReadMinValue; + + /// + /// Read new messages. + /// + public static RedisValue NewMessages => StreamConstants.NewMessages; + + /// + /// Initializes a value. + /// + /// The key for the stream. + /// The position from which to begin reading the stream. + public StreamPosition(RedisKey key, RedisValue position) + { + Key = key; + Position = position; + } + + /// + /// The stream key. + /// + public RedisKey Key { get; } + + /// + /// The offset at which to begin reading the stream. + /// + public RedisValue Position { get; } + + internal static RedisValue Resolve(RedisValue value, RedisCommand command) + { + if (value == NewMessages) + { + return command switch + { + RedisCommand.XREAD => throw new InvalidOperationException("StreamPosition.NewMessages cannot be used with StreamRead."), + RedisCommand.XREADGROUP => StreamConstants.UndeliveredMessages, + RedisCommand.XGROUP => StreamConstants.NewMessages, + // new is only valid for the above + _ => throw new ArgumentException($"Unsupported command in StreamPosition.Resolve: {command}.", nameof(command)), + }; + } + else if (value == StreamPosition.Beginning) + { + switch (command) + { + case RedisCommand.XREAD: + case RedisCommand.XREADGROUP: + case RedisCommand.XGROUP: + return StreamConstants.AllMessages; + } + } + return value; + } +} diff --git a/src/StackExchange.Redis/AssemblyInfoHack.cs b/src/StackExchange.Redis/AssemblyInfoHack.cs new file mode 100644 index 000000000..50cdc2c1c --- /dev/null +++ b/src/StackExchange.Redis/AssemblyInfoHack.cs @@ -0,0 +1,6 @@ +// Yes, this is embarrassing. However, in .NET Core the including AssemblyInfo (ifdef'd or not) will screw with +// your version numbers. Therefore, we need to move the attribute out into another file...this file. +// When .csproj merges in, this should be able to return to Properties/AssemblyInfo.cs +using System; + +[assembly: CLSCompliant(true)] diff --git a/src/StackExchange.Redis/BacklogPolicy.cs b/src/StackExchange.Redis/BacklogPolicy.cs new file mode 100644 index 000000000..baa13ae20 --- /dev/null +++ b/src/StackExchange.Redis/BacklogPolicy.cs @@ -0,0 +1,43 @@ +namespace StackExchange.Redis +{ + /// + /// The backlog policy to use for commands. This policy comes into effect when a connection is unhealthy or unavailable. + /// The policy can choose to backlog commands and wait to try them (within their timeout) against a connection when it comes up, + /// or it could choose to fail fast and throw ASAP. Different apps desire different behaviors with backpressure and how to handle + /// large amounts of load, so this is configurable to optimize the happy path but avoid spiral-of-death queue scenarios for others. + /// + public sealed class BacklogPolicy + { + /// + /// Backlog behavior matching StackExchange.Redis's 2.x line, failing fast and not attempting to queue + /// and retry when a connection is available again. + /// + public static BacklogPolicy FailFast { get; } = new() + { + QueueWhileDisconnected = false, + AbortPendingOnConnectionFailure = true, + }; + + /// + /// Default backlog policy which will allow commands to be issues against an endpoint and queue up. + /// Commands are still subject to their async timeout (which serves as a queue size check). + /// + public static BacklogPolicy Default { get; } = new() + { + QueueWhileDisconnected = true, + AbortPendingOnConnectionFailure = false, + }; + + /// + /// Whether to queue commands while disconnected. + /// True means queue for attempts up until their timeout. + /// means to fail ASAP and queue nothing. + /// + public bool QueueWhileDisconnected { get; init; } + + /// + /// Whether to immediately abandon (with an exception) all pending commands when a connection goes unhealthy. + /// + public bool AbortPendingOnConnectionFailure { get; init; } + } +} diff --git a/src/StackExchange.Redis/BufferReader.cs b/src/StackExchange.Redis/BufferReader.cs new file mode 100644 index 000000000..22b36ccb6 --- /dev/null +++ b/src/StackExchange.Redis/BufferReader.cs @@ -0,0 +1,221 @@ +using System; +using System.Buffers; +using System.IO; + +namespace StackExchange.Redis +{ + internal enum ConsumeResult + { + Failure, + Success, + NeedMoreData, + } + + internal ref struct BufferReader + { + private long _totalConsumed; + public int OffsetThisSpan { get; private set; } + public int RemainingThisSpan { get; private set; } + + public long TotalConsumed => _totalConsumed; + + private ReadOnlySequence.Enumerator _iterator; + private ReadOnlySpan _current; + + public ReadOnlySpan OversizedSpan => _current; + + public ReadOnlySpan SlicedSpan => _current.Slice(OffsetThisSpan, RemainingThisSpan); + + public bool IsEmpty => RemainingThisSpan == 0; + + private bool FetchNextSegment() + { + do + { + if (!_iterator.MoveNext()) + { + OffsetThisSpan = RemainingThisSpan = 0; + return false; + } + + _current = _iterator.Current.Span; + OffsetThisSpan = 0; + RemainingThisSpan = _current.Length; + } + while (IsEmpty); // skip empty segments, they don't help us! + + return true; + } + + public BufferReader(scoped in ReadOnlySequence buffer) + { + _buffer = buffer; + _lastSnapshotPosition = buffer.Start; + _lastSnapshotBytes = 0; + _iterator = buffer.GetEnumerator(); + _current = default; + _totalConsumed = OffsetThisSpan = RemainingThisSpan = 0; + + FetchNextSegment(); + } + + /// + /// Note that in results other than success, no guarantees are made about final state; if you care: snapshot. + /// + public ConsumeResult TryConsumeCRLF() + { + switch (RemainingThisSpan) + { + case 0: + return ConsumeResult.NeedMoreData; + case 1: + if (_current[OffsetThisSpan] != (byte)'\r') return ConsumeResult.Failure; + Consume(1); + if (IsEmpty) return ConsumeResult.NeedMoreData; + var next = _current[OffsetThisSpan]; + Consume(1); + return next == '\n' ? ConsumeResult.Success : ConsumeResult.Failure; + default: + var offset = OffsetThisSpan; + var result = _current[offset++] == (byte)'\r' && _current[offset] == (byte)'\n' + ? ConsumeResult.Success : ConsumeResult.Failure; + Consume(2); + return result; + } + } + + public bool TryConsume(int count) + { + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + do + { + var available = RemainingThisSpan; + if (count <= available) + { + // consume part of this span + _totalConsumed += count; + RemainingThisSpan -= count; + OffsetThisSpan += count; + + if (count == available) FetchNextSegment(); // burned all of it; fetch next + return true; + } + + // consume all of this span + _totalConsumed += available; + count -= available; + } + while (FetchNextSegment()); + return false; + } + + private readonly ReadOnlySequence _buffer; + private SequencePosition _lastSnapshotPosition; + private long _lastSnapshotBytes; + + // makes an internal note of where we are, as a SequencePosition; useful + // to avoid having to use buffer.Slice on huge ranges + private SequencePosition SnapshotPosition() + { + var delta = _totalConsumed - _lastSnapshotBytes; + if (delta == 0) return _lastSnapshotPosition; + + var pos = _buffer.GetPosition(delta, _lastSnapshotPosition); + _lastSnapshotBytes = _totalConsumed; + return _lastSnapshotPosition = pos; + } + + public ReadOnlySequence ConsumeAsBuffer(int count) + { + if (!TryConsumeAsBuffer(count, out var buffer)) throw new EndOfStreamException(); + return buffer; + } + + public ReadOnlySequence ConsumeToEnd() + { + var from = SnapshotPosition(); + var result = _buffer.Slice(from); + while (FetchNextSegment()) + { + // consume all + } + return result; + } + + public bool TryConsumeAsBuffer(int count, out ReadOnlySequence buffer) + { + var from = SnapshotPosition(); + if (!TryConsume(count)) + { + buffer = default; + return false; + } + var to = SnapshotPosition(); + buffer = _buffer.Slice(from, to); + return true; + } + + public void Consume(int count) + { + if (!TryConsume(count)) throw new EndOfStreamException(); + } + + internal static int FindNext(BufferReader reader, byte value) // very deliberately not ref; want snapshot + { + int totalSkipped = 0; + do + { + if (reader.RemainingThisSpan == 0) continue; + + var span = reader.SlicedSpan; + int found = span.VectorSafeIndexOf(value); + if (found >= 0) return totalSkipped + found; + + totalSkipped += span.Length; + } + while (reader.FetchNextSegment()); + return -1; + } + + internal static int FindNextCrLf(BufferReader reader) // very deliberately not ref; want snapshot + { + // is it in the current span? (we need to handle the offsets differently if so) + int totalSkipped = 0; + bool haveTrailingCR = false; + do + { + if (reader.RemainingThisSpan == 0) continue; + + var span = reader.SlicedSpan; + if (haveTrailingCR) + { + if (span[0] == '\n') return totalSkipped - 1; + } + + int found = span.VectorSafeIndexOfCRLF(); + if (found >= 0) return totalSkipped + found; + + haveTrailingCR = span[span.Length - 1] == '\r'; + totalSkipped += span.Length; + } + while (reader.FetchNextSegment()); + return -1; + } + + public int ConsumeByte() + { + if (IsEmpty) return -1; + var value = _current[OffsetThisSpan]; + Consume(1); + return value; + } + + public int PeekByte() => IsEmpty ? -1 : _current[OffsetThisSpan]; + + public ReadOnlySequence SliceFromCurrent() + { + var from = SnapshotPosition(); + return _buffer.Slice(from); + } + } +} diff --git a/src/StackExchange.Redis/ChannelMessageQueue.cs b/src/StackExchange.Redis/ChannelMessageQueue.cs new file mode 100644 index 000000000..e58fb393b --- /dev/null +++ b/src/StackExchange.Redis/ChannelMessageQueue.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +#if NETCOREAPP3_1 +using System.Reflection; +#endif + +namespace StackExchange.Redis +{ + /// + /// Represents a message that is broadcast via publish/subscribe. + /// + public readonly struct ChannelMessage + { + // this is *smaller* than storing a RedisChannel for the subscribed channel + private readonly ChannelMessageQueue _queue; + + /// + /// The Channel:Message string representation. + /// + public override string ToString() => ((string?)Channel) + ":" + ((string?)Message); + + /// + public override int GetHashCode() => Channel.GetHashCode() ^ Message.GetHashCode(); + + /// + public override bool Equals(object? obj) => obj is ChannelMessage cm + && cm.Channel == Channel && cm.Message == Message; + + internal ChannelMessage(ChannelMessageQueue queue, in RedisChannel channel, in RedisValue value) + { + _queue = queue; + Channel = channel; + Message = value; + } + + /// + /// The channel that the subscription was created from. + /// + public RedisChannel SubscriptionChannel => _queue.Channel; + + /// + /// The channel that the message was broadcast to. + /// + public RedisChannel Channel { get; } + + /// + /// The value that was broadcast. + /// + public RedisValue Message { get; } + + /// + /// Checks if 2 messages are .Equal(). + /// + public static bool operator ==(ChannelMessage left, ChannelMessage right) => left.Equals(right); + + /// + /// Checks if 2 messages are not .Equal(). + /// + public static bool operator !=(ChannelMessage left, ChannelMessage right) => !left.Equals(right); + } + + /// + /// Represents a message queue of ordered pub/sub notifications. + /// + /// + /// To create a ChannelMessageQueue, use + /// or . + /// + public sealed class ChannelMessageQueue : IAsyncEnumerable + { + private readonly Channel _queue; + + /// + /// The Channel that was subscribed for this queue. + /// + public RedisChannel Channel { get; } + private RedisSubscriber? _parent; + + /// + /// The string representation of this channel. + /// + public override string? ToString() => (string?)Channel; + + /// + /// An awaitable task the indicates completion of the queue (including drain of data). + /// + public Task Completion => _queue.Reader.Completion; + + internal ChannelMessageQueue(in RedisChannel redisChannel, RedisSubscriber parent) + { + Channel = redisChannel; + _parent = parent; + _queue = System.Threading.Channels.Channel.CreateUnbounded(s_ChannelOptions); + } + + private static readonly UnboundedChannelOptions s_ChannelOptions = new UnboundedChannelOptions + { + SingleWriter = true, + SingleReader = false, + AllowSynchronousContinuations = false, + }; + + private void Write(in RedisChannel channel, in RedisValue value) + { + var writer = _queue.Writer; + writer.TryWrite(new ChannelMessage(this, channel, value)); + } + + /// + /// Consume a message from the channel. + /// + /// The to use. + public ValueTask ReadAsync(CancellationToken cancellationToken = default) + => _queue.Reader.ReadAsync(cancellationToken); + + /// + /// Attempt to synchronously consume a message from the channel. + /// + /// The read from the Channel. + public bool TryRead(out ChannelMessage item) => _queue.Reader.TryRead(out item); + + /// + /// Attempt to query the backlog length of the queue. + /// + /// The (approximate) count of items in the Channel. + public bool TryGetCount(out int count) + { + // This is specific to netcoreapp3.1, because full framework was out of band and the new prop is present +#if NETCOREAPP3_1 + // get this using the reflection + try + { + var prop = _queue.GetType().GetProperty("ItemsCountForDebugger", BindingFlags.Instance | BindingFlags.NonPublic); + if (prop is not null) + { + count = (int)prop.GetValue(_queue)!; + return true; + } + } + catch { } +#else + var reader = _queue.Reader; + if (reader.CanCount) + { + count = reader.Count; + return true; + } +#endif + + count = default; + return false; + } + + private Delegate? _onMessageHandler; + private void AssertOnMessage(Delegate handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + if (Interlocked.CompareExchange(ref _onMessageHandler, handler, null) != null) + throw new InvalidOperationException("Only a single " + nameof(OnMessage) + " is allowed"); + } + + /// + /// Create a message loop that processes messages sequentially. + /// + /// The handler to run when receiving a message. + public void OnMessage(Action handler) + { + AssertOnMessage(handler); + + ThreadPool.QueueUserWorkItem( + state => ((ChannelMessageQueue)state!).OnMessageSyncImpl().RedisFireAndForget(), this); + } + + private async Task OnMessageSyncImpl() + { + var handler = (Action?)_onMessageHandler; + while (!Completion.IsCompleted) + { + ChannelMessage next; + try { if (!TryRead(out next)) next = await ReadAsync().ForAwait(); } + catch (ChannelClosedException) { break; } // expected + catch (Exception ex) + { + _parent?.multiplexer?.OnInternalError(ex); + break; + } + + try { handler?.Invoke(next); } + catch { } // matches MessageCompletable + } + } + + internal static void Combine(ref ChannelMessageQueue? head, ChannelMessageQueue queue) + { + if (queue != null) + { + // insert at the start of the linked-list + ChannelMessageQueue? old; + do + { + old = Volatile.Read(ref head); + queue._next = old; + } + while (Interlocked.CompareExchange(ref head, queue, old) != old); + } + } + + /// + /// Create a message loop that processes messages sequentially. + /// + /// The handler to execute when receiving a message. + public void OnMessage(Func handler) + { + AssertOnMessage(handler); + + ThreadPool.QueueUserWorkItem( + state => ((ChannelMessageQueue)state!).OnMessageAsyncImpl().RedisFireAndForget(), this); + } + + internal static void Remove(ref ChannelMessageQueue? head, ChannelMessageQueue queue) + { + if (queue is null) + { + return; + } + + bool found; + // if we fail due to a conflict, re-do from start + do + { + var current = Volatile.Read(ref head); + if (current == null) return; // no queue? nothing to do + if (current == queue) + { + found = true; + // found at the head - then we need to change the head + if (Interlocked.CompareExchange(ref head, Volatile.Read(ref current._next), current) == current) + { + return; // success + } + } + else + { + ChannelMessageQueue? previous = current; + current = Volatile.Read(ref previous._next); + found = false; + do + { + if (current == queue) + { + found = true; + // found it, not at the head; remove the node + if (Interlocked.CompareExchange(ref previous._next, Volatile.Read(ref current._next), current) == current) + { + return; // success + } + else + { + break; // exit the inner loop, and repeat the outer loop + } + } + previous = current; + current = Volatile.Read(ref previous!._next); + } + while (current != null); + } + } + while (found); + } + + internal static int Count(ref ChannelMessageQueue? head) + { + var current = Volatile.Read(ref head); + int count = 0; + while (current != null) + { + count++; + current = Volatile.Read(ref current._next); + } + return count; + } + + internal static void WriteAll(ref ChannelMessageQueue head, in RedisChannel channel, in RedisValue message) + { + var current = Volatile.Read(ref head); + while (current != null) + { + current.Write(channel, message); + current = Volatile.Read(ref current._next); + } + } + + private ChannelMessageQueue? _next; + + private async Task OnMessageAsyncImpl() + { + var handler = (Func?)_onMessageHandler; + while (!Completion.IsCompleted) + { + ChannelMessage next; + try { if (!TryRead(out next)) next = await ReadAsync().ForAwait(); } + catch (ChannelClosedException) { break; } // expected + catch (Exception ex) + { + _parent?.multiplexer?.OnInternalError(ex); + break; + } + + try + { + var task = handler?.Invoke(next); + if (task != null && task.Status != TaskStatus.RanToCompletion) await task.ForAwait(); + } + catch { } // matches MessageCompletable + } + } + + internal static void MarkAllCompleted(ref ChannelMessageQueue? head) + { + var current = Interlocked.Exchange(ref head, null); + while (current != null) + { + current.MarkCompleted(); + current = Volatile.Read(ref current._next); + } + } + + private void MarkCompleted(Exception? error = null) + { + _parent = null; + _queue.Writer.TryComplete(error); + } + + internal void UnsubscribeImpl(Exception? error = null, CommandFlags flags = CommandFlags.None) + { + var parent = _parent; + _parent = null; + parent?.UnsubscribeAsync(Channel, null, this, flags); + _queue.Writer.TryComplete(error); + } + + internal async Task UnsubscribeAsyncImpl(Exception? error = null, CommandFlags flags = CommandFlags.None) + { + var parent = _parent; + _parent = null; + if (parent != null) + { + await parent.UnsubscribeAsync(Channel, null, this, flags).ForAwait(); + } + _queue.Writer.TryComplete(error); + } + + /// + /// Stop receiving messages on this channel. + /// + /// The flags to use when unsubscribing. + public void Unsubscribe(CommandFlags flags = CommandFlags.None) => UnsubscribeImpl(null, flags); + + /// + /// Stop receiving messages on this channel. + /// + /// The flags to use when unsubscribing. + public Task UnsubscribeAsync(CommandFlags flags = CommandFlags.None) => UnsubscribeAsyncImpl(null, flags); + + /// +#if NETCOREAPP3_0_OR_GREATER + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + => _queue.Reader.ReadAllAsync().GetAsyncEnumerator(cancellationToken); +#else + public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + while (await _queue.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + while (_queue.Reader.TryRead(out var item)) + { + yield return item; + } + } + } +#endif + } +} diff --git a/src/StackExchange.Redis/ClientInfo.cs b/src/StackExchange.Redis/ClientInfo.cs new file mode 100644 index 000000000..d743affff --- /dev/null +++ b/src/StackExchange.Redis/ClientInfo.cs @@ -0,0 +1,309 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Net; + +namespace StackExchange.Redis +{ + /// + /// Represents the state of an individual client connection to redis. + /// + public sealed class ClientInfo + { + internal static readonly ResultProcessor Processor = new ClientInfoProcessor(); + + /// + /// Address (host and port) of the client. + /// + public EndPoint? Address { get; private set; } + + /// + /// Total duration of the connection in seconds. + /// + public int AgeSeconds { get; private set; } + + /// + /// Current database ID. + /// + public int Database { get; private set; } + + /// + /// The flags associated with this connection. + /// + public ClientFlags Flags { get; private set; } + + /// + /// The client flags can be a combination of: + /// + /// + /// A + /// Connection to be closed ASAP. + /// + /// + /// b + /// The client is waiting in a blocking operation. + /// + /// + /// c + /// Connection to be closed after writing entire reply. + /// + /// + /// d + /// A watched keys has been modified - EXEC will fail. + /// + /// + /// i + /// The client is waiting for a VM I/O (deprecated). + /// + /// + /// M + /// The client is a primary. + /// + /// + /// N + /// No specific flag set. + /// + /// + /// O + /// The client is a replica in MONITOR mode. + /// + /// + /// P + /// The client is a Pub/Sub subscriber. + /// + /// + /// r + /// The client is in readonly mode against a cluster node. + /// + /// + /// S + /// The client is a normal replica server. + /// + /// + /// u + /// The client is unblocked. + /// + /// + /// U + /// The client is unblocked. + /// + /// + /// x + /// The client is in a MULTI/EXEC context. + /// + /// + /// t + /// The client enabled keys tracking in order to perform client side caching. + /// + /// + /// R + /// The client tracking target client is invalid. + /// + /// + /// B + /// The client enabled broadcast tracking mode. + /// + /// + /// + /// + public string? FlagsRaw { get; private set; } + + /// + /// The host of the client (typically an IP address). + /// + public string? Host => Format.TryGetHostPort(Address, out string? host, out _) ? host : null; + + /// + /// Idle time of the connection in seconds. + /// + public int IdleSeconds { get; private set; } + + /// + /// Last command played. + /// + public string? LastCommand { get; private set; } + + /// + /// The name allocated to this connection, if any. + /// + public string? Name { get; private set; } + + /// + /// Number of pattern-matching subscriptions. + /// + public int PatternSubscriptionCount { get; private set; } + + /// + /// Number of sharded subscriptions. + /// + public int ShardedSubscriptionCount { get; private set; } + + /// + /// The port of the client. + /// + public int Port => Format.TryGetHostPort(Address, out _, out int? port) ? port.Value : 0; + + /// + /// The raw content from redis. + /// + public string? Raw { get; private set; } + + /// + /// Number of channel subscriptions. + /// + public int SubscriptionCount { get; private set; } + + /// + /// Number of commands in a MULTI/EXEC context. + /// + public int TransactionCommandLength { get; private set; } + + /// + /// A unique 64-bit client ID (introduced in Redis 2.8.12). + /// + public long Id { get; private set; } + + /// + /// Format the object as a string. + /// + public override string ToString() + { + string addr = Format.ToString(Address); + return string.IsNullOrWhiteSpace(Name) ? addr : (addr + " - " + Name); + } + + /// + /// The class of the connection. + /// + public ClientType ClientType + { + get + { + if (SubscriptionCount != 0 || PatternSubscriptionCount != 0) return ClientType.PubSub; + if ((Flags & ClientFlags.Replica) != 0) return ClientType.Replica; + return ClientType.Normal; + } + } + + /// + /// Client RESP protocol version. Added in Redis 7.0. + /// + public string? ProtocolVersion { get; private set; } + + /// + /// Client RESP protocol version. Added in Redis 7.0. + /// + public RedisProtocol? Protocol => ConfigurationOptions.TryParseRedisProtocol(ProtocolVersion, out var value) ? value : null; + + /// + /// Client library name. Added in Redis 7.2. + /// + /// + public string? LibraryName { get; private set; } + + /// + /// Client library version. Added in Redis 7.2. + /// + /// + public string? LibraryVersion { get; private set; } + + internal static bool TryParse(string? input, [NotNullWhen(true)] out ClientInfo[]? clientList) + { + if (input == null) + { + clientList = null; + return false; + } + + var clients = new List(); + using (var reader = new StringReader(input)) + { + while (reader.ReadLine() is string line) + { + var client = new ClientInfo + { + Raw = line, + }; + string[] tokens = line.Split(StringSplits.Space); + for (int i = 0; i < tokens.Length; i++) + { + string tok = tokens[i]; + int idx = tok.IndexOf('='); + if (idx < 0) continue; + string key = tok.Substring(0, idx), value = tok.Substring(idx + 1); + + switch (key) + { + case "addr" when Format.TryParseEndPoint(value, out var addr): client.Address = addr; break; + case "age": client.AgeSeconds = Format.ParseInt32(value); break; + case "idle": client.IdleSeconds = Format.ParseInt32(value); break; + case "db": client.Database = Format.ParseInt32(value); break; + case "name": client.Name = value; break; + case "sub": client.SubscriptionCount = Format.ParseInt32(value); break; + case "psub": client.PatternSubscriptionCount = Format.ParseInt32(value); break; + case "ssub": client.ShardedSubscriptionCount = Format.ParseInt32(value); break; + case "multi": client.TransactionCommandLength = Format.ParseInt32(value); break; + case "cmd": client.LastCommand = value; break; + case "flags": + client.FlagsRaw = value; + ClientFlags flags = ClientFlags.None; + AddFlag(ref flags, value, ClientFlags.CloseASAP, 'A'); + AddFlag(ref flags, value, ClientFlags.Blocked, 'b'); + AddFlag(ref flags, value, ClientFlags.Closing, 'c'); + AddFlag(ref flags, value, ClientFlags.TransactionDoomed, 'd'); + // i: deprecated + AddFlag(ref flags, value, ClientFlags.Master, 'M'); + // N: not needed + AddFlag(ref flags, value, ClientFlags.ReplicaMonitor, 'O'); + AddFlag(ref flags, value, ClientFlags.PubSubSubscriber, 'P'); + AddFlag(ref flags, value, ClientFlags.ReadOnlyCluster, 'r'); + AddFlag(ref flags, value, ClientFlags.Replica, 'S'); + AddFlag(ref flags, value, ClientFlags.Unblocked, 'u'); + AddFlag(ref flags, value, ClientFlags.UnixDomainSocket, 'U'); + AddFlag(ref flags, value, ClientFlags.Transaction, 'x'); + + AddFlag(ref flags, value, ClientFlags.KeysTracking, 't'); + AddFlag(ref flags, value, ClientFlags.TrackingTargetInvalid, 'R'); + AddFlag(ref flags, value, ClientFlags.BroadcastTracking, 'B'); + + client.Flags = flags; + break; + case "id": client.Id = Format.ParseInt64(value); break; + case "resp": client.ProtocolVersion = value; break; + case "lib-name": client.LibraryName = value; break; + case "lib-ver": client.LibraryVersion = value; break; + } + } + clients.Add(client); + } + } + + clientList = clients.ToArray(); + return true; + } + + private static void AddFlag(ref ClientFlags value, string raw, ClientFlags toAdd, char token) + { + if (raw.IndexOf(token) >= 0) value |= toAdd; + } + + private sealed class ClientInfoProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.BulkString: + var raw = result.GetString(); + if (TryParse(raw, out var clients)) + { + SetResult(message, clients); + return true; + } + break; + } + return false; + } + } + } +} diff --git a/src/StackExchange.Redis/ClusterConfiguration.cs b/src/StackExchange.Redis/ClusterConfiguration.cs new file mode 100644 index 000000000..99488ddff --- /dev/null +++ b/src/StackExchange.Redis/ClusterConfiguration.cs @@ -0,0 +1,490 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; + +namespace StackExchange.Redis +{ + /// + /// Indicates a range of slots served by a cluster node. + /// + public readonly struct SlotRange : IEquatable, IComparable, IComparable + { + private readonly short from, to; + + /// + /// Create a new SlotRange value. + /// + /// The slot ID to start at. + /// The slot ID to end at. + public SlotRange(int from, int to) + { + checked + { + this.from = (short)from; + this.to = (short)to; + } + } + + private SlotRange(short from, short to) + { + this.from = from; + this.to = to; + } + + /// + /// The start of the range (inclusive). + /// + public int From => from; + + /// + /// The end of the range (inclusive). + /// + public int To => to; + + /// + /// Indicates whether two ranges are not equal. + /// + /// The first slot range. + /// The second slot range. + public static bool operator !=(SlotRange x, SlotRange y) => x.from != y.from || x.to != y.to; + + /// + /// Indicates whether two ranges are equal. + /// + /// The first slot range. + /// The second slot range. + public static bool operator ==(SlotRange x, SlotRange y) => x.from == y.from && x.to == y.to; + + /// + /// Try to parse a string as a range. + /// + /// The range string to parse, e.g."1-12". + /// The parsed , if successful. + public static bool TryParse(string range, out SlotRange value) + { + if (string.IsNullOrWhiteSpace(range)) + { + value = default; + return false; + } + int i = range.IndexOf('-'); + short from; + if (i < 0) + { + if (TryParseInt16(range, 0, range.Length, out from)) + { + value = new SlotRange(from, from); + return true; + } + } + else + { + if (TryParseInt16(range, 0, i++, out from) && TryParseInt16(range, i, range.Length - i, out short to)) + { + value = new SlotRange(from, to); + return true; + } + } + value = default; + return false; + } + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// + /// The other slot range to compare to. + public int CompareTo(SlotRange other) + { + int delta = (int)from - (int)other.from; + return delta == 0 ? (int)to - (int)other.to : delta; + } + + /// + /// See . + /// + /// The other slot range to compare to. + public override bool Equals(object? obj) => obj is SlotRange sRange && Equals(sRange); + + /// + /// Indicates whether two ranges are equal. + /// + /// The other slot range to compare to. + public bool Equals(SlotRange other) => other.from == from && other.to == to; + + /// + public override int GetHashCode() + { + int x = from, y = to; // makes CS0675 a little happier + return x | (y << 16); + } + + /// + /// String representation ("{from}-{to}") of the range. + /// + public override string ToString() => from == to ? from.ToString() : (from + "-" + to); + + internal bool Includes(int hashSlot) => hashSlot >= from && hashSlot <= to; + + private static bool TryParseInt16(string s, int offset, int count, out short value) + { + checked + { + value = 0; + int tmp = 0; + for (int i = 0; i < count; i++) + { + char c = s[offset + i]; + if (c < '0' || c > '9') return false; + tmp = (tmp * 10) + (c - '0'); + } + value = (short)tmp; + return true; + } + } + + int IComparable.CompareTo(object? obj) => obj is SlotRange sRange ? CompareTo(sRange) : -1; + } + + /// + /// Describes the state of the cluster as reported by a single node. + /// + public sealed class ClusterConfiguration + { + private readonly Dictionary nodeLookup = new(); + + private readonly ServerSelectionStrategy serverSelectionStrategy; + internal ClusterConfiguration(ServerSelectionStrategy serverSelectionStrategy, string nodes, EndPoint origin) + { + // Beware: Any exception thrown here will wreak silent havoc like inability to connect to cluster nodes or non returning calls + this.serverSelectionStrategy = serverSelectionStrategy; + Origin = origin; + using (var reader = new StringReader(nodes)) + { + while (reader.ReadLine() is string line) + { + if (string.IsNullOrWhiteSpace(line)) continue; + var node = new ClusterNode(this, line, origin); + + // Be resilient to ":0 {primary,replica},fail,noaddr" nodes, and nodes where the endpoint doesn't parse + if (node.IsNoAddr || node.IsFail || node.EndPoint == null) + continue; + + // Override the origin value with the endpoint advertised with the target node to + // make sure that things like clusterConfiguration[clusterConfiguration.Origin] + // will work as expected. + if (node.IsMyself) + Origin = node.EndPoint; + + if (nodeLookup.TryGetValue(node.EndPoint, out var lookedUpNode)) + { + // Deal with conflicting node entries for the same endpoint + // This can happen in dynamic environments when a node goes down and a new one is created + // to replace it. + if (!node.IsConnected) + { + // The node we're trying to add is probably about to become stale. Ignore it. + continue; + } + else if (!lookedUpNode.IsConnected) + { + // The node we registered previously is probably stale. Replace it with a known good node. + nodeLookup[node.EndPoint] = node; + } + else + { + // We have conflicting connected nodes. There's nothing much we can do other than + // wait for the cluster state to converge and refresh on the next pass. + // The same is true if we have multiple disconnected nodes. + } + } + else + { + nodeLookup.Add(node.EndPoint, node); + } + } + } + } + + /// + /// Gets all nodes contained in the configuration. + /// + public ICollection Nodes => nodeLookup.Values; + + /// + /// The node that was asked for the configuration. + /// + public EndPoint Origin { get; } + + /// + /// Obtain the node relating to a specified endpoint. + /// + /// The endpoint to get a cluster node from. + public ClusterNode? this[EndPoint endpoint] => endpoint == null + ? null + : nodeLookup.TryGetValue(endpoint, out ClusterNode? result) ? result : null; + + internal ClusterNode? this[string nodeId] + { + get + { + if (string.IsNullOrWhiteSpace(nodeId)) return null; + foreach (var pair in nodeLookup) + { + if (pair.Value.NodeId == nodeId) return pair.Value; + } + return null; + } + } + + /// + /// Gets the node that serves the specified slot. + /// + /// The slot ID to get a node by. + public ClusterNode? GetBySlot(int slot) + { + foreach (var node in Nodes) + { + if (!node.IsReplica && node.ServesSlot(slot)) return node; + } + return null; + } + + /// + /// Gets the node that serves the specified key's slot. + /// + /// The key to identify a node by. + public ClusterNode? GetBySlot(RedisKey key) => GetBySlot(serverSelectionStrategy.HashSlot(key)); + } + + /// + /// Represents the configuration of a single node in a cluster configuration. + /// + /// + public sealed class ClusterNode : IEquatable, IComparable, IComparable + { + private readonly ClusterConfiguration configuration; + private IList? children; + private ClusterNode? parent; + private string? toString; + + internal ClusterNode(ClusterConfiguration configuration, string raw, EndPoint origin) + { + this.configuration = configuration; + Raw = raw; + var parts = raw.Split(StringSplits.Space); + + var flags = parts[2].Split(StringSplits.Comma); + + // redis 4 changes the format of "cluster nodes" - adds @... to the endpoint + var ep = parts[1]; + int at = ep.IndexOf('@'); + if (at >= 0) ep = ep.Substring(0, at); + + if (Format.TryParseEndPoint(ep, out var epResult)) + { + EndPoint = epResult; + } + if (flags.Contains("myself")) + { + IsMyself = true; + if (EndPoint == null) + { + // Unconfigured cluster nodes might report themselves as endpoint ":{port}", + // hence the origin fallback value to make sure that we can address them + EndPoint = origin; + } + } + + NodeId = parts[0]; + IsFail = flags.Contains("fail"); + IsPossiblyFail = flags.Contains("fail?"); + IsReplica = flags.Contains("slave") || flags.Contains("replica"); + IsNoAddr = flags.Contains("noaddr"); + ParentNodeId = string.IsNullOrWhiteSpace(parts[3]) ? null : parts[3]; + + List? slots = null; + + for (int i = 8; i < parts.Length; i++) + { + if (SlotRange.TryParse(parts[i], out SlotRange range)) + { + (slots ??= new List(parts.Length - i)).Add(range); + } + } + Slots = slots?.AsReadOnly() ?? (IList)Array.Empty(); + IsConnected = parts[7] == "connected"; // Can be "connected" or "disconnected" + } + + /// + /// Gets all child nodes of the current node. + /// + public IList Children + { + get + { + if (children is not null) return children; + + List? nodes = null; + foreach (var node in configuration.Nodes) + { + if (node.ParentNodeId == NodeId) + { + (nodes ??= new List()).Add(node); + } + } + children = nodes?.AsReadOnly() ?? (IList)Array.Empty(); + return children; + } + } + + /// + /// Gets the endpoint of the current node. + /// + public EndPoint? EndPoint { get; } + + /// + /// Gets whether this node is in a failed state. + /// + public bool IsFail { get; } + + /// + /// Gets whether this node is possibly in a failed state. + /// Possibly here means the node we're getting status from can't communicate with it, but doesn't mean it's down for sure. + /// + public bool IsPossiblyFail { get; } + + /// + /// Gets whether this is the node which responded to the CLUSTER NODES request. + /// + public bool IsMyself { get; } + + /// + /// Gets whether this node is a replica. + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(IsReplica) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public bool IsSlave => IsReplica; + + /// + /// Gets whether this node is a replica. + /// + public bool IsReplica { get; } + + /// + /// Gets whether this node is flagged as noaddr. + /// + public bool IsNoAddr { get; } + + /// + /// Gets the node's connection status. + /// + public bool IsConnected { get; } + + /// + /// Gets the unique node-id of the current node. + /// + public string NodeId { get; } + + /// + /// Gets the parent node of the current node. + /// + public ClusterNode? Parent => (parent is not null) ? parent = configuration[ParentNodeId!] : null; + + /// + /// Gets the unique node-id of the parent of the current node. + /// + public string? ParentNodeId { get; } + + /// + /// The configuration as reported by the server. + /// + public string Raw { get; } + + /// + /// The slots owned by this server. + /// + public IList Slots { get; } + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// + /// The to compare to. + public int CompareTo(ClusterNode? other) + { + if (other == null) return -1; + + if (IsReplica != other.IsReplica) return IsReplica ? 1 : -1; // primaries first + + // both replicas? compare by parent, so we get primaries A, B, C and then replicas of A, B, C + if (IsReplica) + { + int i = string.CompareOrdinal(ParentNodeId, other.ParentNodeId); + if (i != 0) return i; + } + return string.CompareOrdinal(NodeId, other.NodeId); + } + + /// + /// See . + /// + /// The to compare to. + public override bool Equals(object? obj) => Equals(obj as ClusterNode); + + /// + /// Indicates whether two instances are equivalent. + /// + /// The to compare to. + public bool Equals(ClusterNode? other) => other is ClusterNode node && ToString() == node.ToString(); + + /// + public override int GetHashCode() => ToString().GetHashCode(); + + /// + /// A string summary of this cluster configuration. + /// + public override string ToString() + { + if (toString is not null) return toString; + var sb = new StringBuilder().Append(NodeId).Append(" at ").Append(EndPoint); + if (IsReplica) + { + sb.Append(", replica of ").Append(ParentNodeId); + if (Parent is ClusterNode parent) sb.Append(" at ").Append(parent.EndPoint); + } + var childCount = Children.Count; + switch (childCount) + { + case 0: break; + case 1: sb.Append(", 1 replica"); break; + default: sb.Append(", ").Append(childCount).Append(" replicas"); break; + } + if (Slots.Count != 0) + { + sb.Append(", slots: "); + foreach (var slot in Slots) + { + sb.Append(slot).Append(' '); + } + sb.Length--; // remove tailing space + } + return toString = sb.ToString(); + } + + internal bool ServesSlot(int hashSlot) + { + foreach (var slot in Slots) + { + if (slot.Includes(hashSlot)) return true; + } + return false; + } + + int IComparable.CompareTo(object? obj) => CompareTo(obj as ClusterNode); + } +} diff --git a/src/StackExchange.Redis/CommandBytes.cs b/src/StackExchange.Redis/CommandBytes.cs new file mode 100644 index 000000000..19a69549b --- /dev/null +++ b/src/StackExchange.Redis/CommandBytes.cs @@ -0,0 +1,207 @@ +using System; +using System.Buffers; +using System.Text; + +namespace StackExchange.Redis +{ + internal readonly struct CommandBytes : IEquatable + { + private static Encoding Encoding => Encoding.UTF8; + + internal static unsafe CommandBytes TrimToFit(string value) + { + if (string.IsNullOrWhiteSpace(value)) return default; + value = value.Trim(); + var len = Encoding.GetByteCount(value); + if (len <= MaxLength) return new CommandBytes(value); // all fits + + fixed (char* c = value) + { + byte* b = stackalloc byte[ChunkLength * sizeof(ulong)]; + var encoder = PhysicalConnection.GetPerThreadEncoder(); + encoder.Convert(c, value.Length, b, MaxLength, true, out var maxLen, out _, out var isComplete); + if (!isComplete) maxLen--; + return new CommandBytes(value.Substring(0, maxLen)); + } + } + + // Uses [n=4] x UInt64 values to store a command payload, + // allowing allocation free storage and efficient + // equality tests. If you're glancing at this and thinking + // "that's what fixed buffers are for", please see: + // https://github.com/dotnet/coreclr/issues/19149 + // + // note: this tries to use case insensitive comparison + private readonly ulong _0, _1, _2, _3; + private const int ChunkLength = 4; // must reflect qty above + + public const int MaxLength = (ChunkLength * sizeof(ulong)) - 1; + + public override int GetHashCode() + { + var hashCode = -1923861349; + hashCode = (hashCode * -1521134295) + _0.GetHashCode(); + hashCode = (hashCode * -1521134295) + _1.GetHashCode(); + hashCode = (hashCode * -1521134295) + _2.GetHashCode(); + hashCode = (hashCode * -1521134295) + _3.GetHashCode(); + return hashCode; + } + + public override bool Equals(object? obj) => obj is CommandBytes cb && Equals(cb); + + bool IEquatable.Equals(CommandBytes other) => _0 == other._0 && _1 == other._1 && _2 == other._2 && _3 == other._3; + + public bool Equals(in CommandBytes other) => _0 == other._0 && _1 == other._1 && _2 == other._2 && _3 == other._3; + + // note: don't add == operators; with the implicit op above, that invalidates "==null" compiler checks (which should report a failure!) + public static implicit operator CommandBytes(string value) => new CommandBytes(value); + + public override unsafe string ToString() + { + fixed (ulong* uPtr = &_0) + { + var bPtr = (byte*)uPtr; + int len = *bPtr; + return len == 0 ? "" : Encoding.GetString(bPtr + 1, *bPtr); + } + } + public unsafe int Length + { + get + { + fixed (ulong* uPtr = &_0) + { + return *(byte*)uPtr; + } + } + } + + public bool IsEmpty => _0 == 0L; // cheap way of checking zero length + + public unsafe void CopyTo(Span target) + { + fixed (ulong* uPtr = &_0) + { + byte* bPtr = (byte*)uPtr; + new Span(bPtr + 1, *bPtr).CopyTo(target); + } + } + + public unsafe byte this[int index] + { + get + { + fixed (ulong* uPtr = &_0) + { + byte* bPtr = (byte*)uPtr; + int len = *bPtr; + if (index < 0 || index >= len) throw new IndexOutOfRangeException(); + return bPtr[index + 1]; + } + } + } + + public unsafe CommandBytes(string? value) + { + _0 = _1 = _2 = _3 = 0L; + if (value.IsNullOrEmpty()) return; + + var len = Encoding.GetByteCount(value); + if (len > MaxLength) throw new ArgumentOutOfRangeException($"Command '{value}' exceeds library limit of {MaxLength} bytes"); + fixed (ulong* uPtr = &_0) + { + byte* bPtr = (byte*)uPtr; + fixed (char* cPtr = value) + { + len = Encoding.GetBytes(cPtr, value.Length, bPtr + 1, MaxLength); + } + *bPtr = (byte)UpperCasify(len, bPtr + 1); + } + } + + public unsafe CommandBytes(ReadOnlySpan value) + { + if (value.Length > MaxLength) throw new ArgumentOutOfRangeException("Maximum command length exceeded: " + value.Length + " bytes"); + _0 = _1 = _2 = _3 = 0L; + fixed (ulong* uPtr = &_0) + { + byte* bPtr = (byte*)uPtr; + value.CopyTo(new Span(bPtr + 1, value.Length)); + *bPtr = (byte)UpperCasify(value.Length, bPtr + 1); + } + } + + public unsafe CommandBytes(in ReadOnlySequence value) + { + if (value.Length > MaxLength) throw new ArgumentOutOfRangeException(nameof(value), "Maximum command length exceeded"); + int len = unchecked((int)value.Length); + _0 = _1 = _2 = _3 = 0L; + fixed (ulong* uPtr = &_0) + { + byte* bPtr = (byte*)uPtr; + var target = new Span(bPtr + 1, len); + + if (value.IsSingleSegment) + { + value.First.Span.CopyTo(target); + } + else + { + foreach (var segment in value) + { + segment.Span.CopyTo(target); + target = target.Slice(segment.Length); + } + } + *bPtr = (byte)UpperCasify(len, bPtr + 1); + } + } + private unsafe int UpperCasify(int len, byte* bPtr) + { + const ulong HighBits = 0x8080808080808080; + if (((_0 | _1 | _2 | _3) & HighBits) == 0) + { + // no Unicode; use ASCII bit bricks + for (int i = 0; i < len; i++) + { + *bPtr = ToUpperInvariantAscii(*bPtr++); + } + return len; + } + else + { + return UpperCasifyUnicode(len, bPtr); + } + } + + private static unsafe int UpperCasifyUnicode(int oldLen, byte* bPtr) + { + const int MaxChars = ChunkLength * sizeof(ulong); // leave rounded up; helps stackalloc + char* workspace = stackalloc char[MaxChars]; + int charCount = Encoding.GetChars(bPtr, oldLen, workspace, MaxChars); + char* c = workspace; + for (int i = 0; i < charCount; i++) + { + *c = char.ToUpperInvariant(*c++); + } + int newLen = Encoding.GetBytes(workspace, charCount, bPtr, MaxLength); + // don't forget to zero any shrink + for (int i = newLen; i < oldLen; i++) + { + bPtr[i] = 0; + } + return newLen; + } + + private static byte ToUpperInvariantAscii(byte b) => b >= 'a' && b <= 'z' ? (byte)(b - 32) : b; + + internal unsafe byte[] ToArray() + { + fixed (ulong* uPtr = &_0) + { + byte* bPtr = (byte*)uPtr; + return new Span(bPtr + 1, *bPtr).ToArray(); + } + } + } +} diff --git a/src/StackExchange.Redis/CommandMap.cs b/src/StackExchange.Redis/CommandMap.cs new file mode 100644 index 000000000..1f31d3224 --- /dev/null +++ b/src/StackExchange.Redis/CommandMap.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace StackExchange.Redis +{ + /// + /// Represents the commands mapped on a particular configuration. + /// + public sealed class CommandMap + { + private readonly CommandBytes[] map; + + internal CommandMap(CommandBytes[] map) => this.map = map; + + /// + /// The default commands specified by redis. + /// + public static CommandMap Default { get; } = CreateImpl(null, null); + + /// + /// The commands available to twemproxy. + /// + /// + public static CommandMap Twemproxy { get; } = CreateImpl(null, exclusions: new HashSet + { + RedisCommand.KEYS, RedisCommand.MIGRATE, RedisCommand.MOVE, RedisCommand.OBJECT, RedisCommand.RANDOMKEY, + RedisCommand.RENAME, RedisCommand.RENAMENX, RedisCommand.SCAN, + + RedisCommand.BITOP, RedisCommand.MSETNX, + + RedisCommand.BLPOP, RedisCommand.BRPOP, RedisCommand.BRPOPLPUSH, // yeah, me neither! + + RedisCommand.PSUBSCRIBE, RedisCommand.PUBLISH, RedisCommand.PUNSUBSCRIBE, RedisCommand.SUBSCRIBE, RedisCommand.UNSUBSCRIBE, RedisCommand.SPUBLISH, RedisCommand.SSUBSCRIBE, RedisCommand.SUNSUBSCRIBE, + + RedisCommand.DISCARD, RedisCommand.EXEC, RedisCommand.MULTI, RedisCommand.UNWATCH, RedisCommand.WATCH, + + RedisCommand.SCRIPT, + + RedisCommand.ECHO, RedisCommand.SELECT, + + RedisCommand.BGREWRITEAOF, RedisCommand.BGSAVE, RedisCommand.CLIENT, RedisCommand.CLUSTER, RedisCommand.CONFIG, RedisCommand.DBSIZE, + RedisCommand.DEBUG, RedisCommand.FLUSHALL, RedisCommand.FLUSHDB, RedisCommand.INFO, RedisCommand.LASTSAVE, RedisCommand.MONITOR, RedisCommand.REPLICAOF, + RedisCommand.SAVE, RedisCommand.SHUTDOWN, RedisCommand.SLAVEOF, RedisCommand.SLOWLOG, RedisCommand.SYNC, RedisCommand.TIME, + }); + + /// + /// The commands available to envoyproxy. + /// + /// + public static CommandMap Envoyproxy { get; } = CreateImpl(null, exclusions: new HashSet + { + RedisCommand.KEYS, RedisCommand.MIGRATE, RedisCommand.MOVE, RedisCommand.OBJECT, RedisCommand.RANDOMKEY, + RedisCommand.RENAME, RedisCommand.RENAMENX, RedisCommand.SORT, RedisCommand.SCAN, + + RedisCommand.BITOP, RedisCommand.MSETNX, + + RedisCommand.BLPOP, RedisCommand.BRPOP, RedisCommand.BRPOPLPUSH, // yeah, me neither! + + RedisCommand.PSUBSCRIBE, RedisCommand.PUBLISH, RedisCommand.PUNSUBSCRIBE, RedisCommand.SUBSCRIBE, RedisCommand.UNSUBSCRIBE, RedisCommand.SPUBLISH, RedisCommand.SSUBSCRIBE, RedisCommand.SUNSUBSCRIBE, + + RedisCommand.SCRIPT, + + RedisCommand.SELECT, + + RedisCommand.BGREWRITEAOF, RedisCommand.BGSAVE, RedisCommand.CLIENT, RedisCommand.CLUSTER, RedisCommand.CONFIG, RedisCommand.DBSIZE, + RedisCommand.DEBUG, RedisCommand.FLUSHALL, RedisCommand.FLUSHDB, RedisCommand.INFO, RedisCommand.LASTSAVE, RedisCommand.MONITOR, RedisCommand.REPLICAOF, + RedisCommand.SAVE, RedisCommand.SHUTDOWN, RedisCommand.SLAVEOF, RedisCommand.SLOWLOG, RedisCommand.SYNC, RedisCommand.TIME, + + // supported by envoy but not enabled by stack exchange + // RedisCommand.BITFIELD, + // + // RedisCommand.GEORADIUS_RO, + // RedisCommand.GEORADIUSBYMEMBER_RO, + }); + + /// + /// The commands available to SSDB. + /// + /// + public static CommandMap SSDB { get; } = Create( + new HashSet + { + "ping", + "get", "set", "del", "incr", "incrby", "mget", "mset", "keys", "getset", "setnx", + "hget", "hset", "hdel", "hincrby", "hkeys", "hvals", "hmget", "hmset", "hlen", + "zscore", "zadd", "zrem", "zrange", "zrangebyscore", "zincrby", "zdecrby", "zcard", + "llen", "lpush", "rpush", "lpop", "rpop", "lrange", "lindex", + }, + true); + + /// + /// The commands available to Sentinel. + /// + /// + public static CommandMap Sentinel { get; } = Create( + new HashSet + { + "auth", "hello", "ping", "info", "role", "sentinel", "subscribe", "shutdown", "psubscribe", "unsubscribe", "punsubscribe", + }, + true); + + /// + /// Create a new , customizing some commands. + /// + /// The commands to override. + public static CommandMap Create(Dictionary? overrides) + { + if (overrides == null || overrides.Count == 0) return Default; + + if (ReferenceEquals(overrides.Comparer, StringComparer.OrdinalIgnoreCase)) + { + // that's ok; we're happy with ordinal/invariant case-insensitive + // (but not culture-specific insensitive; completely untested) + } + else + { + // need case insensitive + overrides = new Dictionary(overrides, StringComparer.OrdinalIgnoreCase); + } + return CreateImpl(overrides, null); + } + + /// + /// Creates a by specifying which commands are available or unavailable. + /// + /// The commands to specify. + /// Whether the commands are available or excluded. + public static CommandMap Create(HashSet commands, bool available = true) + { + if (available) + { + var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + // nix everything + foreach (RedisCommand command in (RedisCommand[])Enum.GetValues(typeof(RedisCommand))) + { + dictionary[command.ToString()] = null; + } + if (commands != null) + { + // then include (by removal) the things that are available + foreach (string command in commands) + { + dictionary.Remove(command); + } + } + return CreateImpl(dictionary, null); + } + else + { + HashSet? exclusions = null; + if (commands != null) + { + // nix the things that are specified + foreach (var command in commands) + { + if (Enum.TryParse(command, true, out RedisCommand parsed)) + { + (exclusions ??= new HashSet()).Add(parsed); + } + } + } + if (exclusions == null || exclusions.Count == 0) return Default; + return CreateImpl(null, exclusions); + } + } + + /// + /// See . + /// + public override string ToString() + { + var sb = new StringBuilder(); + AppendDeltas(sb); + return sb.ToString(); + } + + internal void AppendDeltas(StringBuilder sb) + { + for (int i = 0; i < map.Length; i++) + { + var keyString = ((RedisCommand)i).ToString(); + var keyBytes = new CommandBytes(keyString); + var value = map[i]; + if (!keyBytes.Equals(value)) + { + if (sb.Length != 0) sb.Append(','); + sb.Append('$').Append(keyString).Append('=').Append(value); + } + } + } + + internal void AssertAvailable(RedisCommand command) + { + if (map[(int)command].IsEmpty) throw ExceptionFactory.CommandDisabled(command); + } + + internal CommandBytes GetBytes(RedisCommand command) => map[(int)command]; + + internal CommandBytes GetBytes(string command) + { + if (command == null) return default; + if (Enum.TryParse(command, true, out RedisCommand cmd)) + { + // we know that one! + return map[(int)cmd]; + } + return new CommandBytes(command); + } + + internal bool IsAvailable(RedisCommand command) => !map[(int)command].IsEmpty; + + private static CommandMap CreateImpl(Dictionary? caseInsensitiveOverrides, HashSet? exclusions) + { + var commands = (RedisCommand[])Enum.GetValues(typeof(RedisCommand)); + + var map = new CommandBytes[commands.Length]; + for (int i = 0; i < commands.Length; i++) + { + int idx = (int)commands[i]; + string? name = commands[i].ToString(), value = name; + + if (exclusions?.Contains(commands[i]) == true) + { + map[idx] = default; + } + else + { + if (caseInsensitiveOverrides != null && caseInsensitiveOverrides.TryGetValue(name, out string? tmp)) + { + value = tmp; + } + map[idx] = new CommandBytes(value); + } + } + return new CommandMap(map); + } + } +} diff --git a/src/StackExchange.Redis/CommandTrace.cs b/src/StackExchange.Redis/CommandTrace.cs new file mode 100644 index 000000000..a61499f0c --- /dev/null +++ b/src/StackExchange.Redis/CommandTrace.cs @@ -0,0 +1,96 @@ +using System; + +namespace StackExchange.Redis +{ + /// + /// Represents the information known about long-running commands. + /// + public sealed class CommandTrace + { + internal static readonly ResultProcessor Processor = new CommandTraceProcessor(); + + internal CommandTrace(long uniqueId, long time, long duration, RedisValue[] arguments) + { + UniqueId = uniqueId; + Time = RedisBase.UnixEpoch.AddSeconds(time); + // duration = The amount of time needed for its execution, in microseconds. + // A tick is equal to 100 nanoseconds, or one ten-millionth of a second. + // So 1 microsecond = 10 ticks + Duration = TimeSpan.FromTicks(duration * 10); + Arguments = arguments; + } + + /// + /// The array composing the arguments of the command. + /// + public RedisValue[] Arguments { get; } + + /// + /// The amount of time needed for its execution. + /// + public TimeSpan Duration { get; } + + /// + /// The time at which the logged command was processed. + /// + public DateTime Time { get; } + + /// + /// A unique progressive identifier for every slow log entry. + /// + /// The entry's unique ID can be used in order to avoid processing slow log entries multiple times (for instance you may have a script sending you an email alert for every new slow log entry). The ID is never reset in the course of the Redis server execution, only a server restart will reset it. + public long UniqueId { get; } + + /// + /// Deduces a link to the redis documentation about the specified command. + /// + public string? GetHelpUrl() + { + if (Arguments == null || Arguments.Length == 0) return null; + + const string BaseUrl = "https://redis.io/commands/"; + + string encoded0 = Uri.EscapeDataString(((string)Arguments[0]!).ToLowerInvariant()); + + if (Arguments.Length > 1) + { + switch (encoded0) + { + case "script": + case "client": + case "cluster": + case "config": + case "debug": + case "pubsub": + string encoded1 = Uri.EscapeDataString(((string)Arguments[1]!).ToLowerInvariant()); + return BaseUrl + encoded0 + "-" + encoded1; + } + } + return BaseUrl + encoded0; + } + + private sealed class CommandTraceProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var parts = result.GetItems(); + CommandTrace[] arr = new CommandTrace[parts.Length]; + int i = 0; + foreach (var item in parts) + { + var subParts = item.GetItems(); + if (!subParts[0].TryGetInt64(out long uniqueid) || !subParts[1].TryGetInt64(out long time) || !subParts[2].TryGetInt64(out long duration)) + return false; + arr[i++] = new CommandTrace(uniqueid, time, duration, subParts[3].GetItemsAsValues()!); + } + SetResult(message, arr); + return true; + } + return false; + } + } + } +} diff --git a/src/StackExchange.Redis/CompletedDefaultTask.cs b/src/StackExchange.Redis/CompletedDefaultTask.cs new file mode 100644 index 000000000..1035cb6a8 --- /dev/null +++ b/src/StackExchange.Redis/CompletedDefaultTask.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + internal static class CompletedTask + { + private static readonly Task defaultTask = FromResult(default(T), null); + + public static Task Default(object? asyncState) => asyncState == null ? defaultTask : FromResult(default(T), asyncState); + + public static Task FromResult(T? value, object? asyncState) + { + if (asyncState == null) return Task.FromResult(value); + // note we do not need to deny exec-sync here; the value will be known + // before we hand it to them + var tcs = TaskSource.Create(asyncState); + tcs.SetResult(value); + return tcs.Task; + } + + public static Task FromDefault(T value, object? asyncState) + { + if (asyncState == null) return Task.FromResult(value); + // note we do not need to deny exec-sync here; the value will be known + // before we hand it to them + var tcs = TaskSource.Create(asyncState); + tcs.SetResult(value); + return tcs.Task; + } + } +} diff --git a/src/StackExchange.Redis/Condition.cs b/src/StackExchange.Redis/Condition.cs new file mode 100644 index 000000000..ec7ee53b6 --- /dev/null +++ b/src/StackExchange.Redis/Condition.cs @@ -0,0 +1,954 @@ +using System; +using System.Collections.Generic; + +namespace StackExchange.Redis +{ + /// + /// Describes a precondition used in a redis transaction. + /// + public abstract class Condition + { + internal abstract Condition MapKeys(Func map); + + private Condition() { } + + /// + /// Enforces that the given hash-field must have the specified value. + /// + /// The key of the hash to check. + /// The field in the hash to check. + /// The value that the hash field must match. + public static Condition HashEqual(RedisKey key, RedisValue hashField, RedisValue value) + { + if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField)); + if (value.IsNull) return HashNotExists(key, hashField); + return new EqualsCondition(key, RedisType.Hash, hashField, true, value); + } + + /// + /// Enforces that the given hash-field must exist. + /// + /// The key of the hash to check. + /// The field in the hash to check. + public static Condition HashExists(RedisKey key, RedisValue hashField) + { + if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField)); + return new ExistsCondition(key, RedisType.Hash, hashField, true); + } + + /// + /// Enforces that the given hash-field must not have the specified value. + /// + /// The key of the hash to check. + /// The field in the hash to check. + /// The value that the hash field must not match. + public static Condition HashNotEqual(RedisKey key, RedisValue hashField, RedisValue value) + { + if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField)); + if (value.IsNull) return HashExists(key, hashField); + return new EqualsCondition(key, RedisType.Hash, hashField, false, value); + } + + /// + /// Enforces that the given hash-field must not exist. + /// + /// The key of the hash to check. + /// The field in the hash that must not exist. + public static Condition HashNotExists(RedisKey key, RedisValue hashField) + { + if (hashField.IsNull) throw new ArgumentNullException(nameof(hashField)); + return new ExistsCondition(key, RedisType.Hash, hashField, false); + } + + /// + /// Enforces that the given key must exist. + /// + /// The key that must exist. + public static Condition KeyExists(RedisKey key) => new ExistsCondition(key, RedisType.None, RedisValue.Null, true); + + /// + /// Enforces that the given key must not exist. + /// + /// The key that must not exist. + public static Condition KeyNotExists(RedisKey key) => new ExistsCondition(key, RedisType.None, RedisValue.Null, false); + + /// + /// Enforces that the given list index must have the specified value. + /// + /// The key of the list to check. + /// The position in the list to check. + /// The value of the list position that must match. + public static Condition ListIndexEqual(RedisKey key, long index, RedisValue value) => new ListCondition(key, index, true, value); + + /// + /// Enforces that the given list index must exist. + /// + /// The key of the list to check. + /// The position in the list that must exist. + public static Condition ListIndexExists(RedisKey key, long index) => new ListCondition(key, index, true, null); + + /// + /// Enforces that the given list index must not have the specified value. + /// + /// The key of the list to check. + /// The position in the list to check. + /// The value of the list position must not match. + public static Condition ListIndexNotEqual(RedisKey key, long index, RedisValue value) => new ListCondition(key, index, false, value); + + /// + /// Enforces that the given list index must not exist. + /// + /// The key of the list to check. + /// The position in the list that must not exist. + public static Condition ListIndexNotExists(RedisKey key, long index) => new ListCondition(key, index, false, null); + + /// + /// Enforces that the given key must have the specified value. + /// + /// The key to check. + /// The value that must match. + public static Condition StringEqual(RedisKey key, RedisValue value) + { + if (value.IsNull) return KeyNotExists(key); + return new EqualsCondition(key, RedisType.Hash, RedisValue.Null, true, value); + } + + /// + /// Enforces that the given key must not have the specified value. + /// + /// The key to check. + /// The value that must not match. + public static Condition StringNotEqual(RedisKey key, RedisValue value) + { + if (value.IsNull) return KeyExists(key); + return new EqualsCondition(key, RedisType.Hash, RedisValue.Null, false, value); + } + + /// + /// Enforces that the given hash length is a certain value. + /// + /// The key of the hash to check. + /// The length the hash must have. + public static Condition HashLengthEqual(RedisKey key, long length) => new LengthCondition(key, RedisType.Hash, 0, length); + + /// + /// Enforces that the given hash length is less than a certain value. + /// + /// The key of the hash to check. + /// The length the hash must be less than. + public static Condition HashLengthLessThan(RedisKey key, long length) => new LengthCondition(key, RedisType.Hash, 1, length); + + /// + /// Enforces that the given hash length is greater than a certain value. + /// + /// The key of the hash to check. + /// The length the hash must be greater than. + public static Condition HashLengthGreaterThan(RedisKey key, long length) => new LengthCondition(key, RedisType.Hash, -1, length); + + /// + /// Enforces that the given string length is a certain value. + /// + /// The key of the string to check. + /// The length the string must be equal to. + public static Condition StringLengthEqual(RedisKey key, long length) => new LengthCondition(key, RedisType.String, 0, length); + + /// + /// Enforces that the given string length is less than a certain value. + /// + /// The key of the string to check. + /// The length the string must be less than. + public static Condition StringLengthLessThan(RedisKey key, long length) => new LengthCondition(key, RedisType.String, 1, length); + + /// + /// Enforces that the given string length is greater than a certain value. + /// + /// The key of the string to check. + /// The length the string must be greater than. + public static Condition StringLengthGreaterThan(RedisKey key, long length) => new LengthCondition(key, RedisType.String, -1, length); + + /// + /// Enforces that the given list length is a certain value. + /// + /// The key of the list to check. + /// The length the list must be equal to. + public static Condition ListLengthEqual(RedisKey key, long length) => new LengthCondition(key, RedisType.List, 0, length); + + /// + /// Enforces that the given list length is less than a certain value. + /// + /// The key of the list to check. + /// The length the list must be less than. + public static Condition ListLengthLessThan(RedisKey key, long length) => new LengthCondition(key, RedisType.List, 1, length); + + /// + /// Enforces that the given list length is greater than a certain value. + /// + /// The key of the list to check. + /// The length the list must be greater than. + public static Condition ListLengthGreaterThan(RedisKey key, long length) => new LengthCondition(key, RedisType.List, -1, length); + + /// + /// Enforces that the given set cardinality is a certain value. + /// + /// The key of the set to check. + /// The length the set must be equal to. + public static Condition SetLengthEqual(RedisKey key, long length) => new LengthCondition(key, RedisType.Set, 0, length); + + /// + /// Enforces that the given set cardinality is less than a certain value. + /// + /// The key of the set to check. + /// The length the set must be less than. + public static Condition SetLengthLessThan(RedisKey key, long length) => new LengthCondition(key, RedisType.Set, 1, length); + + /// + /// Enforces that the given set cardinality is greater than a certain value. + /// + /// The key of the set to check. + /// The length the set must be greater than. + public static Condition SetLengthGreaterThan(RedisKey key, long length) => new LengthCondition(key, RedisType.Set, -1, length); + + /// + /// Enforces that the given set contains a certain member. + /// + /// The key of the set to check. + /// The member the set must contain. + public static Condition SetContains(RedisKey key, RedisValue member) => new ExistsCondition(key, RedisType.Set, member, true); + + /// + /// Enforces that the given set does not contain a certain member. + /// + /// The key of the set to check. + /// The member the set must not contain. + public static Condition SetNotContains(RedisKey key, RedisValue member) => new ExistsCondition(key, RedisType.Set, member, false); + + /// + /// Enforces that the given sorted set cardinality is a certain value. + /// + /// The key of the sorted set to check. + /// The length the sorted set must be equal to. + public static Condition SortedSetLengthEqual(RedisKey key, long length) => new LengthCondition(key, RedisType.SortedSet, 0, length); + + /// + /// Enforces that the given sorted set contains a certain number of members with scores in the given range. + /// + /// The key of the sorted set to check. + /// The length the sorted set must be equal to. + /// Minimum inclusive score. + /// Maximum inclusive score. + public static Condition SortedSetLengthEqual(RedisKey key, long length, double min = double.NegativeInfinity, double max = double.PositiveInfinity) => new SortedSetRangeLengthCondition(key, min, max, 0, length); + + /// + /// Enforces that the given sorted set cardinality is less than a certain value. + /// + /// The key of the sorted set to check. + /// The length the sorted set must be less than. + public static Condition SortedSetLengthLessThan(RedisKey key, long length) => new LengthCondition(key, RedisType.SortedSet, 1, length); + + /// + /// Enforces that the given sorted set contains less than a certain number of members with scores in the given range. + /// + /// The key of the sorted set to check. + /// The length the sorted set must be equal to. + /// Minimum inclusive score. + /// Maximum inclusive score. + public static Condition SortedSetLengthLessThan(RedisKey key, long length, double min = double.NegativeInfinity, double max = double.PositiveInfinity) => new SortedSetRangeLengthCondition(key, min, max, 1, length); + + /// + /// Enforces that the given sorted set cardinality is greater than a certain value. + /// + /// The key of the sorted set to check. + /// The length the sorted set must be greater than. + public static Condition SortedSetLengthGreaterThan(RedisKey key, long length) => new LengthCondition(key, RedisType.SortedSet, -1, length); + + /// + /// Enforces that the given sorted set contains more than a certain number of members with scores in the given range. + /// + /// The key of the sorted set to check. + /// The length the sorted set must be equal to. + /// Minimum inclusive score. + /// Maximum inclusive score. + public static Condition SortedSetLengthGreaterThan(RedisKey key, long length, double min = double.NegativeInfinity, double max = double.PositiveInfinity) => new SortedSetRangeLengthCondition(key, min, max, -1, length); + + /// + /// Enforces that the given sorted set contains a certain member. + /// + /// The key of the sorted set to check. + /// The member the sorted set must contain. + public static Condition SortedSetContains(RedisKey key, RedisValue member) => new ExistsCondition(key, RedisType.SortedSet, member, true); + + /// + /// Enforces that the given sorted set does not contain a certain member. + /// + /// The key of the sorted set to check. + /// The member the sorted set must not contain. + public static Condition SortedSetNotContains(RedisKey key, RedisValue member) => new ExistsCondition(key, RedisType.SortedSet, member, false); + + /// + /// Enforces that the given sorted set contains a member that starts with the specified prefix. + /// + /// The key of the sorted set to check. + /// The sorted set must contain at least one member that starts with the specified prefix. + public static Condition SortedSetContainsStarting(RedisKey key, RedisValue prefix) => new StartsWithCondition(key, prefix, true); + + /// + /// Enforces that the given sorted set does not contain a member that starts with the specified prefix. + /// + /// The key of the sorted set to check. + /// The sorted set must not contain at a member that starts with the specified prefix. + public static Condition SortedSetNotContainsStarting(RedisKey key, RedisValue prefix) => new StartsWithCondition(key, prefix, false); + + /// + /// Enforces that the given sorted set member must have the specified score. + /// + /// The key of the sorted set to check. + /// The member the sorted set to check. + /// The score that member must have. + public static Condition SortedSetEqual(RedisKey key, RedisValue member, RedisValue score) => new EqualsCondition(key, RedisType.SortedSet, member, true, score); + + /// + /// Enforces that the given sorted set member must not have the specified score. + /// + /// The key of the sorted set to check. + /// The member the sorted set to check. + /// The score that member must not have. + public static Condition SortedSetNotEqual(RedisKey key, RedisValue member, RedisValue score) => new EqualsCondition(key, RedisType.SortedSet, member, false, score); + + /// + /// Enforces that the given sorted set must have the given score. + /// + /// The key of the sorted set to check. + /// The score that the sorted set must have. + public static Condition SortedSetScoreExists(RedisKey key, RedisValue score) => new SortedSetScoreCondition(key, score, false, 0); + + /// + /// Enforces that the given sorted set must not have the given score. + /// + /// The key of the sorted set to check. + /// The score that the sorted set must not have. + public static Condition SortedSetScoreNotExists(RedisKey key, RedisValue score) => new SortedSetScoreCondition(key, score, true, 0); + + /// + /// Enforces that the given sorted set must have the specified count of the given score. + /// + /// The key of the sorted set to check. + /// The score that the sorted set must have. + /// The number of members which sorted set must have. + public static Condition SortedSetScoreExists(RedisKey key, RedisValue score, RedisValue count) => new SortedSetScoreCondition(key, score, true, count); + + /// + /// Enforces that the given sorted set must not have the specified count of the given score. + /// + /// The key of the sorted set to check. + /// The score that the sorted set must not have. + /// The number of members which sorted set must not have. + public static Condition SortedSetScoreNotExists(RedisKey key, RedisValue score, RedisValue count) => new SortedSetScoreCondition(key, score, false, count); + + /// + /// Enforces that the given stream length is a certain value. + /// + /// The key of the stream to check. + /// The length the stream must have. + public static Condition StreamLengthEqual(RedisKey key, long length) => new LengthCondition(key, RedisType.Stream, 0, length); + + /// + /// Enforces that the given stream length is less than a certain value. + /// + /// The key of the stream to check. + /// The length the stream must be less than. + public static Condition StreamLengthLessThan(RedisKey key, long length) => new LengthCondition(key, RedisType.Stream, 1, length); + + /// + /// Enforces that the given stream length is greater than a certain value. + /// + /// The key of the stream to check. + /// The length the stream must be greater than. + public static Condition StreamLengthGreaterThan(RedisKey key, long length) => new LengthCondition(key, RedisType.Stream, -1, length); + +#pragma warning restore RCS1231 + + internal abstract void CheckCommands(CommandMap commandMap); + + internal abstract IEnumerable CreateMessages(int db, IResultBox? resultBox); + + internal abstract int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy); + internal abstract bool TryValidate(in RawResult result, out bool value); + + internal sealed class ConditionProcessor : ResultProcessor + { + public static readonly ConditionProcessor Default = new(); + + public static Message CreateMessage(Condition condition, int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisValue value = default) => + new ConditionMessage(condition, db, flags, command, key, value); + + public static Message CreateMessage(Condition condition, int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value, in RedisValue value1) => + new ConditionMessage(condition, db, flags, command, key, value, value1); + + public static Message CreateMessage(Condition condition, int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) => + new ConditionMessage(condition, db, flags, command, key, value, value1, value2, value3, value4); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0071:Simplify interpolation", Justification = "Allocations (string.Concat vs. string.Format)")] + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + connection?.BridgeCouldBeNull?.Multiplexer?.OnTransactionLog($"condition '{message.CommandAndKey}' got '{result.ToString()}'"); + var msg = message as ConditionMessage; + var condition = msg?.Condition; + if (condition != null && condition.TryValidate(result, out bool final)) + { + SetResult(message, final); + return true; + } + return false; + } + + private sealed class ConditionMessage : Message.CommandKeyBase + { + public readonly Condition Condition; + private readonly RedisValue value; + private readonly RedisValue value1; + private readonly RedisValue value2; + private readonly RedisValue value3; + private readonly RedisValue value4; + + public ConditionMessage(Condition condition, int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value) + : base(db, flags, command, key) + { + Condition = condition; + this.value = value; // note no assert here + } + + public ConditionMessage(Condition condition, int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value, in RedisValue value1) + : this(condition, db, flags, command, key, value) + { + this.value1 = value1; // note no assert here + } + + // Message with 3 or 4 values not used, therefore not implemented + public ConditionMessage(Condition condition, int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) + : this(condition, db, flags, command, key, value, value1) + { + this.value2 = value2; // note no assert here + this.value3 = value3; // note no assert here + this.value4 = value4; // note no assert here + } + + protected override void WriteImpl(PhysicalConnection physical) + { + if (value.IsNull) + { + physical.WriteHeader(command, 1); + physical.Write(Key); + } + else + { + physical.WriteHeader(command, value1.IsNull ? 2 : value2.IsNull ? 3 : value3.IsNull ? 4 : value4.IsNull ? 5 : 6); + physical.Write(Key); + physical.WriteBulkString(value); + if (!value1.IsNull) + physical.WriteBulkString(value1); + if (!value2.IsNull) + physical.WriteBulkString(value2); + if (!value3.IsNull) + physical.WriteBulkString(value3); + if (!value4.IsNull) + physical.WriteBulkString(value4); + } + } + public override int ArgCount => value.IsNull ? 1 : value1.IsNull ? 2 : value2.IsNull ? 3 : value3.IsNull ? 4 : value4.IsNull ? 5 : 6; + } + } + + internal sealed class ExistsCondition : Condition + { + private readonly bool expectedResult; + private readonly RedisValue expectedValue; + private readonly RedisKey key; + private readonly RedisType type; + private readonly RedisCommand cmd; + + internal override Condition MapKeys(Func map) => + new ExistsCondition(map(key), type, expectedValue, expectedResult); + + public ExistsCondition(in RedisKey key, RedisType type, in RedisValue expectedValue, bool expectedResult) + { + if (key.IsNull) throw new ArgumentNullException(nameof(key)); + this.key = key; + this.type = type; + this.expectedValue = expectedValue; + this.expectedResult = expectedResult; + + if (expectedValue.IsNull) + { + cmd = RedisCommand.EXISTS; + } + else + { + cmd = type switch + { + RedisType.Hash => RedisCommand.HEXISTS, + RedisType.Set => RedisCommand.SISMEMBER, + RedisType.SortedSet => RedisCommand.ZSCORE, + _ => throw new ArgumentException($"Type {type} is not recognized", nameof(type)), + }; + } + } + + public override string ToString() => + (expectedValue.IsNull ? key.ToString() : ((string?)key) + " " + type + " > " + expectedValue) + + (expectedResult ? " exists" : " does not exists"); + + internal override void CheckCommands(CommandMap commandMap) => commandMap.AssertAvailable(cmd); + + internal override IEnumerable CreateMessages(int db, IResultBox? resultBox) + { + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + + var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key, expectedValue); + message.SetSource(ConditionProcessor.Default, resultBox); + yield return message; + } + + internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) => serverSelectionStrategy.HashSlot(key); + + internal override bool TryValidate(in RawResult result, out bool value) + { + switch (type) + { + case RedisType.SortedSet: + var parsedValue = result.AsRedisValue(); + value = parsedValue.IsNull != expectedResult; + ConnectionMultiplexer.TraceWithoutContext("exists: " + parsedValue + "; expected: " + expectedResult + "; voting: " + value); + return true; + + default: + bool parsed; + if (ResultProcessor.DemandZeroOrOneProcessor.TryGet(result, out parsed)) + { + value = parsed == expectedResult; + ConnectionMultiplexer.TraceWithoutContext("exists: " + parsed + "; expected: " + expectedResult + "; voting: " + value); + return true; + } + value = false; + return false; + } + } + } + + internal sealed class StartsWithCondition : Condition + { + /* only usable for RedisType.SortedSet, members of SortedSets are always byte-arrays, expectedStartValue therefore is a byte-array + any Encoding and Conversion for the search-sequence has to be executed in calling application + working with byte arrays should prevent any encoding within this class, that could distort the comparison */ + + private readonly bool expectedResult; + private readonly RedisValue prefix; + private readonly RedisKey key; + + internal override Condition MapKeys(Func map) => + new StartsWithCondition(map(key), prefix, expectedResult); + + public StartsWithCondition(in RedisKey key, in RedisValue prefix, bool expectedResult) + { + if (key.IsNull) throw new ArgumentNullException(nameof(key)); + if (prefix.IsNull) throw new ArgumentNullException(nameof(prefix)); + this.key = key; + this.prefix = prefix; + this.expectedResult = expectedResult; + } + + public override string ToString() => + $"{key} {nameof(RedisType.SortedSet)} > {(expectedResult ? " member starting " : " no member starting ")} {prefix} + prefix"; + + internal override void CheckCommands(CommandMap commandMap) => commandMap.AssertAvailable(RedisCommand.ZRANGEBYLEX); + + internal override IEnumerable CreateMessages(int db, IResultBox? resultBox) + { + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + + // prepend '[' to prefix for inclusive search + var startValueWithToken = RedisDatabase.GetLexRange(prefix, Exclude.None, isStart: true, Order.Ascending); + + var message = ConditionProcessor.CreateMessage( + this, + db, + CommandFlags.None, + RedisCommand.ZRANGEBYLEX, + key, + startValueWithToken, + RedisLiterals.PlusSymbol, + RedisLiterals.LIMIT, + 0, + 1); + + message.SetSource(ConditionProcessor.Default, resultBox); + yield return message; + } + + internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) => serverSelectionStrategy.HashSlot(key); + + internal override bool TryValidate(in RawResult result, out bool value) + { + value = result.ItemsCount == 1 && result[0].AsRedisValue().StartsWith(prefix); + + if (!expectedResult) value = !value; + return true; + } + } + + internal sealed class EqualsCondition : Condition + { + internal override Condition MapKeys(Func map) => + new EqualsCondition(map(key), type, memberName, expectedEqual, expectedValue); + + private readonly bool expectedEqual; + private readonly RedisValue memberName, expectedValue; + private readonly RedisKey key; + private readonly RedisType type; + private readonly RedisCommand cmd; + + public EqualsCondition(in RedisKey key, RedisType type, in RedisValue memberName, bool expectedEqual, in RedisValue expectedValue) + { + if (key.IsNull) throw new ArgumentNullException(nameof(key)); + this.key = key; + this.memberName = memberName; + this.expectedEqual = expectedEqual; + this.expectedValue = expectedValue; + this.type = type; + cmd = type switch + { + RedisType.Hash => memberName.IsNull ? RedisCommand.GET : RedisCommand.HGET, + RedisType.SortedSet => RedisCommand.ZSCORE, + _ => throw new ArgumentException($"Unknown type: {type}", nameof(type)), + }; + } + + public override string ToString() => + (memberName.IsNull ? key.ToString() : ((string?)key) + " " + type + " > " + memberName) + + (expectedEqual ? " == " : " != ") + + expectedValue; + + internal override void CheckCommands(CommandMap commandMap) => commandMap.AssertAvailable(cmd); + + internal override IEnumerable CreateMessages(int db, IResultBox? resultBox) + { + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + + var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key, memberName); + message.SetSource(ConditionProcessor.Default, resultBox); + yield return message; + } + + internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) => serverSelectionStrategy.HashSlot(key); + + internal override bool TryValidate(in RawResult result, out bool value) + { + switch (type) + { + case RedisType.SortedSet: + var parsedValue = RedisValue.Null; + if (!result.IsNull && result.TryGetDouble(out var val)) + { + parsedValue = val; + } + + value = (parsedValue == expectedValue) == expectedEqual; + ConnectionMultiplexer.TraceWithoutContext("actual: " + (string?)parsedValue + "; expected: " + (string?)expectedValue + + "; wanted: " + (expectedEqual ? "==" : "!=") + "; voting: " + value); + return true; + + default: + switch (result.Resp2TypeBulkString) + { + case ResultType.BulkString: + case ResultType.SimpleString: + case ResultType.Integer: + var parsed = result.AsRedisValue(); + value = (parsed == expectedValue) == expectedEqual; + ConnectionMultiplexer.TraceWithoutContext("actual: " + (string?)parsed + "; expected: " + (string?)expectedValue + + "; wanted: " + (expectedEqual ? "==" : "!=") + "; voting: " + value); + return true; + } + value = false; + return false; + } + } + } + + internal sealed class ListCondition : Condition + { + internal override Condition MapKeys(Func map) => + new ListCondition(map(key), index, expectedResult, expectedValue); + + private readonly bool expectedResult; + private readonly long index; + private readonly RedisValue? expectedValue; + private readonly RedisKey key; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1242:Do not pass non-read-only struct by read-only reference.", Justification = "Attribute")] + public ListCondition(in RedisKey key, long index, bool expectedResult, in RedisValue? expectedValue) + { + if (key.IsNull) throw new ArgumentNullException(nameof(key)); + this.key = key; + this.index = index; + this.expectedResult = expectedResult; + this.expectedValue = expectedValue; + } + + public override string ToString() => + ((string?)key) + "[" + index.ToString() + "]" + + (expectedValue.HasValue ? (expectedResult ? " == " : " != ") + expectedValue.Value : (expectedResult ? " exists" : " does not exist")); + + internal override void CheckCommands(CommandMap commandMap) => commandMap.AssertAvailable(RedisCommand.LINDEX); + + internal override IEnumerable CreateMessages(int db, IResultBox? resultBox) + { + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + + var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, RedisCommand.LINDEX, key, index); + message.SetSource(ConditionProcessor.Default, resultBox); + yield return message; + } + + internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) => serverSelectionStrategy.HashSlot(key); + + internal override bool TryValidate(in RawResult result, out bool value) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.BulkString: + case ResultType.SimpleString: + case ResultType.Integer: + var parsed = result.AsRedisValue(); + if (expectedValue.HasValue) + { + value = (parsed == expectedValue.Value) == expectedResult; + ConnectionMultiplexer.TraceWithoutContext("actual: " + (string?)parsed + "; expected: " + (string?)expectedValue.Value + + "; wanted: " + (expectedResult ? "==" : "!=") + "; voting: " + value); + } + else + { + value = parsed.IsNull != expectedResult; + ConnectionMultiplexer.TraceWithoutContext("exists: " + parsed + "; expected: " + expectedResult + "; voting: " + value); + } + return true; + } + value = false; + return false; + } + } + + internal sealed class LengthCondition : Condition + { + internal override Condition MapKeys(Func map) => + new LengthCondition(map(key), type, compareToResult, expectedLength); + + private readonly int compareToResult; + private readonly long expectedLength; + private readonly RedisKey key; + private readonly RedisType type; + private readonly RedisCommand cmd; + + public LengthCondition(in RedisKey key, RedisType type, int compareToResult, long expectedLength) + { + if (key.IsNull) throw new ArgumentNullException(nameof(key)); + this.key = key; + this.compareToResult = compareToResult; + this.expectedLength = expectedLength; + this.type = type; + cmd = type switch + { + RedisType.Hash => RedisCommand.HLEN, + RedisType.Set => RedisCommand.SCARD, + RedisType.List => RedisCommand.LLEN, + RedisType.SortedSet => RedisCommand.ZCARD, + RedisType.Stream => RedisCommand.XLEN, + RedisType.String => RedisCommand.STRLEN, + _ => throw new ArgumentException($"Type {type} isn't recognized", nameof(type)), + }; + } + + public override string ToString() => ((string?)key) + " " + type + " length" + GetComparisonString() + expectedLength; + + private string GetComparisonString() => compareToResult == 0 ? " == " : (compareToResult < 0 ? " > " : " < "); + + internal override void CheckCommands(CommandMap commandMap) => commandMap.AssertAvailable(cmd); + + internal override IEnumerable CreateMessages(int db, IResultBox? resultBox) + { + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + + var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key); + message.SetSource(ConditionProcessor.Default, resultBox); + yield return message; + } + + internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) => serverSelectionStrategy.HashSlot(key); + + internal override bool TryValidate(in RawResult result, out bool value) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.BulkString: + case ResultType.SimpleString: + case ResultType.Integer: + var parsed = result.AsRedisValue(); + value = parsed.IsInteger && (expectedLength.CompareTo((long)parsed) == compareToResult); + ConnectionMultiplexer.TraceWithoutContext("actual: " + (string?)parsed + "; expected: " + expectedLength + + "; wanted: " + GetComparisonString() + "; voting: " + value); + return true; + } + value = false; + return false; + } + } + + internal sealed class SortedSetRangeLengthCondition : Condition + { + internal override Condition MapKeys(Func map) => + new SortedSetRangeLengthCondition(map(key), min, max, compareToResult, expectedLength); + + private readonly RedisValue min; + private readonly RedisValue max; + private readonly int compareToResult; + private readonly long expectedLength; + private readonly RedisKey key; + + public SortedSetRangeLengthCondition(in RedisKey key, RedisValue min, RedisValue max, int compareToResult, long expectedLength) + { + if (key.IsNull) throw new ArgumentNullException(nameof(key)); + this.key = key; + this.min = min; + this.max = max; + this.compareToResult = compareToResult; + this.expectedLength = expectedLength; + } + + public override string ToString() => + ((string?)key) + " " + RedisType.SortedSet + " range[" + min + ", " + max + "] length" + GetComparisonString() + expectedLength; + + private string GetComparisonString() => compareToResult == 0 ? " == " : (compareToResult < 0 ? " > " : " < "); + + internal override void CheckCommands(CommandMap commandMap) => commandMap.AssertAvailable(RedisCommand.ZCOUNT); + + internal override IEnumerable CreateMessages(int db, IResultBox? resultBox) + { + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + + var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, RedisCommand.ZCOUNT, key, min, max); + message.SetSource(ConditionProcessor.Default, resultBox); + yield return message; + } + + internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) => serverSelectionStrategy.HashSlot(key); + + internal override bool TryValidate(in RawResult result, out bool value) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.BulkString: + case ResultType.SimpleString: + case ResultType.Integer: + var parsed = result.AsRedisValue(); + value = parsed.IsInteger && (expectedLength.CompareTo((long)parsed) == compareToResult); + ConnectionMultiplexer.TraceWithoutContext("actual: " + (string?)parsed + "; expected: " + expectedLength + + "; wanted: " + GetComparisonString() + "; voting: " + value); + return true; + } + value = false; + return false; + } + } + + internal sealed class SortedSetScoreCondition : Condition + { + internal override Condition MapKeys(Func map) => + new SortedSetScoreCondition(map(key), sortedSetScore, expectedEqual, expectedValue); + + private readonly bool expectedEqual; + private readonly RedisValue sortedSetScore, expectedValue; + private readonly RedisKey key; + + public SortedSetScoreCondition(in RedisKey key, in RedisValue sortedSetScore, bool expectedEqual, in RedisValue expectedValue) + { + if (key.IsNull) + { + throw new ArgumentNullException(nameof(key)); + } + + this.key = key; + this.sortedSetScore = sortedSetScore; + this.expectedEqual = expectedEqual; + this.expectedValue = expectedValue; + } + + public override string ToString() => + key.ToString() + (expectedEqual ? " contains " : " not contains ") + expectedValue + " members with score: " + sortedSetScore; + + internal override void CheckCommands(CommandMap commandMap) => commandMap.AssertAvailable(RedisCommand.ZCOUNT); + + internal override IEnumerable CreateMessages(int db, IResultBox? resultBox) + { + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + + var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, RedisCommand.ZCOUNT, key, sortedSetScore, sortedSetScore); + message.SetSource(ConditionProcessor.Default, resultBox); + + yield return message; + } + + internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) => serverSelectionStrategy.HashSlot(key); + + internal override bool TryValidate(in RawResult result, out bool value) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + var parsedValue = result.AsRedisValue(); + value = (parsedValue == expectedValue) == expectedEqual; + ConnectionMultiplexer.TraceWithoutContext("actual: " + (string?)parsedValue + "; expected: " + (string?)expectedValue + "; wanted: " + (expectedEqual ? "==" : "!=") + "; voting: " + value); + return true; + } + + value = false; + return false; + } + } + } + + /// + /// Indicates the status of a condition as part of a transaction. + /// + public sealed class ConditionResult + { + internal readonly Condition Condition; + + private IResultBox? resultBox; + + private volatile bool wasSatisfied; + + internal ConditionResult(Condition condition) + { + Condition = condition; + resultBox = SimpleResultBox.Create(); + } + + /// + /// Indicates whether the condition was satisfied. + /// + public bool WasSatisfied => wasSatisfied; + + internal IEnumerable CreateMessages(int db) => Condition.CreateMessages(db, resultBox); + + internal IResultBox? GetBox() => resultBox; + internal bool UnwrapBox() + { + if (resultBox != null) + { + bool val = resultBox.GetResult(out var ex); + resultBox = null; + wasSatisfied = ex == null && val; + } + return wasSatisfied; + } + } +} diff --git a/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs b/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs new file mode 100644 index 000000000..fb01f0704 --- /dev/null +++ b/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs @@ -0,0 +1,101 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using StackExchange.Redis.Maintenance; + +namespace StackExchange.Redis.Configuration +{ + /// + /// Options provider for Azure environments. + /// + public class AzureOptionsProvider : DefaultOptionsProvider + { + /// + /// Allow connecting after startup, in the cases where remote cache isn't ready or is overloaded. + /// + public override bool AbortOnConnectFail => false; + + /// + /// The minimum version of Redis in Azure is 6, so use the widest set of available commands when connecting. + /// + public override Version DefaultVersion => RedisFeatures.v6_0_0; + + /// + /// Lists of domains known to be Azure Redis, so we can light up some helpful functionality + /// for minimizing downtime during maintenance events and such. + /// + private static readonly string[] azureRedisDomains = new[] + { + ".redis.cache.windows.net", + ".redis.cache.chinacloudapi.cn", + ".redis.cache.usgovcloudapi.net", + ".redisenterprise.cache.azure.net", + }; + + private static readonly string[] azureManagedRedisDomains = new[] + { + ".redis.azure.net", + ".redis.chinacloudapi.cn", + ".redis.usgovcloudapi.net", + }; + + /// + public override bool IsMatch(EndPoint endpoint) + { + if (endpoint is DnsEndPoint dnsEp) + { + if (IsHostInDomains(dnsEp.Host, azureRedisDomains) || IsHostInDomains(dnsEp.Host, azureManagedRedisDomains)) + { + return true; + } + } + + return false; + } + + private bool IsHostInDomains(string hostName, string[] domains) + { + foreach (var domain in domains) + { + if (hostName.EndsWith(domain, StringComparison.InvariantCultureIgnoreCase)) + { + return true; + } + } + + return false; + } + + /// + public override Task AfterConnectAsync(ConnectionMultiplexer muxer, Action log) + => AzureMaintenanceEvent.AddListenerAsync(muxer, log); + + /// + public override bool GetDefaultSsl(EndPointCollection endPoints) + { + foreach (var ep in endPoints) + { + switch (ep) + { + case DnsEndPoint dns: + if (dns.Port == 6380) + { + return true; + } + if (dns.Port == 10000 && IsHostInDomains(dns.Host, azureManagedRedisDomains)) + { + return true; // SSL is enabled by default on AMR caches + } + break; + case IPEndPoint ip: + if (ip.Port == 6380) + { + return true; + } + break; + } + } + return false; + } + } +} diff --git a/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs b/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs new file mode 100644 index 000000000..703adbcac --- /dev/null +++ b/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs @@ -0,0 +1,341 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace StackExchange.Redis.Configuration +{ + /// + /// A defaults providers for . + /// This providers defaults not explicitly specified and is present to be inherited by environments that want to provide + /// better defaults for their use case, e.g. in a single wrapper library used many places. + /// + /// + /// Why not just have a default instance? Good question! + /// Since we null coalesce down to the defaults, there's an inherent pit-of-failure with that approach of . + /// If you forget anything or if someone creates a provider nulling these out...kaboom. + /// + public class DefaultOptionsProvider + { + /// + /// The known providers to match against (built into the library) - the default set. + /// If none of these match, is used. + /// + private static readonly List BuiltInProviders = new() + { + new AzureOptionsProvider(), + }; + + /// + /// The current list of providers to match (potentially modified from defaults via . + /// + private static LinkedList KnownProviders { get; set; } = new(BuiltInProviders); + + /// + /// Adds a provider to match endpoints against. The last provider added has the highest priority. + /// If you want your provider to match everything, implement as return true;. + /// + /// The provider to add. + public static void AddProvider(DefaultOptionsProvider provider) + { + var newList = new LinkedList(KnownProviders); + newList.AddFirst(provider); + KnownProviders = newList; + } + + /// + /// Whether this options provider matches a given endpoint, for automatically selecting a provider based on what's being connected to. + /// + public virtual bool IsMatch(EndPoint endpoint) => false; + + /// + /// Gets a provider for the given endpoints, falling back to if nothing more specific is found. + /// + public static DefaultOptionsProvider GetProvider(EndPointCollection endpoints) + { + foreach (var provider in KnownProviders) + { + foreach (var endpoint in endpoints) + { + if (provider.IsMatch(endpoint)) + { + return provider; + } + } + } + + return new DefaultOptionsProvider(); // no memoize; allow mutability concerns (also impacts subclasses, but: pragmatism) + } + + /// + /// Gets a provider for a given endpoints, falling back to if nothing more specific is found. + /// + public static DefaultOptionsProvider GetProvider(EndPoint endpoint) + { + foreach (var provider in KnownProviders) + { + if (provider.IsMatch(endpoint)) + { + return provider; + } + } + return new DefaultOptionsProvider(); // no memoize; allow mutability concerns (also impacts subclasses, but: pragmatism) + } + + /// + /// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException. + /// + public virtual bool AbortOnConnectFail => true; + + /// + /// Indicates whether admin operations should be allowed. + /// + public virtual bool AllowAdmin => false; + + /// + /// The backlog policy to be used for commands when a connection is unhealthy. + /// + public virtual BacklogPolicy BacklogPolicy => BacklogPolicy.Default; + + /// + /// A Boolean value that specifies whether the certificate revocation list is checked during authentication. + /// + public virtual bool CheckCertificateRevocation => true; + + /// + /// A Boolean value that specifies whether to use per-command validation of strict protocol validity. + /// This sends an additional command after EVERY command which incurs measurable overhead. + /// + /// + /// The regular RESP protocol does not include correlation identifiers between requests and responses; in exceptional + /// scenarios, protocol desynchronization can occur, which may not be noticed immediately; this option adds additional data + /// to ensure that this cannot occur, at the cost of some (small) additional bandwidth usage. + /// + public virtual bool HighIntegrity => false; + + /// + /// The number of times to repeat the initial connect cycle if no servers respond promptly. + /// + public virtual int ConnectRetry => 3; + + /// + /// Specifies the time that should be allowed for connection. + /// Falls back to Max(5000, SyncTimeout) if null. + /// + public virtual TimeSpan? ConnectTimeout => null; + + /// + /// The command-map associated with this configuration. + /// + public virtual CommandMap? CommandMap => null; + + /// + /// Channel to use for broadcasting and listening for configuration change notification. + /// + public virtual string ConfigurationChannel => "__Booksleeve_MasterChanged"; + + /// + /// The server version to assume. + /// + public virtual Version DefaultVersion => RedisFeatures.v3_0_0; + + /// + /// Controls how often the connection heartbeats. A heartbeat includes: + /// - Evaluating if any messages have timed out. + /// - Evaluating connection status (checking for failures). + /// - Sending a server message to keep the connection alive if needed. + /// + /// Be aware setting this very low incurs additional overhead of evaluating the above more often. + public virtual TimeSpan HeartbeatInterval => TimeSpan.FromSeconds(1); + + /// + /// Whether to enable ECHO checks on every heartbeat to ensure network stream consistency. + /// This is a rare measure to react to any potential network traffic drops ASAP, terminating the connection. + /// + public virtual bool HeartbeatConsistencyChecks => false; + + /// + /// Whether exceptions include identifiable details (key names, additional .Data annotations). + /// + public virtual bool IncludeDetailInExceptions => true; + + /// + /// Whether exceptions include performance counter details. + /// + /// + /// CPU usage, etc - note that this can be problematic on some platforms. + /// + public virtual bool IncludePerformanceCountersInExceptions => false; + + /// + /// Specifies the time interval at which connections should be pinged to ensure validity. + /// + public virtual TimeSpan KeepAliveInterval => TimeSpan.FromSeconds(60); + + /// + /// The to get loggers for connection events. + /// Note: changes here only affect s created after. + /// + public virtual ILoggerFactory? LoggerFactory => null; + + /// + /// Type of proxy to use (if any); for example . + /// + public virtual Proxy Proxy => Proxy.None; + + /// + /// The retry policy to be used for connection reconnects. + /// + public virtual IReconnectRetryPolicy? ReconnectRetryPolicy => null; + + /// + /// Indicates whether endpoints should be resolved via DNS before connecting. + /// If enabled the ConnectionMultiplexer will not re-resolve DNS when attempting to re-connect after a connection failure. + /// + public virtual bool ResolveDns => false; + + /// + /// Specifies the time that the system should allow for synchronous operations. + /// + public virtual TimeSpan SyncTimeout => TimeSpan.FromSeconds(5); + + /// + /// Tie-breaker used to choose between primaries (must match the endpoint exactly). + /// + public virtual string TieBreaker => "__Booksleeve_TieBreak"; + + /// + /// Check configuration every n interval. + /// + public virtual TimeSpan ConfigCheckInterval => TimeSpan.FromMinutes(1); + + /// + /// The username to use to authenticate with the server. + /// + public virtual string? User => null; + + /// + /// The password to use to authenticate with the server. + /// + public virtual string? Password => null; + + // We memoize this to reduce cost on re-access + private string? defaultClientName; + + /// + /// The default client name for a connection, with the library version appended. + /// + public string ClientName => defaultClientName ??= GetDefaultClientName(); + + /// + /// Gets the default client name for a connection. + /// + protected virtual string GetDefaultClientName() => + (TryGetAzureRoleInstanceIdNoThrow() + ?? ComputerName + ?? "StackExchange.Redis") + "(SE.Redis-v" + LibraryVersion + ")"; + + /// + /// Gets the library name to use for CLIENT SETINFO lib-name calls to Redis during handshake. + /// Defaults to "SE.Redis". + /// + public virtual string LibraryName => "SE.Redis"; + + /// + /// String version of the StackExchange.Redis library, for use in any options. + /// + protected static string LibraryVersion => Utils.GetLibVersion(); + + /// + /// Name of the machine we're running on, for use in any options. + /// + protected static string ComputerName => Environment.MachineName ?? Environment.GetEnvironmentVariable("ComputerName") ?? "Unknown"; + + /// + /// Whether to identify the client by library name/version when possible. + /// + public virtual bool SetClientLibrary => true; + + /// + /// Tries to get the RoleInstance Id if Microsoft.WindowsAzure.ServiceRuntime is loaded. + /// In case of any failure, swallows the exception and returns null. + /// + /// + /// Azure, in the default provider? Yes, to maintain existing compatibility/convenience. + /// Source != destination here. + /// + internal static string? TryGetAzureRoleInstanceIdNoThrow() + { + string? roleInstanceId; + try + { + var roleEnvironmentType = Type.GetType("Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment, Microsoft.WindowsAzure.ServiceRuntime", throwOnError: false); + + // https://msdn.microsoft.com/en-us/library/microsoft.windowsazure.serviceruntime.roleenvironment.isavailable.aspx + if (roleEnvironmentType?.GetProperty("IsAvailable") is not PropertyInfo isAvailableProp + || isAvailableProp.GetValue(null, null) is not bool isAvailableVal + || !isAvailableVal) + { + return null; + } + + var currentRoleInstanceProp = roleEnvironmentType.GetProperty("CurrentRoleInstance"); + var currentRoleInstanceId = currentRoleInstanceProp?.GetValue(null, null); + + var roleInstanceType = Type.GetType("Microsoft.WindowsAzure.ServiceRuntime.RoleInstance, Microsoft.WindowsAzure.ServiceRuntime", throwOnError: false); + roleInstanceId = roleInstanceType?.GetProperty("Id")?.GetValue(currentRoleInstanceId, null)?.ToString(); + + if (roleInstanceId.IsNullOrEmpty()) + { + roleInstanceId = null; + } + } + catch (Exception) + { + // Silently ignores the exception + roleInstanceId = null; + } + return roleInstanceId; + } + + /// + /// The action to perform, if any, immediately after an initial connection completes. + /// + /// The multiplexer that just connected. + /// The logger for the connection, to emit to the connection output log. + public virtual Task AfterConnectAsync(ConnectionMultiplexer multiplexer, Action log) => Task.CompletedTask; + + /// + /// Gets the default SSL "enabled or not" based on a set of endpoints. + /// Note: this setting then applies for *all* endpoints. + /// + /// The configured endpoints to determine SSL usage from (e.g. from the port). + /// Whether to enable SSL for connections (unless explicitly overridden in a direct set). + public virtual bool GetDefaultSsl(EndPointCollection endPoints) => false; + + /// + /// Gets the SSL Host to check for when connecting to endpoints (customizable in case of internal certificate shenanigans. + /// + /// The configured endpoints to determine SSL host from (e.g. from the port). + /// The common host, if any, detected from the endpoint collection. + public virtual string? GetSslHostFromEndpoints(EndPointCollection endPoints) + { + string? commonHost = null; + foreach (var endpoint in endPoints) + { + if (endpoint is DnsEndPoint dnsEndpoint) + { + commonHost ??= dnsEndpoint.Host; + // Mismatch detected, no assumptions. + if (dnsEndpoint.Host != commonHost) + { + return null; + } + } + } + return commonHost; + } + } +} diff --git a/src/StackExchange.Redis/Configuration/LoggingTunnel.cs b/src/StackExchange.Redis/Configuration/LoggingTunnel.cs new file mode 100644 index 000000000..ccfa4ee63 --- /dev/null +++ b/src/StackExchange.Redis/Configuration/LoggingTunnel.cs @@ -0,0 +1,631 @@ +using System; +using System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.IO.Pipelines; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Pipelines.Sockets.Unofficial; +using Pipelines.Sockets.Unofficial.Arenas; +using static StackExchange.Redis.PhysicalConnection; + +namespace StackExchange.Redis.Configuration; + +/// +/// Captures redis traffic; intended for debug use. +/// +[Obsolete("This API is experimental, has security and performance implications, and may change without notice", false)] +[SuppressMessage("ApiDesign", "RS0016:Add public types and members to the declared API", Justification = "Experimental API")] +public abstract class LoggingTunnel : Tunnel +{ + private readonly ConfigurationOptions _options; + private readonly bool _ssl; + private readonly Tunnel? _tail; + + /// + /// Replay the RESP messages for a pair of streams, invoking a callback per operation. + /// + public static async Task ReplayAsync(Stream @out, Stream @in, Action pair) + { + using Arena arena = new(); + var outPipe = StreamConnection.GetReader(@out); + var inPipe = StreamConnection.GetReader(@in); + + long count = 0; + while (true) + { + var sent = await ReadOneAsync(outPipe, arena, isInbound: false).ForAwait(); + ContextualRedisResult received; + try + { + do + { + received = await ReadOneAsync(inPipe, arena, isInbound: true).ForAwait(); + if (received.IsOutOfBand && received.Result is not null) + { + // spoof an empty request for OOB messages + pair(RedisResult.NullSingle, received.Result); + } + } + while (received.IsOutOfBand); + } + catch (Exception ex) + { + // if we got an exception following a command, spoof that as a pair, + // so we see the message that had a corrupted reply + if (sent.Result is not null) + { + pair(sent.Result, RedisResult.Create(ex.Message, ResultType.Error)); + } + throw; // still surface the original exception + } + + if (sent.Result is null || received.Result is null) break; // no more paired messages + + pair(sent.Result, received.Result); + count++; + } + return count; + } + + /// + /// Replay the RESP messages all the streams in a folder, invoking a callback per operation. + /// + /// The directory of captured files to replay. + /// Operation to perform per replayed message pair. + public static async Task ReplayAsync(string path, Action pair) + { + long total = 0; + foreach (var outPath in Directory.EnumerateFiles(path, "*.out")) + { + var inPath = Path.ChangeExtension(outPath, "in"); + if (!File.Exists(outPath)) continue; + + using var outFile = File.OpenRead(outPath); + using var inFile = File.OpenRead(inPath); + total += await ReplayAsync(outFile, inFile, pair).ForAwait(); + } + return total; + } + + private static async ValueTask ReadOneAsync(PipeReader input, Arena arena, bool isInbound) + { + while (true) + { + var readResult = await input.ReadAsync().ForAwait(); + var buffer = readResult.Buffer; + int handled = 0; + var result = buffer.IsEmpty ? default : ProcessBuffer(arena, ref buffer, isInbound); + input.AdvanceTo(buffer.Start, buffer.End); + + if (result.Result is not null) return result; + + if (handled == 0 && readResult.IsCompleted) + { + break; // no more data, or trailing incomplete messages + } + } + return default; + } + + /// + /// Validate a RESP stream and return the number of top-level RESP fragments. + /// + /// The path of a single file to validate, or a directory of captured files to validate. + public static async Task ValidateAsync(string path) + { + if (File.Exists(path)) + { + using var singleFile = File.OpenRead(path); + return await ValidateAsync(singleFile).ForAwait(); + } + else if (Directory.Exists(path)) + { + long total = 0; + foreach (var file in Directory.EnumerateFiles(path)) + { + try + { + using var folderFile = File.OpenRead(file); + total += await ValidateAsync(folderFile).ForAwait(); + } + catch (Exception ex) + { + throw new InvalidOperationException(ex.Message + " in " + file, ex); + } + } + return total; + } + else + { + throw new FileNotFoundException(path); + } + } + + /// + /// Validate a RESP stream and return the number of top-level RESP fragments. + /// + public static async Task ValidateAsync(Stream stream) + { + using var arena = new Arena(); + var input = StreamConnection.GetReader(stream); + long total = 0, position = 0; + while (true) + { + var readResult = await input.ReadAsync().ForAwait(); + var buffer = readResult.Buffer; + int handled = 0; + if (!buffer.IsEmpty) + { + try + { + ProcessBuffer(arena, ref buffer, ref position, ref handled); // updates buffer.Start + } + catch (Exception ex) + { + throw new InvalidOperationException($"Invalid fragment starting at {position} (fragment {total + handled})", ex); + } + total += handled; + } + + input.AdvanceTo(buffer.Start, buffer.End); + + if (handled == 0 && readResult.IsCompleted) + { + break; // no more data, or trailing incomplete messages + } + } + return total; + } + private static void ProcessBuffer(Arena arena, ref ReadOnlySequence buffer, ref long position, ref int messageCount) + { + while (!buffer.IsEmpty) + { + var reader = new BufferReader(buffer); + try + { + var result = TryParseResult(true, arena, in buffer, ref reader, true, null); + if (result.HasValue) + { + buffer = reader.SliceFromCurrent(); + position += reader.TotalConsumed; + messageCount++; + } + else + { + break; // remaining buffer isn't enough; give up + } + } + finally + { + arena.Reset(); + } + } + } + + private readonly struct ContextualRedisResult + { + public readonly RedisResult? Result; + public readonly bool IsOutOfBand; + public ContextualRedisResult(RedisResult? result, bool isOutOfBand) + { + Result = result; + IsOutOfBand = isOutOfBand; + } + } + + private static ContextualRedisResult ProcessBuffer(Arena arena, ref ReadOnlySequence buffer, bool isInbound) + { + if (!buffer.IsEmpty) + { + var reader = new BufferReader(buffer); + try + { + var result = TryParseResult(true, arena, in buffer, ref reader, true, null); + bool isOutOfBand = result.Resp3Type == ResultType.Push + || (isInbound && result.Resp2TypeArray == ResultType.Array && IsArrayOutOfBand(result)); + if (result.HasValue) + { + buffer = reader.SliceFromCurrent(); + if (!RedisResult.TryCreate(null, result, out var parsed)) + { + throw new InvalidOperationException("Unable to parse raw result to RedisResult"); + } + return new(parsed, isOutOfBand); + } + } + finally + { + arena.Reset(); + } + } + return default; + + static bool IsArrayOutOfBand(in RawResult result) + { + var items = result.GetItems(); + return (items.Length >= 3 && (items[0].IsEqual(message) || items[0].IsEqual(smessage))) + || (items.Length >= 4 && items[0].IsEqual(pmessage)); + } + } + private static readonly CommandBytes message = "message", pmessage = "pmessage", smessage = "smessage"; + + /// + /// Create a new instance of a . + /// + protected LoggingTunnel(ConfigurationOptions? options = null, Tunnel? tail = null) + { + options ??= new(); + _options = options; + _ssl = options.Ssl; + _tail = tail; + options.Ssl = false; // disable here, since we want to log *decrypted* + } + + /// + /// Configures the provided options to perform file-based logging to a directory; + /// files will be sequential per stream starting from zero, and will blindly overwrite existing files. + /// + public static void LogToDirectory(ConfigurationOptions options, string path) + { + var tunnel = new DirectoryLoggingTunnel(path, options, options.Tunnel); + options.Tunnel = tunnel; + } + + private sealed class DirectoryLoggingTunnel : LoggingTunnel + { + private readonly string path; + private int _nextIndex = -1; + + internal DirectoryLoggingTunnel(string path, ConfigurationOptions? options = null, Tunnel? tail = null) + : base(options, tail) + { + this.path = path; + if (!Directory.Exists(path)) throw new InvalidOperationException("Directory does not exist: " + path); + } + + protected override Stream Log(Stream stream, EndPoint endpoint, ConnectionType connectionType) + { + int index = Interlocked.Increment(ref _nextIndex); + var name = $"{Format.ToString(endpoint)} {connectionType} {index}.tmp"; + foreach (var c in InvalidChars) + { + name = name.Replace(c, ' '); + } + name = Path.Combine(path, name); + var reads = File.Create(Path.ChangeExtension(name, ".in")); + var writes = File.Create(Path.ChangeExtension(name, ".out")); + return new LoggingDuplexStream(stream, reads, writes); + } + + private static readonly char[] InvalidChars = Path.GetInvalidFileNameChars(); + } + + /// + public override async ValueTask BeforeAuthenticateAsync(EndPoint endpoint, ConnectionType connectionType, Socket? socket, CancellationToken cancellationToken) + { + Stream? stream = null; + if (_tail is not null) + { + stream = await _tail.BeforeAuthenticateAsync(endpoint, connectionType, socket, cancellationToken).ForAwait(); + } + stream ??= new NetworkStream(socket ?? throw new InvalidOperationException("No stream or socket available")); + if (_ssl) + { + stream = await TlsHandshakeAsync(stream, endpoint).ForAwait(); + } + return Log(stream, endpoint, connectionType); + } + + /// + /// Perform logging on the provided stream. + /// + protected abstract Stream Log(Stream stream, EndPoint endpoint, ConnectionType connectionType); + + /// + public override ValueTask BeforeSocketConnectAsync(EndPoint endPoint, ConnectionType connectionType, Socket? socket, CancellationToken cancellationToken) + { + return _tail is null ? base.BeforeSocketConnectAsync(endPoint, connectionType, socket, cancellationToken) + : _tail.BeforeSocketConnectAsync(endPoint, connectionType, socket, cancellationToken); + } + + /// + public override ValueTask GetSocketConnectEndpointAsync(EndPoint endpoint, CancellationToken cancellationToken) + { + return _tail is null ? base.GetSocketConnectEndpointAsync(endpoint, cancellationToken) + : _tail.GetSocketConnectEndpointAsync(endpoint, cancellationToken); + } + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - netfx back-compat mode + private async Task TlsHandshakeAsync(Stream stream, EndPoint endpoint) +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + { + // mirrors TLS handshake from PhysicalConnection, but wouldn't help to share code here + var host = _options.SslHost; + if (host.IsNullOrWhiteSpace()) + { + host = Format.ToStringHostOnly(endpoint); + } + + var ssl = new SslStream( + innerStream: stream, + leaveInnerStreamOpen: false, + userCertificateValidationCallback: _options.CertificateValidationCallback ?? PhysicalConnection.GetAmbientIssuerCertificateCallback(), + userCertificateSelectionCallback: _options.CertificateSelectionCallback ?? PhysicalConnection.GetAmbientClientCertificateCallback(), + encryptionPolicy: EncryptionPolicy.RequireEncryption); + +#if NETCOREAPP3_1_OR_GREATER + var configOptions = _options.SslClientAuthenticationOptions?.Invoke(host); + if (configOptions is not null) + { + await ssl.AuthenticateAsClientAsync(configOptions).ForAwait(); + } + else + { + await ssl.AuthenticateAsClientAsync(host, _options.SslProtocols, _options.CheckCertificateRevocation).ForAwait(); + } +#else + await ssl.AuthenticateAsClientAsync(host, _options.SslProtocols, _options.CheckCertificateRevocation).ForAwait(); +#endif + return ssl; + } + + /// + /// Get a typical text representation of a redis command. + /// + public static string DefaultFormatCommand(RedisResult value) + { + try + { + if (value.IsNull) return "(null)"; + if (value.Type == ResultType.Array) + { + var sb = new StringBuilder(); + for (int i = 0; i < value.Length; i++) + { + var item = value[i]; + if (i != 0) sb.Append(' '); + if (IsSimple(item)) + { + sb.Append(item.AsString()); + } + else + { + sb.Append("..."); + break; + } + } + return sb.ToString(); + } + } + catch { } + return value.Type.ToString(); + + static bool IsSimple(RedisResult value) + { + try + { + switch (value.Resp2Type) + { + case ResultType.Array: return false; + case ResultType.Error: return true; + default: + var blob = value.AsByteArray(); // note non-alloc in the remaining cases + if (blob is null) return true; + if (blob.Length >= 50) return false; + for (int i = 0; i < blob.Length; i++) + { + char c = (char)blob[i]; + if (c < ' ' || c > '~') return false; + } + return true; + } + } + catch + { + return false; + } + } + } + + /// + /// Get a typical text representation of a redis response. + /// + public static string DefaultFormatResponse(RedisResult value) + { + try + { + if (value.IsNull) return "(null)"; + switch (value.Type.ToResp2()) + { + case ResultType.Integer: + case ResultType.BulkString: + case ResultType.SimpleString: + return value.AsString()!; + case ResultType.Error: + return "-" + value.ToString(); + case ResultType.Array: + return $"[{value.Length}]"; + } + } + catch (Exception ex) + { + Debug.Write(ex.Message); + } + return value.Type.ToString(); + } + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + protected sealed class LoggingDuplexStream : Stream + { + private readonly Stream _inner, _reads, _writes; + + internal LoggingDuplexStream(Stream inner, Stream reads, Stream writes) + { + _inner = inner; + _reads = reads; + _writes = writes; + } + + public override bool CanRead => _inner.CanRead; + public override bool CanWrite => _inner.CanWrite; + + public override bool CanSeek => false; // duplex + public override bool CanTimeout => _inner.CanTimeout; + public override int ReadTimeout { get => _inner.ReadTimeout; set => _inner.ReadTimeout = value; } + public override int WriteTimeout { get => _inner.WriteTimeout; set => _inner.WriteTimeout = value; } + public override long Length => throw new NotSupportedException(); // duplex + public override long Position + { + get => throw new NotSupportedException(); // duplex + set => throw new NotSupportedException(); // duplex + } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); // duplex + public override void SetLength(long value) => throw new NotSupportedException(); // duplex + + // we don't use these APIs + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => throw new NotSupportedException(); + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => throw new NotSupportedException(); + public override int EndRead(IAsyncResult asyncResult) => throw new NotSupportedException(); + public override void EndWrite(IAsyncResult asyncResult) => throw new NotSupportedException(); + + public override void Flush() + { + // note we don't flush _reads, as that could be cross-threaded + // (flush is a write operation, not a read one) + _writes.Flush(); + _inner.Flush(); + } + + public override async Task FlushAsync(CancellationToken cancellationToken) + { + var writesTask = _writes.FlushAsync().ForAwait(); + await _inner.FlushAsync().ForAwait(); + await writesTask; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _inner.Dispose(); + try { _reads.Flush(); } catch { } + _reads.Dispose(); + try { _writes.Flush(); } catch { } + _writes.Dispose(); + } + base.Dispose(disposing); + } + + public override void Close() + { + _inner.Close(); + try { _reads.Flush(); } catch { } + _reads.Close(); + try { _writes.Flush(); } catch { } + _writes.Close(); + base.Close(); + } + +#if NETCOREAPP3_0_OR_GREATER + public override async ValueTask DisposeAsync() + { + await _inner.DisposeAsync().ForAwait(); + try { await _reads.FlushAsync().ForAwait(); } catch { } + await _reads.DisposeAsync().ForAwait(); + try { await _writes.FlushAsync().ForAwait(); } catch { } + await _writes.DisposeAsync().ForAwait(); + await base.DisposeAsync().ForAwait(); + } +#endif + + public override int ReadByte() + { + var val = _inner.ReadByte(); + if (val >= 0) + { + _reads.WriteByte((byte)val); + _reads.Flush(); + } + return val; + } + public override int Read(byte[] buffer, int offset, int count) + { + var len = _inner.Read(buffer, offset, count); + if (len > 0) + { + _reads.Write(buffer, offset, len); + _reads.Flush(); + } + return len; + } + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var len = await _inner.ReadAsync(buffer, offset, count, cancellationToken).ForAwait(); + if (len > 0) + { + await _reads.WriteAsync(buffer, offset, len, cancellationToken).ForAwait(); + await _reads.FlushAsync(cancellationToken).ForAwait(); + } + return len; + } +#if NETCOREAPP3_0_OR_GREATER + public override int Read(Span buffer) + { + var len = _inner.Read(buffer); + if (len > 0) + { + _reads.Write(buffer.Slice(0, len)); + _reads.Flush(); + } + return len; + } + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) + { + var len = await _inner.ReadAsync(buffer, cancellationToken).ForAwait(); + if (len > 0) + { + await _reads.WriteAsync(buffer.Slice(0, len), cancellationToken).ForAwait(); + await _reads.FlushAsync(cancellationToken).ForAwait(); + } + return len; + } +#endif + + public override void WriteByte(byte value) + { + _writes.WriteByte(value); + _inner.WriteByte(value); + } + public override void Write(byte[] buffer, int offset, int count) + { + _writes.Write(buffer, offset, count); + _inner.Write(buffer, offset, count); + } + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var writesTask = _writes.WriteAsync(buffer, offset, count, cancellationToken).ForAwait(); + await _inner.WriteAsync(buffer, offset, count, cancellationToken).ForAwait(); + await writesTask; + } +#if NETCOREAPP3_0_OR_GREATER + public override void Write(ReadOnlySpan buffer) + { + _writes.Write(buffer); + _inner.Write(buffer); + } + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + var writesTask = _writes.WriteAsync(buffer, cancellationToken).ForAwait(); + await _inner.WriteAsync(buffer, cancellationToken).ForAwait(); + await writesTask; + } +#endif + } +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member +} diff --git a/src/StackExchange.Redis/Configuration/Tunnel.cs b/src/StackExchange.Redis/Configuration/Tunnel.cs new file mode 100644 index 000000000..beebff2dc --- /dev/null +++ b/src/StackExchange.Redis/Configuration/Tunnel.cs @@ -0,0 +1,116 @@ +using System; +using System.Buffers; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Pipelines.Sockets.Unofficial; + +namespace StackExchange.Redis.Configuration +{ + /// + /// Allows interception of the transport used to communicate with Redis. + /// + public abstract class Tunnel + { + /// + /// Gets the underlying socket endpoint to use when connecting to a logical endpoint. + /// + /// null should be returned if a socket is not required for this endpoint. + public virtual ValueTask GetSocketConnectEndpointAsync(EndPoint endpoint, CancellationToken cancellationToken) => new(endpoint); + + internal virtual bool IsInbuilt => false; // only inbuilt tunnels get added to config strings + + /// + /// Allows modification of a between creation and connection. + /// Passed in is the endpoint we're connecting to, which type of connection it is, and the socket itself. + /// For example, a specific local IP endpoint could be bound, linger time altered, etc. + /// + public virtual ValueTask BeforeSocketConnectAsync(EndPoint endPoint, ConnectionType connectionType, Socket? socket, CancellationToken cancellationToken) => default; + + /// + /// Invoked on a connected endpoint before server authentication and other handshakes occur, allowing pre-redis handshakes. By returning a custom , + /// the entire data flow can be intercepted, providing entire custom transports. + /// + public virtual ValueTask BeforeAuthenticateAsync(EndPoint endpoint, ConnectionType connectionType, Socket? socket, CancellationToken cancellationToken) => default; + + private sealed class HttpProxyTunnel : Tunnel + { + public EndPoint Proxy { get; } + public HttpProxyTunnel(EndPoint proxy) => Proxy = proxy ?? throw new ArgumentNullException(nameof(proxy)); + + public override ValueTask GetSocketConnectEndpointAsync(EndPoint endpoint, CancellationToken cancellationToken) => new(Proxy); + + public override async ValueTask BeforeAuthenticateAsync(EndPoint endpoint, ConnectionType connectionType, Socket? socket, CancellationToken cancellationToken) + { + if (socket is not null) + { + var encoding = Encoding.ASCII; + var ep = Format.ToString(endpoint); + const string Prefix = "CONNECT ", Suffix = " HTTP/1.1\r\n\r\n", ExpectedResponse1 = "HTTP/1.1 200 OK\r\n\r\n", ExpectedResponse2 = "HTTP/1.1 200 Connection established\r\n\r\n"; + byte[] chunk = ArrayPool.Shared.Rent(Math.Max( + encoding.GetByteCount(Prefix) + encoding.GetByteCount(ep) + encoding.GetByteCount(Suffix), + Math.Max(encoding.GetByteCount(ExpectedResponse1), encoding.GetByteCount(ExpectedResponse2)))); + var offset = 0; + offset += encoding.GetBytes(Prefix, 0, Prefix.Length, chunk, offset); + offset += encoding.GetBytes(ep, 0, ep.Length, chunk, offset); + offset += encoding.GetBytes(Suffix, 0, Suffix.Length, chunk, offset); + + static void SafeAbort(object? obj) + { + try + { + (obj as SocketAwaitableEventArgs)?.Abort(SocketError.TimedOut); + } + catch { } // best effort only + } + + using (var args = new SocketAwaitableEventArgs()) + using (cancellationToken.Register(static s => SafeAbort(s), args)) + { + args.SetBuffer(chunk, 0, offset); + if (!socket.SendAsync(args)) args.Complete(); + await args; + + // we expect to see: "HTTP/1.1 200 OK\n"; note our buffer is definitely big enough already + int toRead = Math.Max(encoding.GetByteCount(ExpectedResponse1), encoding.GetByteCount(ExpectedResponse2)), read; + offset = 0; + + var actualResponse = ""; + while (toRead > 0 && !actualResponse.EndsWith("\r\n\r\n")) + { + args.SetBuffer(chunk, offset, toRead); + if (!socket.ReceiveAsync(args)) args.Complete(); + read = await args; + + if (read <= 0) break; // EOF (since we're never doing zero-length reads) + toRead -= read; + offset += read; + + actualResponse = encoding.GetString(chunk, 0, offset); + } + if (toRead != 0 && !actualResponse.EndsWith("\r\n\r\n")) throw new EndOfStreamException("EOF negotiating HTTP tunnel"); + // lazy + if (ExpectedResponse1 != actualResponse && ExpectedResponse2 != actualResponse) + { + throw new InvalidOperationException("Unexpected response negotiating HTTP tunnel"); + } + ArrayPool.Shared.Return(chunk); + } + } + return default; // no need for custom stream wrapper here + } + + internal override bool IsInbuilt => true; + public override string ToString() => "http:" + Format.ToString(Proxy); + } + + /// + /// Create a tunnel via an HTTP proxy server. + /// + /// The endpoint to use as an HTTP proxy server. + public static Tunnel HttpProxy(EndPoint proxy) => new HttpProxyTunnel(proxy); + } +} diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs new file mode 100644 index 000000000..c0021f024 --- /dev/null +++ b/src/StackExchange.Redis/ConfigurationOptions.cs @@ -0,0 +1,1218 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using StackExchange.Redis.Configuration; + +namespace StackExchange.Redis +{ + /// + /// The options relevant to a set of redis connections. + /// + /// + /// Some options are not observed by a after initial creation: + /// + /// + /// + /// + /// + /// + /// + public sealed class ConfigurationOptions : ICloneable + { + private static class OptionKeys + { + public static int ParseInt32(string key, string value, int minValue = int.MinValue, int maxValue = int.MaxValue) + { + if (!Format.TryParseInt32(value, out int tmp)) throw new ArgumentOutOfRangeException(key, $"Keyword '{key}' requires an integer value; the value '{value}' is not recognised."); + if (tmp < minValue) throw new ArgumentOutOfRangeException(key, $"Keyword '{key}' has a minimum value of '{minValue}'; the value '{tmp}' is not permitted."); + if (tmp > maxValue) throw new ArgumentOutOfRangeException(key, $"Keyword '{key}' has a maximum value of '{maxValue}'; the value '{tmp}' is not permitted."); + return tmp; + } + + internal static bool ParseBoolean(string key, string value) + { + if (!Format.TryParseBoolean(value, out bool tmp)) throw new ArgumentOutOfRangeException(key, $"Keyword '{key}' requires a boolean value; the value '{value}' is not recognised."); + return tmp; + } + + internal static Version ParseVersion(string key, string value) + { + if (Format.TryParseVersion(value, out Version? tmp)) + { + return tmp; + } + throw new ArgumentOutOfRangeException(key, $"Keyword '{key}' requires a version value; the value '{value}' is not recognised."); + } + + internal static Proxy ParseProxy(string key, string value) + { + if (!Enum.TryParse(value, true, out Proxy tmp)) throw new ArgumentOutOfRangeException(key, $"Keyword '{key}' requires a proxy value; the value '{value}' is not recognised."); + return tmp; + } + + internal static SslProtocols ParseSslProtocols(string key, string? value) + { + // Flags expect commas as separators, but we need to use '|' since commas are already used in the connection string to mean something else + value = value?.Replace("|", ","); + + if (!Enum.TryParse(value, true, out SslProtocols tmp)) throw new ArgumentOutOfRangeException(key, $"Keyword '{key}' requires an SslProtocol value (multiple values separated by '|'); the value '{value}' is not recognised."); + + return tmp; + } + + internal static RedisProtocol ParseRedisProtocol(string key, string value) + { + if (TryParseRedisProtocol(value, out var protocol)) return protocol; + throw new ArgumentOutOfRangeException(key, $"Keyword '{key}' requires a RedisProtocol value or a known protocol version number; the value '{value}' is not recognised."); + } + + internal static void Unknown(string key) => + throw new ArgumentException($"Keyword '{key}' is not supported.", key); + + internal const string + AbortOnConnectFail = "abortConnect", + AllowAdmin = "allowAdmin", + AsyncTimeout = "asyncTimeout", + ChannelPrefix = "channelPrefix", + ConfigChannel = "configChannel", + ConfigCheckSeconds = "configCheckSeconds", + ConnectRetry = "connectRetry", + ConnectTimeout = "connectTimeout", + DefaultDatabase = "defaultDatabase", + HighPrioritySocketThreads = "highPriorityThreads", + KeepAlive = "keepAlive", + ClientName = "name", + User = "user", + Password = "password", + PreserveAsyncOrder = "preserveAsyncOrder", + Proxy = "proxy", + ResolveDns = "resolveDns", + ResponseTimeout = "responseTimeout", + ServiceName = "serviceName", + Ssl = "ssl", + SslHost = "sslHost", + SslProtocols = "sslProtocols", + SyncTimeout = "syncTimeout", + TieBreaker = "tiebreaker", + Version = "version", + WriteBuffer = "writeBuffer", + CheckCertificateRevocation = "checkCertificateRevocation", + Tunnel = "tunnel", + SetClientLibrary = "setlib", + Protocol = "protocol", + HighIntegrity = "highIntegrity"; + + private static readonly Dictionary normalizedOptions = new[] + { + AbortOnConnectFail, + AllowAdmin, + AsyncTimeout, + ChannelPrefix, + ClientName, + ConfigChannel, + ConfigCheckSeconds, + ConnectRetry, + ConnectTimeout, + DefaultDatabase, + HighPrioritySocketThreads, + KeepAlive, + User, + Password, + PreserveAsyncOrder, + Proxy, + ResolveDns, + ServiceName, + Ssl, + SslHost, + SslProtocols, + SyncTimeout, + TieBreaker, + Version, + WriteBuffer, + CheckCertificateRevocation, + Protocol, + HighIntegrity, + }.ToDictionary(x => x, StringComparer.OrdinalIgnoreCase); + + public static string TryNormalize(string value) + { + if (value != null && normalizedOptions.TryGetValue(value, out string? tmp)) + { + return tmp ?? ""; + } + return value ?? ""; + } + } + + private DefaultOptionsProvider? defaultOptions; + + private bool? allowAdmin, abortOnConnectFail, resolveDns, ssl, checkCertificateRevocation, heartbeatConsistencyChecks, + includeDetailInExceptions, includePerformanceCountersInExceptions, setClientLibrary, highIntegrity; + + private string? tieBreaker, sslHost, configChannel, user, password; + + private TimeSpan? heartbeatInterval; + + private CommandMap? commandMap; + + private Version? defaultVersion; + + private int? keepAlive, asyncTimeout, syncTimeout, connectTimeout, responseTimeout, connectRetry, configCheckSeconds; + + private Proxy? proxy; + + private IReconnectRetryPolicy? reconnectRetryPolicy; + + private BacklogPolicy? backlogPolicy; + + private ILoggerFactory? loggerFactory; + + /// + /// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication; note + /// that this cannot be specified in the configuration-string. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Existing compatibility")] + public event LocalCertificateSelectionCallback? CertificateSelection; + + /// + /// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party; note + /// that this cannot be specified in the configuration-string. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Existing compatibility")] + public event RemoteCertificateValidationCallback? CertificateValidation; + + /// + /// The default (not explicitly configured) options for this connection, fetched based on our parsed endpoints. + /// + public DefaultOptionsProvider Defaults + { + get => defaultOptions ??= DefaultOptionsProvider.GetProvider(EndPoints); + set => defaultOptions = value; + } + + /// + /// Allows modification of a between creation and connection. + /// Passed in is the endpoint we're connecting to, which type of connection it is, and the socket itself. + /// For example, a specific local IP endpoint could be bound, linger time altered, etc. + /// + public Action? BeforeSocketConnect { get; set; } + + internal Func, Task> AfterConnectAsync => Defaults.AfterConnectAsync; + + /// + /// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException. + /// + public bool AbortOnConnectFail + { + get => abortOnConnectFail ?? Defaults.AbortOnConnectFail; + set => abortOnConnectFail = value; + } + + /// + /// Indicates whether admin operations should be allowed. + /// + public bool AllowAdmin + { + get => allowAdmin ?? Defaults.AllowAdmin; + set => allowAdmin = value; + } + + /// + /// Specifies the time in milliseconds that the system should allow for asynchronous operations (defaults to SyncTimeout). + /// + public int AsyncTimeout + { + get => asyncTimeout ?? SyncTimeout; + set => asyncTimeout = value; + } + + /// + /// Indicates whether the connection should be encrypted. + /// + [Obsolete("Please use .Ssl instead of .UseSsl, will be removed in 3.0."), + Browsable(false), + EditorBrowsable(EditorBrowsableState.Never)] + public bool UseSsl + { + get => Ssl; + set => Ssl = value; + } + + /// + /// Gets or sets whether the library should identify itself by library-name/version when possible. + /// + public bool SetClientLibrary + { + get => setClientLibrary ?? Defaults.SetClientLibrary; + set => setClientLibrary = value; + } + + /// + /// Gets or sets the library name to use for CLIENT SETINFO lib-name calls to Redis during handshake. + /// Defaults to "SE.Redis". + /// + /// If the value is null, empty or whitespace, then the value from the options-provider is used; + /// to disable the library name feature, use instead. + public string? LibraryName { get; set; } + + /// + /// Automatically encodes and decodes channels. + /// + public RedisChannel ChannelPrefix { get; set; } + + /// + /// A Boolean value that specifies whether the certificate revocation list is checked during authentication. + /// + public bool CheckCertificateRevocation + { + get => checkCertificateRevocation ?? Defaults.CheckCertificateRevocation; + set => checkCertificateRevocation = value; + } + + /// + /// A Boolean value that specifies whether to use per-command validation of strict protocol validity. + /// This sends an additional command after EVERY command which incurs measurable overhead. + /// + /// + /// The regular RESP protocol does not include correlation identifiers between requests and responses; in exceptional + /// scenarios, protocol desynchronization can occur, which may not be noticed immediately; this option adds additional data + /// to ensure that this cannot occur, at the cost of some (small) additional bandwidth usage. + /// + public bool HighIntegrity + { + get => highIntegrity ?? Defaults.HighIntegrity; + set => highIntegrity = value; + } + + /// + /// Create a certificate validation check that checks against the supplied issuer even when not known by the machine. + /// + /// The file system path to find the certificate at. + public void TrustIssuer(string issuerCertificatePath) => CertificateValidationCallback = TrustIssuerCallback(issuerCertificatePath); + +#if NET5_0_OR_GREATER + /// + /// Supply a user certificate from a PEM file pair and enable TLS. + /// + /// The path for the the user certificate (commonly a .crt file). + /// The path for the the user key (commonly a .key file). + public void SetUserPemCertificate(string userCertificatePath, string? userKeyPath = null) + { + CertificateSelectionCallback = CreatePemUserCertificateCallback(userCertificatePath, userKeyPath); + Ssl = true; + } +#endif + + /// + /// Supply a user certificate from a PFX file and optional password and enable TLS. + /// + /// The path for the the user certificate (commonly a .pfx file). + /// The password for the certificate file. + public void SetUserPfxCertificate(string userCertificatePath, string? password = null) + { + CertificateSelectionCallback = CreatePfxUserCertificateCallback(userCertificatePath, password); + Ssl = true; + } + +#if NET5_0_OR_GREATER + internal static LocalCertificateSelectionCallback CreatePemUserCertificateCallback(string userCertificatePath, string? userKeyPath) + { + // PEM handshakes not universally supported and causes a runtime error about ephemeral certificates; to avoid, export as PFX + using var pem = X509Certificate2.CreateFromPemFile(userCertificatePath, userKeyPath); +#pragma warning disable SYSLIB0057 // Type or member is obsolete + var pfx = new X509Certificate2(pem.Export(X509ContentType.Pfx)); +#pragma warning restore SYSLIB0057 // Type or member is obsolete + + return (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => pfx; + } +#endif + + internal static LocalCertificateSelectionCallback CreatePfxUserCertificateCallback(string userCertificatePath, string? password, X509KeyStorageFlags storageFlags = X509KeyStorageFlags.DefaultKeySet) + { + var pfx = new X509Certificate2(userCertificatePath, password ?? "", storageFlags); + return (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => pfx; + } + + /// + /// Create a certificate validation check that checks against the supplied issuer even when not known by the machine. + /// + /// The issuer to trust. + public void TrustIssuer(X509Certificate2 issuer) => CertificateValidationCallback = TrustIssuerCallback(issuer); + + internal static RemoteCertificateValidationCallback TrustIssuerCallback(string issuerCertificatePath) + => TrustIssuerCallback(new X509Certificate2(issuerCertificatePath)); + private static RemoteCertificateValidationCallback TrustIssuerCallback(X509Certificate2 issuer) + { + if (issuer == null) throw new ArgumentNullException(nameof(issuer)); + + return (object _, X509Certificate? certificate, X509Chain? certificateChain, SslPolicyErrors sslPolicyError) => + { + // If we're already valid, there's nothing further to check + if (sslPolicyError == SslPolicyErrors.None) + { + return true; + } + // If we're not valid due to chain errors - check against the trusted issuer + // Note that we're only proceeding at all here if the *only* issue is chain errors (not more flags in SslPolicyErrors) + return sslPolicyError == SslPolicyErrors.RemoteCertificateChainErrors + && certificate is X509Certificate2 v2 + && CheckTrustedIssuer(v2, certificateChain, issuer); + }; + } + + private static readonly Oid _serverAuthOid = new Oid("1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.1"); + + private static bool CheckTrustedIssuer(X509Certificate2 certificateToValidate, X509Chain? chainToValidate, X509Certificate2 authority) + { + // Reference: + // https://stackoverflow.com/questions/6497040/how-do-i-validate-that-a-certificate-was-created-by-a-particular-certification-a + // https://github.com/stewartadam/dotnet-x509-certificate-verification + using X509Chain chain = new X509Chain(); + chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + chain.ChainPolicy.VerificationTime = chainToValidate?.ChainPolicy?.VerificationTime ?? DateTime.Now; + chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 0); + // Ensure entended key usage checks are run and that we're observing a server TLS certificate + chain.ChainPolicy.ApplicationPolicy.Add(_serverAuthOid); + + chain.ChainPolicy.ExtraStore.Add(authority); + try + { + // This only verifies that the chain is valid, but with AllowUnknownCertificateAuthority could trust + // self-signed or partial chained certificates + bool chainIsVerified; + try + { + chainIsVerified = chain.Build(certificateToValidate); + } + catch (ArgumentException ex) when ((ex.ParamName ?? ex.Message) == "certificate" && Runtime.IsMono) + { + // work around Mono cert limitation; report as rejected rather than fault + // (note also the likely .ctor mixup re param-name vs message) + chainIsVerified = false; + } + if (chainIsVerified) + { + // Our method is "TrustIssuer", which means any intermediate cert we're being told to trust + // is a valid thing to trust, up until it's a root CA + bool found = false; + byte[] authorityData = authority.RawData; + foreach (var chainElement in chain.ChainElements) + { + using var chainCert = chainElement.Certificate; + if (!found) + { +#if NET8_0_OR_GREATER + if (chainCert.RawDataMemory.Span.SequenceEqual(authorityData)) +#else + if (chainCert.RawData.SequenceEqual(authorityData)) +#endif + { + found = true; + } + } + } + return found; + } + } + catch (CryptographicException) + { + // We specifically don't want to throw during validation here and would rather exit out gracefully + } + + // If we didn't find the trusted issuer in the chain at all - we do not trust the result. + return false; + } + + /// + /// The client name to use for all connections. + /// + public string? ClientName { get; set; } + + /// + /// The number of times to repeat the initial connect cycle if no servers respond promptly. + /// + public int ConnectRetry + { + get => connectRetry ?? Defaults.ConnectRetry; + set => connectRetry = value; + } + + /// + /// The command-map associated with this configuration. + /// + /// + /// This is memoized when a connects. + /// Modifying it afterwards will have no effect on already-created multiplexers. + /// + public CommandMap CommandMap + { + get => commandMap ?? Defaults.CommandMap ?? Proxy switch + { + Proxy.Twemproxy => CommandMap.Twemproxy, + Proxy.Envoyproxy => CommandMap.Envoyproxy, + _ => CommandMap.Default, + }; + set => commandMap = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// Gets the command map for a given server type, since some supersede settings when connecting. + /// + internal CommandMap GetCommandMap(ServerType? serverType) => serverType switch + { + ServerType.Sentinel => CommandMap.Sentinel, + _ => CommandMap, + }; + + /// + /// Channel to use for broadcasting and listening for configuration change notification. + /// + /// + /// This is memoized when a connects. + /// Modifying it afterwards will have no effect on already-created multiplexers. + /// + public string ConfigurationChannel + { + get => configChannel ?? Defaults.ConfigurationChannel; + set => configChannel = value; + } + + /// + /// Specifies the time in milliseconds that should be allowed for connection (defaults to 5 seconds unless SyncTimeout is higher). + /// + public int ConnectTimeout + { + get => connectTimeout ?? ((int?)Defaults.ConnectTimeout?.TotalMilliseconds) ?? Math.Max(5000, SyncTimeout); + set => connectTimeout = value; + } + + /// + /// Specifies the default database to be used when calling without any parameters. + /// + public int? DefaultDatabase { get; set; } + + /// + /// The server version to assume. + /// + public Version DefaultVersion + { + get => defaultVersion ?? Defaults.DefaultVersion; + set => defaultVersion = value; + } + + /// + /// The endpoints defined for this configuration. + /// + /// + /// This is memoized when a connects. + /// Modifying it afterwards will have no effect on already-created multiplexers. + /// + public EndPointCollection EndPoints { get; init; } = new EndPointCollection(); + + /// + /// Whether to enable ECHO checks on every heartbeat to ensure network stream consistency. + /// This is a rare measure to react to any potential network traffic drops ASAP, terminating the connection. + /// + public bool HeartbeatConsistencyChecks + { + get => heartbeatConsistencyChecks ?? Defaults.HeartbeatConsistencyChecks; + set => heartbeatConsistencyChecks = value; + } + + /// + /// Controls how often the connection heartbeats. A heartbeat includes: + /// - Evaluating if any messages have timed out. + /// - Evaluating connection status (checking for failures). + /// - Sending a server message to keep the connection alive if needed. + /// + /// + /// This defaults to 1000 milliseconds and should not be changed for most use cases. + /// If for example you want to evaluate whether commands have violated the at a lower fidelity + /// than 1000 milliseconds, you could lower this value. + /// Be aware setting this very low incurs additional overhead of evaluating the above more often. + /// + public TimeSpan HeartbeatInterval + { + get => heartbeatInterval ?? Defaults.HeartbeatInterval; + set => heartbeatInterval = value; + } + + /// + /// Use ThreadPriority.AboveNormal for SocketManager reader and writer threads (true by default). + /// If , will be used. + /// + [Obsolete($"This setting no longer has any effect, please use {nameof(SocketManager.SocketManagerOptions)}.{nameof(SocketManager.SocketManagerOptions.UseHighPrioritySocketThreads)} instead - this setting will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public bool HighPrioritySocketThreads + { + get => false; + set { } + } + + /// + /// Whether exceptions include identifiable details (key names, additional .Data annotations). + /// + public bool IncludeDetailInExceptions + { + get => includeDetailInExceptions ?? Defaults.IncludeDetailInExceptions; + set => includeDetailInExceptions = value; + } + + /// + /// Whether exceptions include performance counter details. + /// + /// + /// CPU usage, etc - note that this can be problematic on some platforms. + /// + public bool IncludePerformanceCountersInExceptions + { + get => includePerformanceCountersInExceptions ?? Defaults.IncludePerformanceCountersInExceptions; + set => includePerformanceCountersInExceptions = value; + } + + /// + /// Specifies the time in seconds at which connections should be pinged to ensure validity. + /// -1 Defaults to 60 Seconds. + /// + public int KeepAlive + { + get => keepAlive ?? (int)Defaults.KeepAliveInterval.TotalSeconds; + set => keepAlive = value; + } + + /// + /// The to get loggers for connection events. + /// Note: changes here only affect s created after. + /// + public ILoggerFactory? LoggerFactory + { + get => loggerFactory ?? Defaults.LoggerFactory; + set => loggerFactory = value; + } + + /// + /// The username to use to authenticate with the server. + /// + public string? User + { + get => user ?? Defaults.User; + set => user = value; + } + + /// + /// The password to use to authenticate with the server. + /// + public string? Password + { + get => password ?? Defaults.Password; + set => password = value; + } + + /// + /// Specifies whether asynchronous operations should be invoked in a way that guarantees their original delivery order. + /// + [Obsolete("Not supported; if you require ordered pub/sub, please see " + nameof(ChannelMessageQueue) + " - this will be removed in 3.0.", false)] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public bool PreserveAsyncOrder + { + get => false; + set { } + } + + /// + /// Type of proxy to use (if any); for example . + /// + public Proxy Proxy + { + get => proxy ?? Defaults.Proxy; + set => proxy = value; + } + + /// + /// The retry policy to be used for connection reconnects. + /// + public IReconnectRetryPolicy ReconnectRetryPolicy + { + get => reconnectRetryPolicy ??= Defaults.ReconnectRetryPolicy ?? new ExponentialRetry(ConnectTimeout / 2); + set => reconnectRetryPolicy = value; + } + + /// + /// The backlog policy to be used for commands when a connection is unhealthy. + /// + public BacklogPolicy BacklogPolicy + { + get => backlogPolicy ?? Defaults.BacklogPolicy; + set => backlogPolicy = value; + } + + /// + /// Indicates whether endpoints should be resolved via DNS before connecting. + /// If enabled the ConnectionMultiplexer will not re-resolve DNS when attempting to re-connect after a connection failure. + /// + public bool ResolveDns + { + get => resolveDns ?? Defaults.ResolveDns; + set => resolveDns = value; + } + + /// + /// Specifies the time in milliseconds that the system should allow for responses before concluding that the socket is unhealthy. + /// + [Obsolete("This setting no longer has any effect, and should not be used - will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public int ResponseTimeout + { + get => 0; + set { } + } + + /// + /// The service name used to resolve a service via sentinel. + /// + public string? ServiceName { get; set; } + + /// + /// Gets or sets the SocketManager instance to be used with these options. + /// If this is null a shared cross-multiplexer is used. + /// + /// + /// This is only used when a is created. + /// Modifying it afterwards will have no effect on already-created multiplexers. + /// + public SocketManager? SocketManager { get; set; } + +#if NETCOREAPP3_1_OR_GREATER + /// + /// A provider for a given host, for custom TLS connection options. + /// Note: this overrides *all* other TLS and certificate settings, only for advanced use cases. + /// + public Func? SslClientAuthenticationOptions { get; set; } +#endif + + /// + /// Indicates whether the connection should be encrypted. + /// + public bool Ssl + { + get => ssl ?? Defaults.GetDefaultSsl(EndPoints); + set => ssl = value; + } + + /// + /// The target-host to use when validating SSL certificate; setting a value here enables SSL mode. + /// + public string? SslHost + { + get => sslHost ?? Defaults.GetSslHostFromEndpoints(EndPoints); + set => sslHost = value; + } + + /// + /// Configures which SSL/TLS protocols should be allowed. If not set, defaults are chosen by the .NET framework. + /// + public SslProtocols? SslProtocols { get; set; } + + /// + /// Specifies the time in milliseconds that the system should allow for synchronous operations (defaults to 5 seconds). + /// + public int SyncTimeout + { + get => syncTimeout ?? (int)Defaults.SyncTimeout.TotalMilliseconds; + set => syncTimeout = value; + } + + /// + /// Tie-breaker used to choose between primaries (must match the endpoint exactly). + /// + public string TieBreaker + { + get => tieBreaker ?? Defaults.TieBreaker; + set => tieBreaker = value; + } + + /// + /// The size of the output buffer to use. + /// + [Obsolete("This setting no longer has any effect, and should not be used - will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public int WriteBuffer + { + get => 0; + set { } + } + + internal LocalCertificateSelectionCallback? CertificateSelectionCallback + { + get => CertificateSelection; + private set => CertificateSelection = value; + } + + // these just rip out the underlying handlers, bypassing the event accessors - needed when creating the SSL stream + internal RemoteCertificateValidationCallback? CertificateValidationCallback + { + get => CertificateValidation; + private set => CertificateValidation = value; + } + + /// + /// Check configuration every n seconds (every minute by default). + /// + public int ConfigCheckSeconds + { + get => configCheckSeconds ?? (int)Defaults.ConfigCheckInterval.TotalSeconds; + set => configCheckSeconds = value; + } + + /// + /// Parse the configuration from a comma-delimited configuration string. + /// + /// The configuration string to parse. + /// is . + /// is empty. + public static ConfigurationOptions Parse(string configuration) => Parse(configuration, false); + + /// + /// Parse the configuration from a comma-delimited configuration string. + /// + /// The configuration string to parse. + /// Whether to ignore unknown elements in . + /// is . + /// is empty. + public static ConfigurationOptions Parse(string configuration, bool ignoreUnknown) => + new ConfigurationOptions().DoParse(configuration, ignoreUnknown); + + /// + /// Create a copy of the configuration. + /// + public ConfigurationOptions Clone() => new ConfigurationOptions + { + defaultOptions = defaultOptions, + ClientName = ClientName, + ServiceName = ServiceName, + keepAlive = keepAlive, + syncTimeout = syncTimeout, + asyncTimeout = asyncTimeout, + allowAdmin = allowAdmin, + defaultVersion = defaultVersion, + connectTimeout = connectTimeout, + user = user, + password = password, + tieBreaker = tieBreaker, + ssl = ssl, + sslHost = sslHost, + configChannel = configChannel, + abortOnConnectFail = abortOnConnectFail, + resolveDns = resolveDns, + proxy = proxy, + commandMap = commandMap, + CertificateValidationCallback = CertificateValidationCallback, + CertificateSelectionCallback = CertificateSelectionCallback, + ChannelPrefix = ChannelPrefix.Clone(), + SocketManager = SocketManager, + connectRetry = connectRetry, + configCheckSeconds = configCheckSeconds, + responseTimeout = responseTimeout, + DefaultDatabase = DefaultDatabase, + reconnectRetryPolicy = reconnectRetryPolicy, + backlogPolicy = backlogPolicy, + SslProtocols = SslProtocols, + checkCertificateRevocation = checkCertificateRevocation, + BeforeSocketConnect = BeforeSocketConnect, + EndPoints = EndPoints.Clone(), + LoggerFactory = LoggerFactory, +#if NETCOREAPP3_1_OR_GREATER + SslClientAuthenticationOptions = SslClientAuthenticationOptions, +#endif + Tunnel = Tunnel, + setClientLibrary = setClientLibrary, + LibraryName = LibraryName, + Protocol = Protocol, + heartbeatInterval = heartbeatInterval, + heartbeatConsistencyChecks = heartbeatConsistencyChecks, + highIntegrity = highIntegrity, + }; + + /// + /// Apply settings to configure this instance of , e.g. for a specific scenario. + /// + /// An action that will update the properties of this instance. + /// This instance, with any changes made. + public ConfigurationOptions Apply(Action configure) + { + configure?.Invoke(this); + return this; + } + + /// + /// Resolve the default port for any endpoints that did not have a port explicitly specified. + /// + public void SetDefaultPorts() => EndPoints.SetDefaultPorts(ServerType.Standalone, ssl: Ssl); + + internal bool IsSentinel => !string.IsNullOrEmpty(ServiceName); + + /// + /// Gets a tie breaker if we both have one set, and should be using one. + /// + internal bool TryGetTieBreaker(out RedisKey tieBreaker) + { + var key = TieBreaker; + if (!IsSentinel && !string.IsNullOrWhiteSpace(key)) + { + tieBreaker = key; + return true; + } + tieBreaker = default; + return false; + } + + /// + /// Returns the effective configuration string for this configuration, including Redis credentials. + /// + /// + /// Includes password to allow generation of configuration strings used for connecting multiplexer. + /// + public override string ToString() => ToString(includePassword: true); + + /// + /// Returns the effective configuration string for this configuration + /// with the option to include or exclude the password from the string. + /// + /// Whether to include the password. + public string ToString(bool includePassword) + { + var sb = new StringBuilder(); + foreach (var endpoint in EndPoints) + { + Append(sb, Format.ToString(endpoint)); + } + Append(sb, OptionKeys.ClientName, ClientName); + Append(sb, OptionKeys.ServiceName, ServiceName); + Append(sb, OptionKeys.KeepAlive, keepAlive); + Append(sb, OptionKeys.SyncTimeout, syncTimeout); + Append(sb, OptionKeys.AsyncTimeout, asyncTimeout); + Append(sb, OptionKeys.AllowAdmin, allowAdmin); + Append(sb, OptionKeys.Version, defaultVersion); + Append(sb, OptionKeys.ConnectTimeout, connectTimeout); + Append(sb, OptionKeys.User, user); + Append(sb, OptionKeys.Password, (includePassword || string.IsNullOrEmpty(password)) ? password : "*****"); + Append(sb, OptionKeys.TieBreaker, tieBreaker); + Append(sb, OptionKeys.Ssl, ssl); + Append(sb, OptionKeys.SslProtocols, SslProtocols?.ToString().Replace(',', '|')); + Append(sb, OptionKeys.CheckCertificateRevocation, checkCertificateRevocation); + Append(sb, OptionKeys.SslHost, sslHost); + Append(sb, OptionKeys.ConfigChannel, configChannel); + Append(sb, OptionKeys.AbortOnConnectFail, abortOnConnectFail); + Append(sb, OptionKeys.ResolveDns, resolveDns); + Append(sb, OptionKeys.ChannelPrefix, (string?)ChannelPrefix); + Append(sb, OptionKeys.ConnectRetry, connectRetry); + Append(sb, OptionKeys.Proxy, proxy); + Append(sb, OptionKeys.ConfigCheckSeconds, configCheckSeconds); + Append(sb, OptionKeys.ResponseTimeout, responseTimeout); + Append(sb, OptionKeys.DefaultDatabase, DefaultDatabase); + Append(sb, OptionKeys.SetClientLibrary, setClientLibrary); + Append(sb, OptionKeys.HighIntegrity, highIntegrity); + Append(sb, OptionKeys.Protocol, FormatProtocol(Protocol)); + if (Tunnel is { IsInbuilt: true } tunnel) + { + Append(sb, OptionKeys.Tunnel, tunnel.ToString()); + } + commandMap?.AppendDeltas(sb); + return sb.ToString(); + + static string? FormatProtocol(RedisProtocol? protocol) => protocol switch { + null => null, + RedisProtocol.Resp2 => "resp2", + RedisProtocol.Resp3 => "resp3", + _ => protocol.GetValueOrDefault().ToString(), + }; + } + + private static void Append(StringBuilder sb, object value) + { + if (value == null) return; + string s = Format.ToString(value); + if (!string.IsNullOrWhiteSpace(s)) + { + if (sb.Length != 0) sb.Append(','); + sb.Append(s); + } + } + + private static void Append(StringBuilder sb, string prefix, object? value) + { + string? s = value?.ToString(); + if (!string.IsNullOrWhiteSpace(s)) + { + if (sb.Length != 0) sb.Append(','); + if (!string.IsNullOrEmpty(prefix)) + { + sb.Append(prefix).Append('='); + } + sb.Append(s); + } + } + + private void Clear() + { + ClientName = ServiceName = user = password = tieBreaker = sslHost = configChannel = null; + keepAlive = syncTimeout = asyncTimeout = connectTimeout = connectRetry = configCheckSeconds = DefaultDatabase = null; + allowAdmin = abortOnConnectFail = resolveDns = ssl = setClientLibrary = highIntegrity = null; + SslProtocols = null; + defaultVersion = null; + EndPoints.Clear(); + commandMap = null; + + CertificateSelection = null; + CertificateValidation = null; + ChannelPrefix = default; + SocketManager = null; + Tunnel = null; + } + + object ICloneable.Clone() => Clone(); + + private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown) + { + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (string.IsNullOrWhiteSpace(configuration)) + { + throw new ArgumentException("is empty", nameof(configuration)); + } + + Clear(); + + // break it down by commas + var arr = configuration.Split(StringSplits.Comma); + Dictionary? map = null; + foreach (var paddedOption in arr) + { + var option = paddedOption.Trim(); + + if (string.IsNullOrWhiteSpace(option)) continue; + + // check for special tokens + int idx = option.IndexOf('='); + if (idx > 0) + { + var key = option.Substring(0, idx).Trim(); + var value = option.Substring(idx + 1).Trim(); + + switch (OptionKeys.TryNormalize(key)) + { + case OptionKeys.CheckCertificateRevocation: + CheckCertificateRevocation = OptionKeys.ParseBoolean(key, value); + break; + case OptionKeys.SyncTimeout: + SyncTimeout = OptionKeys.ParseInt32(key, value, minValue: 1); + break; + case OptionKeys.AsyncTimeout: + AsyncTimeout = OptionKeys.ParseInt32(key, value, minValue: 1); + break; + case OptionKeys.AllowAdmin: + AllowAdmin = OptionKeys.ParseBoolean(key, value); + break; + case OptionKeys.AbortOnConnectFail: + AbortOnConnectFail = OptionKeys.ParseBoolean(key, value); + break; + case OptionKeys.ResolveDns: + ResolveDns = OptionKeys.ParseBoolean(key, value); + break; + case OptionKeys.ServiceName: + ServiceName = value; + break; + case OptionKeys.ClientName: + ClientName = value; + break; + case OptionKeys.ChannelPrefix: + ChannelPrefix = RedisChannel.Literal(value); + break; + case OptionKeys.ConfigChannel: + ConfigurationChannel = value; + break; + case OptionKeys.KeepAlive: + KeepAlive = OptionKeys.ParseInt32(key, value); + break; + case OptionKeys.ConnectTimeout: + ConnectTimeout = OptionKeys.ParseInt32(key, value); + break; + case OptionKeys.ConnectRetry: + ConnectRetry = OptionKeys.ParseInt32(key, value); + break; + case OptionKeys.ConfigCheckSeconds: + ConfigCheckSeconds = OptionKeys.ParseInt32(key, value); + break; + case OptionKeys.Version: + DefaultVersion = OptionKeys.ParseVersion(key, value); + break; + case OptionKeys.User: + user = value; + break; + case OptionKeys.Password: + password = value; + break; + case OptionKeys.TieBreaker: + TieBreaker = value; + break; + case OptionKeys.Ssl: + Ssl = OptionKeys.ParseBoolean(key, value); + break; + case OptionKeys.SslHost: + SslHost = value; + break; + case OptionKeys.Proxy: + Proxy = OptionKeys.ParseProxy(key, value); + break; + case OptionKeys.DefaultDatabase: + DefaultDatabase = OptionKeys.ParseInt32(key, value); + break; + case OptionKeys.SslProtocols: + SslProtocols = OptionKeys.ParseSslProtocols(key, value); + break; + case OptionKeys.SetClientLibrary: + SetClientLibrary = OptionKeys.ParseBoolean(key, value); + break; + case OptionKeys.HighIntegrity: + HighIntegrity = OptionKeys.ParseBoolean(key, value); + break; + case OptionKeys.Tunnel: + if (value.IsNullOrWhiteSpace()) + { + Tunnel = null; + } + else + { + // For backwards compatibility with `http:address_with_port`. + if (value.StartsWith("http:") && !value.StartsWith("http://")) + { + value = value.Insert(5, "//"); + } + + var uri = new Uri(value, UriKind.Absolute); + if (uri.Scheme != "http") + { + throw new ArgumentException("Tunnel cannot be parsed: " + value); + } + if (!Format.TryParseEndPoint($"{uri.Host}:{uri.Port}", out var ep)) + { + throw new ArgumentException("HTTP tunnel cannot be parsed: " + value); + } + Tunnel = Tunnel.HttpProxy(ep); + } + break; + case OptionKeys.Protocol: + Protocol = OptionKeys.ParseRedisProtocol(key, value); + break; + // Deprecated options we ignore... + case OptionKeys.HighPrioritySocketThreads: + case OptionKeys.PreserveAsyncOrder: + case OptionKeys.ResponseTimeout: + case OptionKeys.WriteBuffer: + break; + default: + if (!string.IsNullOrEmpty(key) && key[0] == '$') + { + var cmdName = option.Substring(1, idx - 1); + if (Enum.TryParse(cmdName, true, out RedisCommand cmd)) + { + map ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + map[cmdName] = value; + } + } + else + { + if (!ignoreUnknown) OptionKeys.Unknown(key); + } + break; + } + } + else + { + if (Format.TryParseEndPoint(option, out var ep) && !EndPoints.Contains(ep)) + { + EndPoints.Add(ep); + } + } + } + if (map != null && map.Count != 0) + { + CommandMap = CommandMap.Create(map); + } + return this; + } + + /// + /// Allows custom transport implementations, such as http-tunneling via a proxy. + /// + public Tunnel? Tunnel { get; set; } + + /// + /// Specify the redis protocol type. + /// + public RedisProtocol? Protocol { get; set; } + + internal bool TryResp3() + { + // note: deliberately leaving the IsAvailable duplicated to use short-circuit + + // if (Protocol is null) + // { + // // if not specified, lean on the server version and whether HELLO is available + // return new RedisFeatures(DefaultVersion).Resp3 && CommandMap.IsAvailable(RedisCommand.HELLO); + // } + // else + // ^^^ left for context; originally our intention was to auto-enable RESP3 by default *if* the server version + // is >= 6; however, it turns out (see extensive conversation here https://github.com/StackExchange/StackExchange.Redis/pull/2396) + // that tangential undocumented API breaks were made at the same time; this means that even if we fix every + // edge case in the library itself, the break is still visible to external callers via Execute[Async]; with an + // abundance of caution, we are therefore making RESP3 explicit opt-in only for now; we may revisit this in a major + { + return Protocol.GetValueOrDefault() >= RedisProtocol.Resp3 && CommandMap.IsAvailable(RedisCommand.HELLO); + } + } + + internal static bool TryParseRedisProtocol(string? value, out RedisProtocol protocol) + { + // accept raw integers too, but only trust them if we recognize them + // (note we need to do this before enums, because Enum.TryParse will + // accept integers as the raw value, which is not what we want here) + if (value is not null) + { + if (Format.TryParseInt32(value, out int i32)) + { + switch (i32) + { + case 2: + protocol = RedisProtocol.Resp2; + return true; + case 3: + protocol = RedisProtocol.Resp3; + return true; + } + } + else + { + if (Enum.TryParse(value, true, out protocol)) return true; + } + } + protocol = default; + return false; + } + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/ConnectionCounters.cs b/src/StackExchange.Redis/ConnectionCounters.cs similarity index 80% rename from StackExchange.Redis/StackExchange/Redis/ConnectionCounters.cs rename to src/StackExchange.Redis/ConnectionCounters.cs index 174f3f229..546e2eff5 100644 --- a/StackExchange.Redis/StackExchange/Redis/ConnectionCounters.cs +++ b/src/StackExchange.Redis/ConnectionCounters.cs @@ -3,7 +3,7 @@ namespace StackExchange.Redis { /// - /// Illustrates the counters associated with an individual connection + /// Illustrates the counters associated with an individual connection. /// public class ConnectionCounters { @@ -13,78 +13,78 @@ internal ConnectionCounters(ConnectionType connectionType) } /// - /// The number of operations that have been completed asynchronously + /// The number of operations that have been completed asynchronously. /// public long CompletedAsynchronously { get; internal set; } /// - /// The number of operations that have been completed synchronously + /// The number of operations that have been completed synchronously. /// public long CompletedSynchronously { get; internal set; } /// - /// The type of this connection + /// The type of this connection. /// public ConnectionType ConnectionType { get; } /// - /// The number of operations that failed to complete asynchronously + /// The number of operations that failed to complete asynchronously. /// public long FailedAsynchronously { get; internal set; } /// - /// Indicates if there are any pending items or failures on this connection + /// Indicates if there are any pending items or failures on this connection. /// public bool IsEmpty => PendingUnsentItems == 0 && SentItemsAwaitingResponse == 0 && ResponsesAwaitingAsyncCompletion == 0 && FailedAsynchronously == 0; /// - /// Indicates the total number of messages despatched to a non-preferred endpoint, for example sent to a master - /// when the caller stated a preference of slave + /// Indicates the total number of messages dispatched to a non-preferred endpoint, for example sent + /// to a primary when the caller stated a preference of replica. /// public long NonPreferredEndpointCount { get; internal set; } /// - /// The number of operations performed on this connection + /// The number of operations performed on this connection. /// public long OperationCount { get; internal set; } /// - /// Operations that have been requested, but which have not yet been sent to the server + /// Operations that have been requested, but which have not yet been sent to the server. /// public int PendingUnsentItems { get; internal set; } /// - /// Operations for which the response has been processed, but which are awaiting asynchronous completion + /// Operations for which the response has been processed, but which are awaiting asynchronous completion. /// public int ResponsesAwaitingAsyncCompletion { get; internal set; } /// - /// Operations that have been sent to the server, but which are awaiting a response + /// Operations that have been sent to the server, but which are awaiting a response. /// public int SentItemsAwaitingResponse { get; internal set; } /// - /// The number of sockets used by this logical connection (total, including reconnects) + /// The number of sockets used by this logical connection (total, including reconnects). /// public long SocketCount { get; internal set; } /// - /// The number of subscriptions (with and without patterns) currently held against this connection + /// The number of subscriptions (with and without patterns) currently held against this connection. /// - public long Subscriptions { get;internal set; } + public long Subscriptions { get; internal set; } /// - /// Indicates the total number of outstanding items against this connection + /// Indicates the total number of outstanding items against this connection. /// public int TotalOutstanding => PendingUnsentItems + SentItemsAwaitingResponse + ResponsesAwaitingAsyncCompletion; /// - /// Indicates the total number of writers items against this connection + /// Indicates the total number of writers items against this connection. /// public int WriterCount { get; internal set; } /// - /// See Object.ToString() + /// See . /// public override string ToString() { @@ -99,6 +99,7 @@ internal void Add(ConnectionCounters other) CompletedAsynchronously += other.CompletedAsynchronously; CompletedSynchronously += other.CompletedSynchronously; FailedAsynchronously += other.FailedAsynchronously; + NonPreferredEndpointCount += other.NonPreferredEndpointCount; OperationCount += other.OperationCount; PendingUnsentItems += other.PendingUnsentItems; ResponsesAwaitingAsyncCompletion += other.ResponsesAwaitingAsyncCompletion; @@ -106,18 +107,21 @@ internal void Add(ConnectionCounters other) SocketCount += other.SocketCount; Subscriptions += other.Subscriptions; WriterCount += other.WriterCount; - NonPreferredEndpointCount += other.NonPreferredEndpointCount; } - internal bool Any() - { - return CompletedAsynchronously != 0 || CompletedSynchronously != 0 - || FailedAsynchronously != 0 || OperationCount != 0 - || PendingUnsentItems != 0 || ResponsesAwaitingAsyncCompletion != 0 - || SentItemsAwaitingResponse != 0 || SocketCount != 0 - || Subscriptions != 0 || WriterCount != 0 - || NonPreferredEndpointCount != 0; - } + internal bool Any() => + CompletedAsynchronously != 0 + || CompletedSynchronously != 0 + || FailedAsynchronously != 0 + || NonPreferredEndpointCount != 0 + || OperationCount != 0 + || PendingUnsentItems != 0 + || ResponsesAwaitingAsyncCompletion != 0 + || SentItemsAwaitingResponse != 0 + || SocketCount != 0 + || Subscriptions != 0 + || WriterCount != 0; + internal void Append(StringBuilder sb) { sb.Append("ops=").Append(OperationCount).Append(", qu=").Append(PendingUnsentItems) diff --git a/src/StackExchange.Redis/ConnectionFailedEventArgs.cs b/src/StackExchange.Redis/ConnectionFailedEventArgs.cs new file mode 100644 index 000000000..5d165add1 --- /dev/null +++ b/src/StackExchange.Redis/ConnectionFailedEventArgs.cs @@ -0,0 +1,71 @@ +using System; +using System.Net; +using System.Text; + +namespace StackExchange.Redis +{ + /// + /// Contains information about a server connection failure. + /// + public class ConnectionFailedEventArgs : EventArgs, ICompletable + { + private readonly EventHandler? handler; + private readonly object sender; + internal ConnectionFailedEventArgs(EventHandler? handler, object sender, EndPoint? endPoint, ConnectionType connectionType, ConnectionFailureType failureType, Exception? exception, string? physicalName) + { + this.handler = handler; + this.sender = sender; + EndPoint = endPoint; + ConnectionType = connectionType; + Exception = exception; + FailureType = failureType; + _physicalName = physicalName ?? GetType().Name; + } + + /// + /// This constructor is only for testing purposes. + /// + /// The source of the event. + /// Redis endpoint. + /// Redis connection type. + /// Redis connection failure type. + /// The exception that occurred. + /// Connection physical name. + public ConnectionFailedEventArgs(object sender, EndPoint endPoint, ConnectionType connectionType, ConnectionFailureType failureType, Exception exception, string physicalName) + : this(null, sender, endPoint, connectionType, failureType, exception, physicalName) + { + } + + private readonly string _physicalName; + + /// + /// Gets the connection-type of the failing connection. + /// + public ConnectionType ConnectionType { get; } + + /// + /// Gets the failing server-endpoint. + /// + public EndPoint? EndPoint { get; } + + /// + /// Gets the exception if available (this can be null). + /// + public Exception? Exception { get; } + + /// + /// The type of failure. + /// + public ConnectionFailureType FailureType { get; } + + void ICompletable.AppendStormLog(StringBuilder sb) => + sb.Append("event, connection-failed: ").Append(EndPoint != null ? Format.ToString(EndPoint) : "n/a"); + + bool ICompletable.TryComplete(bool isAsync) => ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync); + + /// + /// Returns the physical name of the connection. + /// + public override string ToString() => _physicalName; + } +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.Compat.cs b/src/StackExchange.Redis/ConnectionMultiplexer.Compat.cs new file mode 100644 index 000000000..6786e87d2 --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.Compat.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel; +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + /// + /// No longer used. + /// + [Obsolete("No longer used, will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public static TaskFactory Factory { get => Task.Factory; set { } } + + /// + /// Gets or sets whether asynchronous operations should be invoked in a way that guarantees their original delivery order. + /// + [Obsolete("Not supported; if you require ordered pub/sub, please see " + nameof(ChannelMessageQueue) + ", will be removed in 3.0", false)] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public bool PreserveAsyncOrder { get => false; set { } } +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.Debug.cs b/src/StackExchange.Redis/ConnectionMultiplexer.Debug.cs new file mode 100644 index 000000000..9b30ac141 --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.Debug.cs @@ -0,0 +1,39 @@ +using System.Threading; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + private static int _collectedWithoutDispose; + internal static int CollectedWithoutDispose => Volatile.Read(ref _collectedWithoutDispose); + + /// + /// Invoked by the garbage collector. + /// + ~ConnectionMultiplexer() + { + Interlocked.Increment(ref _collectedWithoutDispose); + } + + bool IInternalConnectionMultiplexer.AllowConnect + { + get => AllowConnect; + set => AllowConnect = value; + } + + bool IInternalConnectionMultiplexer.IgnoreConnect + { + get => IgnoreConnect; + set => IgnoreConnect = value; + } + + /// + /// For debugging: when not enabled, servers cannot connect. + /// + internal volatile bool AllowConnect = true; + + /// + /// For debugging: when not enabled, end-connect is silently ignored (to simulate a long-running connect). + /// + internal volatile bool IgnoreConnect; +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.Events.cs b/src/StackExchange.Redis/ConnectionMultiplexer.Events.cs new file mode 100644 index 000000000..0a8b95be5 --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.Events.cs @@ -0,0 +1,120 @@ +using System; +using System.Net; +using System.Runtime.CompilerServices; +using StackExchange.Redis.Maintenance; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + /// + /// Raised whenever a physical connection fails. + /// + public event EventHandler? ConnectionFailed; + internal void OnConnectionFailed(EndPoint endpoint, ConnectionType connectionType, ConnectionFailureType failureType, Exception exception, bool reconfigure, string? physicalName) + { + if (_isDisposed) return; + var handler = ConnectionFailed; + if (handler != null) + { + CompleteAsWorker(new ConnectionFailedEventArgs(handler, this, endpoint, connectionType, failureType, exception, physicalName)); + } + if (reconfigure) + { + ReconfigureIfNeeded(endpoint, false, "connection failed"); + } + } + + /// + /// Raised whenever an internal error occurs (this is primarily for debugging). + /// + public event EventHandler? InternalError; + internal void OnInternalError(Exception exception, EndPoint? endpoint = null, ConnectionType connectionType = ConnectionType.None, [CallerMemberName] string? origin = null) + { + try + { + if (_isDisposed) return; + Trace("Internal error: " + origin + ", " + exception == null ? "unknown" : exception.Message); + var handler = InternalError; + if (handler != null) + { + CompleteAsWorker(new InternalErrorEventArgs(handler, this, endpoint, connectionType, exception, origin)); + } + } + catch + { + // Our internal error event failed...whatcha gonna do, exactly? + } + } + + /// + /// Raised whenever a physical connection is established. + /// + public event EventHandler? ConnectionRestored; + internal void OnConnectionRestored(EndPoint endpoint, ConnectionType connectionType, string? physicalName) + { + if (_isDisposed) return; + var handler = ConnectionRestored; + if (handler != null) + { + CompleteAsWorker(new ConnectionFailedEventArgs(handler, this, endpoint, connectionType, ConnectionFailureType.None, null, physicalName)); + } + ReconfigureIfNeeded(endpoint, false, "connection restored"); + } + + /// + /// Raised when configuration changes are detected. + /// + public event EventHandler? ConfigurationChanged; + internal void OnConfigurationChanged(EndPoint endpoint) => OnEndpointChanged(endpoint, ConfigurationChanged); + + /// + /// Raised when nodes are explicitly requested to reconfigure via broadcast. + /// This usually means primary/replica changes. + /// + public event EventHandler? ConfigurationChangedBroadcast; + internal void OnConfigurationChangedBroadcast(EndPoint endpoint) => OnEndpointChanged(endpoint, ConfigurationChangedBroadcast); + + private void OnEndpointChanged(EndPoint endpoint, EventHandler? handler) + { + if (_isDisposed) return; + if (handler != null) + { + CompleteAsWorker(new EndPointEventArgs(handler, this, endpoint)); + } + } + + /// + /// Raised when server indicates a maintenance event is going to happen. + /// + public event EventHandler? ServerMaintenanceEvent; + internal void OnServerMaintenanceEvent(ServerMaintenanceEvent e) => + ServerMaintenanceEvent?.Invoke(this, e); + + /// + /// Raised when a hash-slot has been relocated. + /// + public event EventHandler? HashSlotMoved; + internal void OnHashSlotMoved(int hashSlot, EndPoint? old, EndPoint @new) + { + var handler = HashSlotMoved; + if (handler != null) + { + CompleteAsWorker(new HashSlotMovedEventArgs(handler, this, hashSlot, old, @new)); + } + } + + /// + /// Raised when a server replied with an error message. + /// + public event EventHandler? ErrorMessage; + internal void OnErrorMessage(EndPoint endpoint, string message) + { + if (_isDisposed) return; + var handler = ErrorMessage; + if (handler != null) + { + CompleteAsWorker(new RedisErrorEventArgs(handler, this, endpoint, message)); + } + } +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.ExportConfiguration.cs b/src/StackExchange.Redis/ConnectionMultiplexer.ExportConfiguration.cs new file mode 100644 index 000000000..c095ffd53 --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.ExportConfiguration.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Threading.Tasks; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + private const string NoContent = "(no content)"; + + /// + /// Write the configuration of all servers to an output stream. + /// + /// The destination stream to write the export to. + /// The options to use for this export. + public void ExportConfiguration(Stream destination, ExportOptions options = ExportOptions.All) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + + // What is possible, given the command map? + ExportOptions mask = 0; + if (CommandMap.IsAvailable(RedisCommand.INFO)) mask |= ExportOptions.Info; + if (CommandMap.IsAvailable(RedisCommand.CONFIG)) mask |= ExportOptions.Config; + if (CommandMap.IsAvailable(RedisCommand.CLIENT)) mask |= ExportOptions.Client; + if (CommandMap.IsAvailable(RedisCommand.CLUSTER)) mask |= ExportOptions.Cluster; + options &= mask; + + using (var zip = new ZipArchive(destination, ZipArchiveMode.Create, true)) + { + var arr = GetServerSnapshot(); + foreach (var server in arr) + { + const CommandFlags flags = CommandFlags.None; + if (!server.IsConnected) continue; + var api = GetServer(server.EndPoint); + + List tasks = new List(); + if ((options & ExportOptions.Info) != 0) + { + tasks.Add(api.InfoRawAsync(flags: flags)); + } + if ((options & ExportOptions.Config) != 0) + { + tasks.Add(api.ConfigGetAsync(flags: flags)); + } + if ((options & ExportOptions.Client) != 0) + { + tasks.Add(api.ClientListAsync(flags: flags)); + } + if ((options & ExportOptions.Cluster) != 0) + { + tasks.Add(api.ClusterNodesRawAsync(flags: flags)); + } + + WaitAllIgnoreErrors(tasks.ToArray()); + + int index = 0; + var prefix = Format.ToString(server.EndPoint); + if ((options & ExportOptions.Info) != 0) + { + Write(zip, prefix + "/info.txt", tasks[index++], WriteNormalizingLineEndings); + } + if ((options & ExportOptions.Config) != 0) + { + Write[]>(zip, prefix + "/config.txt", tasks[index++], (settings, writer) => + { + foreach (var setting in settings) + { + writer.WriteLine("{0}={1}", setting.Key, setting.Value); + } + }); + } + if ((options & ExportOptions.Client) != 0) + { + Write(zip, prefix + "/clients.txt", tasks[index++], (clients, writer) => + { + if (clients == null) + { + writer.WriteLine(NoContent); + } + else + { + foreach (var client in clients) + { + writer.WriteLine(client.Raw); + } + } + }); + } + if ((options & ExportOptions.Cluster) != 0) + { + Write(zip, prefix + "/nodes.txt", tasks[index++], WriteNormalizingLineEndings); + } + } + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "We're not double disposing.")] + private static void Write(ZipArchive zip, string name, Task task, Action callback) + { + var entry = zip.CreateEntry(name, CompressionLevel.Optimal); + using (var stream = entry.Open()) + using (var writer = new StreamWriter(stream)) + { + TaskStatus status = task.Status; + switch (status) + { + case TaskStatus.RanToCompletion: + T val = ((Task)task).Result; + callback(val, writer); + break; + case TaskStatus.Faulted: + writer.WriteLine(string.Join(", ", task.Exception!.InnerExceptions.Select(x => x.Message))); + break; + default: + writer.WriteLine(status.ToString()); + break; + } + } + } + + private static void WriteNormalizingLineEndings(string source, StreamWriter writer) + { + if (source == null) + { + writer.WriteLine(NoContent); + } + else + { + using (var reader = new StringReader(source)) + { + while (reader.ReadLine() is string line) + { + writer.WriteLine(line); // normalize line endings + } + } + } + } +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.FeatureFlags.cs b/src/StackExchange.Redis/ConnectionMultiplexer.FeatureFlags.cs new file mode 100644 index 000000000..975da5de1 --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.FeatureFlags.cs @@ -0,0 +1,57 @@ +using System; +using System.ComponentModel; +using System.Threading; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + private static FeatureFlags s_featureFlags; + + [Flags] + private enum FeatureFlags + { + None, + PreventThreadTheft = 1, + } + + private static void SetAutodetectFeatureFlags() + { + bool value = false; + try + { + // attempt to detect a known problem scenario + value = SynchronizationContext.Current?.GetType()?.Name + == "LegacyAspNetSynchronizationContext"; + } + catch { } + SetFeatureFlag(nameof(FeatureFlags.PreventThreadTheft), value); + } + + /// + /// Enables or disables a feature flag. + /// This should only be used under support guidance, and should not be rapidly toggled. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Browsable(false)] + public static void SetFeatureFlag(string flag, bool enabled) + { + if (Enum.TryParse(flag, true, out var flags)) + { + if (enabled) s_featureFlags |= flags; + else s_featureFlags &= ~flags; + } + } + + /// + /// Returns the state of a feature flag. + /// This should only be used under support guidance. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Browsable(false)] + public static bool GetFeatureFlag(string flag) + => Enum.TryParse(flag, true, out var flags) + && (s_featureFlags & flags) == flags; + + internal static bool PreventThreadTheft => (s_featureFlags & FeatureFlags.PreventThreadTheft) != 0; +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.LibraryName.cs b/src/StackExchange.Redis/ConnectionMultiplexer.LibraryName.cs new file mode 100644 index 000000000..2c79f80c5 --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.LibraryName.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + private readonly HashSet _libraryNameSuffixHash = new(); + private string _libraryNameSuffixCombined = ""; + + /// + public void AddLibraryNameSuffix(string suffix) + { + if (string.IsNullOrWhiteSpace(suffix)) return; // trivial + + // sanitize and re-check + suffix = ServerEndPoint.ClientInfoSanitize(suffix ?? "").Trim(); + if (string.IsNullOrWhiteSpace(suffix)) return; // trivial + + lock (_libraryNameSuffixHash) + { + if (!_libraryNameSuffixHash.Add(suffix)) return; // already cited; nothing to do + + _libraryNameSuffixCombined = "-" + string.Join("-", _libraryNameSuffixHash.OrderBy(_ => _)); + } + + // if we get here, we *actually changed something*; we can retroactively fixup the connections + var libName = GetFullLibraryName(); // note this also checks SetClientLibrary + if (string.IsNullOrWhiteSpace(libName) || !CommandMap.IsAvailable(RedisCommand.CLIENT)) return; // disabled on no lib name + + // note that during initial handshake we use raw Message; this is low frequency - no + // concern over overhead of Execute here + var args = new object[] { RedisLiterals.SETINFO, RedisLiterals.lib_name, libName }; + foreach (var server in GetServers()) + { + try + { + // note we can only fixup the *interactive* channel; that's tolerable here + if (server.IsConnected) + { + // best effort only + server.Execute("CLIENT", args, CommandFlags.FireAndForget); + } + } + catch (Exception ex) + { + // if an individual server trips, that's fine - best effort; note we're using + // F+F here anyway, so we don't *expect* any failures + Debug.WriteLine(ex.Message); + } + } + } + + internal string GetFullLibraryName() + { + var config = RawConfig; + if (!config.SetClientLibrary) return ""; // disabled + + var libName = config.LibraryName; + if (string.IsNullOrWhiteSpace(libName)) + { + // defer to provider if missing (note re null vs blank; if caller wants to disable + // it, they should set SetClientLibrary to false, not set the name to empty string) + libName = config.Defaults.LibraryName; + } + + libName = ServerEndPoint.ClientInfoSanitize(libName); + // if no primary name, return nothing, even if suffixes exist + if (string.IsNullOrWhiteSpace(libName)) return ""; + + return libName + Volatile.Read(ref _libraryNameSuffixCombined); + } +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.Profiling.cs b/src/StackExchange.Redis/ConnectionMultiplexer.Profiling.cs new file mode 100644 index 000000000..c60966234 --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.Profiling.cs @@ -0,0 +1,17 @@ +using System; +using StackExchange.Redis.Profiling; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + private Func? _profilingSessionProvider; + + /// + /// Register a callback to provide an on-demand ambient session provider based on the + /// calling context; the implementing code is responsible for reliably resolving the same provider + /// based on ambient context, or returning null to not profile. + /// + /// The session provider to register. + public void RegisterProfiler(Func profilingSessionProvider) => _profilingSessionProvider = profilingSessionProvider; +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.ReaderWriter.cs b/src/StackExchange.Redis/ConnectionMultiplexer.ReaderWriter.cs new file mode 100644 index 000000000..a30da7865 --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.ReaderWriter.cs @@ -0,0 +1,19 @@ +using System.Diagnostics.CodeAnalysis; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + internal SocketManager? SocketManager { get; private set; } + + [MemberNotNull(nameof(SocketManager))] + private void OnCreateReaderWriter(ConfigurationOptions configuration) + { + SocketManager = configuration.SocketManager ?? SocketManager.Shared; + } + + private void OnCloseReaderWriter() + { + SocketManager = null; + } +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs b/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs new file mode 100644 index 000000000..61b36b014 --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs @@ -0,0 +1,467 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Pipelines.Sockets.Unofficial; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + internal EndPoint? currentSentinelPrimaryEndPoint; + internal Timer? sentinelPrimaryReconnectTimer; + internal readonly Dictionary sentinelConnectionChildren = new(); + internal ConnectionMultiplexer? sentinelConnection; + + /// + /// Initializes the connection as a Sentinel connection and adds the necessary event handlers to track changes to the managed primaries. + /// + /// The to log to, if any. + internal void InitializeSentinel(ILogger? log) + { + if (ServerSelectionStrategy.ServerType != ServerType.Sentinel) + { + return; + } + + // Subscribe to sentinel change events + ISubscriber sub = GetSubscriber(); + + if (sub.SubscribedEndpoint(RedisChannel.Literal("+switch-master")) == null) + { + sub.Subscribe( + RedisChannel.Literal("+switch-master"), + (__, message) => + { + string[] messageParts = ((string)message!).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + // We don't care about the result of this - we're just trying + _ = Format.TryParseEndPoint(string.Format("{0}:{1}", messageParts[1], messageParts[2]), out var switchBlame); + + lock (sentinelConnectionChildren) + { + // Switch the primary if we have connections for that service + if (sentinelConnectionChildren.TryGetValue(messageParts[0], out ConnectionMultiplexer? child)) + { + // Is the connection still valid? + if (child.IsDisposed) + { + child.ConnectionFailed -= OnManagedConnectionFailed; + child.ConnectionRestored -= OnManagedConnectionRestored; + sentinelConnectionChildren.Remove(messageParts[0]); + } + else + { + SwitchPrimary(switchBlame, child); + } + } + } + }, + CommandFlags.FireAndForget); + } + + // If we lose connection to a sentinel server, + // we need to reconfigure to make sure we still have a subscription to the +switch-master channel + ConnectionFailed += (sender, e) => + // Reconfigure to get subscriptions back online + ReconfigureAsync(first: false, reconfigureAll: true, log, e.EndPoint, "Lost sentinel connection", false).Wait(); + + // Subscribe to new sentinels being added + if (sub.SubscribedEndpoint(RedisChannel.Literal("+sentinel")) == null) + { + sub.Subscribe( + RedisChannel.Literal("+sentinel"), + (_, message) => + { + string[] messageParts = ((string)message!).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + UpdateSentinelAddressList(messageParts[0]); + }, + CommandFlags.FireAndForget); + } + } + + /// + /// Create a new instance that connects to a Sentinel server. + /// + /// The string configuration to use for this multiplexer. + /// The to log to. + public static ConnectionMultiplexer SentinelConnect(string configuration, TextWriter? log = null) => + SentinelConnect(ConfigurationOptions.Parse(configuration), log); + + /// + /// Create a new instance that connects to a Sentinel server. + /// + /// The string configuration to use for this multiplexer. + /// The to log to. + public static Task SentinelConnectAsync(string configuration, TextWriter? log = null) => + SentinelConnectAsync(ConfigurationOptions.Parse(configuration), log); + + /// + /// Create a new instance that connects to a Sentinel server. + /// + /// The configuration options to use for this multiplexer. + /// The to log to. + public static ConnectionMultiplexer SentinelConnect(ConfigurationOptions configuration, TextWriter? log = null) + { + SocketConnection.AssertDependencies(); + Validate(configuration); + + return ConnectImpl(configuration, log, ServerType.Sentinel); + } + + /// + /// Create a new instance that connects to a Sentinel server. + /// + /// The configuration options to use for this multiplexer. + /// The to log to. + public static Task SentinelConnectAsync(ConfigurationOptions configuration, TextWriter? log = null) + { + SocketConnection.AssertDependencies(); + Validate(configuration); + + return ConnectImplAsync(configuration, log, ServerType.Sentinel); + } + + /// + /// Create a new instance that connects to a sentinel server, discovers the current primary server + /// for the specified in the config and returns a managed connection to the current primary server. + /// + /// The configuration options to use for this multiplexer. + /// The to log to. + private static ConnectionMultiplexer SentinelPrimaryConnect(ConfigurationOptions configuration, TextWriter? log = null) + { + var sentinelConnection = SentinelConnect(configuration, log); + + var muxer = sentinelConnection.GetSentinelMasterConnection(configuration, log); + // Set reference to sentinel connection so that we can dispose it + muxer.sentinelConnection = sentinelConnection; + + return muxer; + } + + /// + /// Create a new instance that connects to a sentinel server, discovers the current primary server + /// for the specified in the config and returns a managed connection to the current primary server. + /// + /// The configuration options to use for this multiplexer. + /// The to log to. + private static async Task SentinelPrimaryConnectAsync(ConfigurationOptions configuration, TextWriter? writer = null) + { + var sentinelConnection = await SentinelConnectAsync(configuration, writer).ForAwait(); + + var muxer = sentinelConnection.GetSentinelMasterConnection(configuration, writer); + // Set reference to sentinel connection so that we can dispose it + muxer.sentinelConnection = sentinelConnection; + + return muxer; + } + + /// + /// Returns a managed connection to the primary server indicated by the in the config. + /// + /// The configuration to be used when connecting to the primary. + /// The writer to log to, if any. + public ConnectionMultiplexer GetSentinelMasterConnection(ConfigurationOptions config, TextWriter? log = null) + { + if (ServerSelectionStrategy.ServerType != ServerType.Sentinel) + { + throw new RedisConnectionException( + ConnectionFailureType.UnableToConnect, + "Sentinel: The ConnectionMultiplexer is not a Sentinel connection. Detected as: " + ServerSelectionStrategy.ServerType); + } + + var serviceName = config.ServiceName; + if (serviceName.IsNullOrEmpty()) + { + throw new ArgumentException("A ServiceName must be specified."); + } + + lock (sentinelConnectionChildren) + { + if (sentinelConnectionChildren.TryGetValue(serviceName, out var sentinelConnectionChild) && !sentinelConnectionChild.IsDisposed) + return sentinelConnectionChild; + } + + bool success = false; + ConnectionMultiplexer? connection = null; + EndPointCollection? endpoints = null; + + var sw = ValueStopwatch.StartNew(); + do + { + // Sentinel has some fun race behavior internally - give things a few shots for a quicker overall connect. + const int queryAttempts = 2; + + EndPoint? newPrimaryEndPoint = null; + for (int i = 0; i < queryAttempts && newPrimaryEndPoint is null; i++) + { + newPrimaryEndPoint = GetConfiguredPrimaryForService(serviceName); + } + + if (newPrimaryEndPoint is null) + { + throw new RedisConnectionException( + ConnectionFailureType.UnableToConnect, + $"Sentinel: Failed connecting to configured primary for service: {config.ServiceName}"); + } + + EndPoint[]? replicaEndPoints = null; + for (int i = 0; i < queryAttempts && replicaEndPoints is null; i++) + { + replicaEndPoints = GetReplicasForService(serviceName); + } + + endpoints = config.EndPoints.Clone(); + + // Replace the primary endpoint, if we found another one + // If not, assume the last state is the best we have and minimize the race + if (endpoints.Count == 1) + { + endpoints[0] = newPrimaryEndPoint; + } + else + { + endpoints.Clear(); + endpoints.TryAdd(newPrimaryEndPoint); + } + + if (replicaEndPoints is not null) + { + foreach (var replicaEndPoint in replicaEndPoints) + { + endpoints.TryAdd(replicaEndPoint); + } + } + + connection = ConnectImpl(config, log, endpoints: endpoints); + + // verify role is primary according to: + // https://redis.io/topics/sentinel-clients + if (connection.GetServer(newPrimaryEndPoint)?.Role()?.Value == RedisLiterals.master) + { + success = true; + break; + } + + Thread.Sleep(100); + } + while (sw.ElapsedMilliseconds < config.ConnectTimeout); + + if (!success) + { + throw new RedisConnectionException( + ConnectionFailureType.UnableToConnect, + $"Sentinel: Failed connecting to configured primary for service: {config.ServiceName}"); + } + + // Attach to reconnect event to ensure proper connection to the new primary + connection.ConnectionRestored += OnManagedConnectionRestored; + + // If we lost the connection, run a switch to a least try and get updated info about the primary + connection.ConnectionFailed += OnManagedConnectionFailed; + + lock (sentinelConnectionChildren) + { + sentinelConnectionChildren[serviceName] = connection; + } + + // Perform the initial switchover + SwitchPrimary(endpoints[0], connection, log); + + return connection; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1075:Avoid empty catch clause that catches System.Exception.", Justification = "We don't care.")] + internal void OnManagedConnectionRestored(object? sender, ConnectionFailedEventArgs e) + { + if (sender is not ConnectionMultiplexer connection) + { + return; // This should never happen - called from non-nullable ConnectionFailedEventArgs + } + + var oldTimer = Interlocked.Exchange(ref connection.sentinelPrimaryReconnectTimer, null); + oldTimer?.Dispose(); + + try + { + // Run a switch to make sure we have update-to-date + // information about which primary we should connect to + SwitchPrimary(e.EndPoint, connection); + + try + { + // Verify that the reconnected endpoint is a primary, + // and the correct one otherwise we should reconnect + if (connection.GetServer(e.EndPoint).IsReplica || e.EndPoint != connection.currentSentinelPrimaryEndPoint) + { + // This isn't a primary, so try connecting again + SwitchPrimary(e.EndPoint, connection); + } + } + catch (Exception) + { + // If we get here it means that we tried to reconnect to a server that is no longer + // considered a primary by Sentinel and was removed from the list of endpoints. + + // If we caught an exception, we may have gotten a stale endpoint + // we are not aware of, so retry + SwitchPrimary(e.EndPoint, connection); + } + } + catch (Exception) + { + // Log, but don't throw in an event handler + // TODO: Log via new event handler? a la ConnectionFailed? + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1075:Avoid empty catch clause that catches System.Exception.", Justification = "We don't care.")] + internal void OnManagedConnectionFailed(object? sender, ConnectionFailedEventArgs e) + { + if (sender is not ConnectionMultiplexer connection) + { + return; // This should never happen - called from non-nullable ConnectionFailedEventArgs + } + + // Periodically check to see if we can reconnect to the proper primary. + // This is here in case we lost our subscription to a good sentinel instance + // or if we miss the published primary change. + if (connection.sentinelPrimaryReconnectTimer == null) + { + connection.sentinelPrimaryReconnectTimer = new Timer( + _ => + { + try + { + // Attempt, but do not fail here + SwitchPrimary(e.EndPoint, connection); + } + catch (Exception) + { + } + finally + { + try + { + connection.sentinelPrimaryReconnectTimer?.Change(TimeSpan.FromSeconds(1), Timeout.InfiniteTimeSpan); + } + catch (ObjectDisposedException) + { + // If we get here the managed connection was restored and the timer was + // disposed by another thread, so there's no need to run the timer again. + } + } + }, + null, + TimeSpan.Zero, + Timeout.InfiniteTimeSpan); + } + } + + internal EndPoint? GetConfiguredPrimaryForService(string serviceName) => + _serverSnapshot // same as GetServerSnapshot, but without forcing span + .Where(static s => s.ServerType == ServerType.Sentinel) + .AsParallel() + .Select(s => + { + try { return GetServer(s.EndPoint).SentinelGetMasterAddressByName(serviceName); } + catch { return null; } + }) + .FirstOrDefault(r => r != null); + + internal EndPoint[]? GetReplicasForService(string serviceName) => + _serverSnapshot // same as GetServerSnapshot, but without forcing span + .Where(static s => s.ServerType == ServerType.Sentinel) + .AsParallel() + .Select(s => + { + try { return GetServer(s.EndPoint).SentinelGetReplicaAddresses(serviceName); } + catch { return null; } + }) + .FirstOrDefault(r => r != null); + + /// + /// Switches the SentinelMasterConnection over to a new primary. + /// + /// The endpoint responsible for the switch. + /// The connection that should be switched over to a new primary endpoint. + /// The writer to log to, if any. + internal void SwitchPrimary(EndPoint? switchBlame, ConnectionMultiplexer connection, TextWriter? writer = null) + { + var logger = Logger.With(writer); + if (connection.RawConfig.ServiceName is not string serviceName) + { + logger?.LogInformationServiceNameNotDefined(); + return; + } + + // Get new primary - try twice + EndPoint newPrimaryEndPoint = GetConfiguredPrimaryForService(serviceName) + ?? GetConfiguredPrimaryForService(serviceName) + ?? throw new RedisConnectionException(ConnectionFailureType.UnableToConnect, $"Sentinel: Failed connecting to switch primary for service: {serviceName}"); + + connection.currentSentinelPrimaryEndPoint = newPrimaryEndPoint; + + if (!connection.servers.Contains(newPrimaryEndPoint)) + { + EndPoint[]? replicaEndPoints = GetReplicasForService(serviceName) + ?? GetReplicasForService(serviceName); + + connection.servers.Clear(); + connection.EndPoints.Clear(); + connection.EndPoints.TryAdd(newPrimaryEndPoint); + if (replicaEndPoints is not null) + { + foreach (var replicaEndPoint in replicaEndPoints) + { + connection.EndPoints.TryAdd(replicaEndPoint); + } + } + Trace($"Switching primary to {newPrimaryEndPoint}"); + // Trigger a reconfigure + connection.ReconfigureAsync( + first: false, + reconfigureAll: false, + log: logger, + blame: switchBlame, + cause: $"Primary switch {serviceName}", + publishReconfigure: false, + publishReconfigureFlags: CommandFlags.PreferMaster).Wait(); + + UpdateSentinelAddressList(serviceName); + } + } + + internal void UpdateSentinelAddressList(string serviceName) + { + var firstCompleteRequest = _serverSnapshot // same as GetServerSnapshot, but without forcing span + .Where(static s => s.ServerType == ServerType.Sentinel) + .AsParallel() + .Select(s => + { + try { return GetServer(s.EndPoint).SentinelGetSentinelAddresses(serviceName); } + catch { return null; } + }) + .FirstOrDefault(r => r != null); + + // Ignore errors, as having an updated sentinel list is not essential + if (firstCompleteRequest == null) + return; + + bool hasNew = false; + foreach (EndPoint newSentinel in firstCompleteRequest.Where(x => !EndPoints.Contains(x))) + { + hasNew = true; + EndPoints.TryAdd(newSentinel); + } + + if (hasNew) + { + // Reconfigure the sentinel multiplexer if we added new endpoints + ReconfigureAsync(first: false, reconfigureAll: true, Logger, EndPoints[0], "Updating Sentinel List", false).Wait(); + } + } +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.StormLog.cs b/src/StackExchange.Redis/ConnectionMultiplexer.StormLog.cs new file mode 100644 index 000000000..51c62d00e --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.StormLog.cs @@ -0,0 +1,29 @@ +using System.Threading; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + internal int haveStormLog = 0; + internal string? stormLogSnapshot; + + /// + /// Limit at which to start recording unusual busy patterns (only one log will be retained at a time). + /// Set to a negative value to disable this feature. + /// + public int StormLogThreshold { get; set; } = 15; + + /// + /// Obtains the log of unusual busy patterns. + /// + public string? GetStormLog() => Volatile.Read(ref stormLogSnapshot); + + /// + /// Resets the log of unusual busy patterns. + /// + public void ResetStormLog() + { + Interlocked.Exchange(ref stormLogSnapshot, null); + Interlocked.Exchange(ref haveStormLog, 0); + } +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.Threading.cs b/src/StackExchange.Redis/ConnectionMultiplexer.Threading.cs new file mode 100644 index 000000000..a4ad1a025 --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.Threading.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading; +using Pipelines.Sockets.Unofficial; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + private static readonly WaitCallback s_CompleteAsWorker = s => ((ICompletable)s!).TryComplete(true); + internal static void CompleteAsWorker(ICompletable completable) + { + if (completable is not null) + { + ThreadPool.QueueUserWorkItem(s_CompleteAsWorker, completable); + } + } + + internal static bool TryCompleteHandler(EventHandler? handler, object sender, T args, bool isAsync) where T : EventArgs, ICompletable + { + if (handler is null) return true; + if (isAsync) + { + if (handler.IsSingle()) + { + try + { + handler(sender, args); + } + catch { } + } + else + { + foreach (EventHandler sub in handler.AsEnumerable()) + { + try + { + sub(sender, args); + } + catch { } + } + } + return true; + } + else + { + return false; + } + } +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.Verbose.cs b/src/StackExchange.Redis/ConnectionMultiplexer.Verbose.cs new file mode 100644 index 000000000..e4746963b --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.Verbose.cs @@ -0,0 +1,58 @@ +using System; +using System.Diagnostics; +using System.Net; +using System.Runtime.CompilerServices; + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + internal event Action? MessageFaulted; + internal event Action? Closing; + internal event Action? PreTransactionExec, TransactionLog, InfoMessage; + internal event Action? Connecting; + internal event Action? Resurrecting; + + partial void OnTrace(string message, string? category); + static partial void OnTraceWithoutContext(string message, string? category); + + [Conditional("VERBOSE")] + internal void Trace(string message, [CallerMemberName] string? category = null) => OnTrace(message, category); + + [Conditional("VERBOSE")] + internal void Trace(bool condition, string message, [CallerMemberName] string? category = null) + { + if (condition) OnTrace(message, category); + } + + [Conditional("VERBOSE")] + internal static void TraceWithoutContext(string message, [CallerMemberName] string? category = null) => OnTraceWithoutContext(message, category); + + [Conditional("VERBOSE")] + internal static void TraceWithoutContext(bool condition, string message, [CallerMemberName] string? category = null) + { + if (condition) OnTraceWithoutContext(message, category); + } + + [Conditional("VERBOSE")] + internal void OnMessageFaulted(Message? msg, Exception? fault, [CallerMemberName] string? origin = default, [CallerFilePath] string? path = default, [CallerLineNumber] int lineNumber = default) => + MessageFaulted?.Invoke(msg?.CommandAndKey, fault, $"{origin} ({path}#{lineNumber})"); + + [Conditional("VERBOSE")] + internal void OnInfoMessage(string message) => InfoMessage?.Invoke(message); + + [Conditional("VERBOSE")] + internal void OnClosing(bool complete) => Closing?.Invoke(complete); + + [Conditional("VERBOSE")] + internal void OnConnecting(EndPoint endpoint, ConnectionType connectionType) => Connecting?.Invoke(endpoint, connectionType); + + [Conditional("VERBOSE")] + internal void OnResurrecting(EndPoint endpoint, ConnectionType connectionType) => Resurrecting?.Invoke(endpoint, connectionType); + + [Conditional("VERBOSE")] + internal void OnPreTransactionExec(Message message) => PreTransactionExec?.Invoke(message.CommandAndKey); + + [Conditional("VERBOSE")] + internal void OnTransactionLog(string message) => TransactionLog?.Invoke(message); +} diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.cs b/src/StackExchange.Redis/ConnectionMultiplexer.cs new file mode 100644 index 000000000..cc766338a --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.cs @@ -0,0 +1,2358 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Net; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Pipelines.Sockets.Unofficial; +using StackExchange.Redis.Profiling; + +namespace StackExchange.Redis +{ + /// + /// Represents an inter-related group of connections to redis servers. + /// A reference to this should be held and re-used. + /// + /// + public sealed partial class ConnectionMultiplexer : IInternalConnectionMultiplexer // implies : IConnectionMultiplexer and : IDisposable + { + // This gets accessed for every received event; let's make sure we can process it "raw" + internal readonly byte[]? ConfigurationChangedChannel; + // Unique identifier used when tracing + internal readonly byte[] UniqueId = Guid.NewGuid().ToByteArray(); + + /// + /// Tracks overall connection multiplexer counts. + /// + internal int _connectAttemptCount = 0, _connectCompletedCount = 0, _connectionCloseCount = 0; + internal long syncOps, asyncOps; + private long syncTimeouts, fireAndForgets, asyncTimeouts; + private string? failureMessage, activeConfigCause; + private TimerToken? pulse; + + private readonly Hashtable servers = new Hashtable(); + private volatile ServerSnapshot _serverSnapshot = ServerSnapshot.Empty; + + private volatile bool _isDisposed; + internal bool IsDisposed => _isDisposed; + internal ILogger? Logger { get; } + + internal CommandMap CommandMap { get; } + internal EndPointCollection EndPoints { get; } + internal ConfigurationOptions RawConfig { get; } + internal ServerSelectionStrategy ServerSelectionStrategy { get; } + ServerSelectionStrategy IInternalConnectionMultiplexer.ServerSelectionStrategy => ServerSelectionStrategy; + ConnectionMultiplexer IInternalConnectionMultiplexer.UnderlyingMultiplexer => this; + + internal Exception? LastException { get; set; } + + ConfigurationOptions IInternalConnectionMultiplexer.RawConfig => RawConfig; + + private int lastReconfigiureTicks = Environment.TickCount; + internal long LastReconfigureSecondsAgo => + unchecked(Environment.TickCount - Volatile.Read(ref lastReconfigiureTicks)) / 1000; + + private int _activeHeartbeatErrors, lastHeartbeatTicks; + internal long LastHeartbeatSecondsAgo => + pulse is null + ? -1 + : unchecked(Environment.TickCount - Volatile.Read(ref lastHeartbeatTicks)) / 1000; + + private static int lastGlobalHeartbeatTicks = Environment.TickCount; + internal static long LastGlobalHeartbeatSecondsAgo => + unchecked(Environment.TickCount - Volatile.Read(ref lastGlobalHeartbeatTicks)) / 1000; + + /// + [Obsolete($"Please use {nameof(ConfigurationOptions)}.{nameof(ConfigurationOptions.IncludeDetailInExceptions)} instead - this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public bool IncludeDetailInExceptions + { + get => RawConfig.IncludeDetailInExceptions; + set => RawConfig.IncludeDetailInExceptions = value; + } + + /// + [Obsolete($"Please use {nameof(ConfigurationOptions)}.{nameof(ConfigurationOptions.IncludePerformanceCountersInExceptions)} instead - this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public bool IncludePerformanceCountersInExceptions + { + get => RawConfig.IncludePerformanceCountersInExceptions; + set => RawConfig.IncludePerformanceCountersInExceptions = value; + } + + /// + /// Gets the synchronous timeout associated with the connections. + /// + public int TimeoutMilliseconds => RawConfig.SyncTimeout; + + /// + /// Gets the asynchronous timeout associated with the connections. + /// + internal int AsyncTimeoutMilliseconds => RawConfig.AsyncTimeout; + + /// + /// Gets the client-name that will be used on all new connections. + /// + /// + /// We null coalesce here instead of in Options so that we don't populate it everywhere (e.g. .ToString()), given it's a default. + /// + public string ClientName => RawConfig.ClientName ?? RawConfig.Defaults.ClientName; + + /// + /// Gets the configuration of the connection. + /// + public string Configuration => RawConfig.ToString(); + + /// + /// Indicates whether any servers are connected. + /// + public bool IsConnected => _serverSnapshot.Any(static s => s.IsConnected); + + /// + /// Indicates whether any servers are currently trying to connect. + /// + public bool IsConnecting => _serverSnapshot.Any(static s => s.IsConnecting); + + static ConnectionMultiplexer() + { + SetAutodetectFeatureFlags(); + } + + private ConnectionMultiplexer(ConfigurationOptions configuration, ServerType? serverType = null, EndPointCollection? endpoints = null) + { + RawConfig = configuration ?? throw new ArgumentNullException(nameof(configuration)); + EndPoints = endpoints ?? RawConfig.EndPoints.Clone(); + EndPoints.SetDefaultPorts(serverType, ssl: RawConfig.Ssl); + Logger = configuration.LoggerFactory?.CreateLogger(); + + var map = CommandMap = configuration.GetCommandMap(serverType); + if (!string.IsNullOrWhiteSpace(configuration.Password) && !configuration.TryResp3()) // RESP3 doesn't need AUTH (can issue as part of HELLO) + { + map.AssertAvailable(RedisCommand.AUTH); + } + if (!map.IsAvailable(RedisCommand.ECHO) && !map.IsAvailable(RedisCommand.PING) && !map.IsAvailable(RedisCommand.TIME)) + { + // I mean really, give me a CHANCE! I need *something* to check the server is available to me... + // see also: SendTracer (matching logic) + map.AssertAvailable(RedisCommand.EXISTS); + } + + OnCreateReaderWriter(configuration); + ServerSelectionStrategy = new ServerSelectionStrategy(this); + + var configChannel = configuration.ConfigurationChannel; + if (!string.IsNullOrWhiteSpace(configChannel)) + { + ConfigurationChangedChannel = Encoding.UTF8.GetBytes(configChannel); + } + lastHeartbeatTicks = Environment.TickCount; + } + + private static ConnectionMultiplexer CreateMultiplexer(ConfigurationOptions configuration, ILogger? log, ServerType? serverType, out EventHandler? connectHandler, EndPointCollection? endpoints = null) + { + var muxer = new ConnectionMultiplexer(configuration, serverType, endpoints); + connectHandler = null; + if (log is not null) + { + // Create a detachable event-handler to log detailed errors if something happens during connect/handshake + connectHandler = (_, a) => + { + try + { + lock (log) // Keep the outer and any inner errors contiguous + { + var ex = a.Exception; + log?.LogErrorConnectionFailed(ex, new(a.EndPoint), a.ConnectionType, a.FailureType, ex?.Message ?? "(unknown)"); + while ((ex = ex?.InnerException) != null) + { + log?.LogErrorInnerException(ex, ex.Message); + } + } + } + catch { } + }; + muxer.ConnectionFailed += connectHandler; + } + return muxer; + } + + /// + /// Get summary statistics associated with all servers in this multiplexer. + /// + public ServerCounters GetCounters() + { + var counters = new ServerCounters(null); + var snapshot = GetServerSnapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + counters.Add(snapshot[i].GetCounters()); + } + return counters; + } + + internal async Task MakePrimaryAsync(ServerEndPoint server, ReplicationChangeOptions options, TextWriter? writer) + { + _ = server ?? throw new ArgumentNullException(nameof(server)); + var log = Logger.With(writer); + + var cmd = server.GetFeatures().ReplicaCommands ? RedisCommand.REPLICAOF : RedisCommand.SLAVEOF; + CommandMap.AssertAvailable(cmd); + + if (!RawConfig.AllowAdmin) + { + throw ExceptionFactory.AdminModeNotEnabled(RawConfig.IncludeDetailInExceptions, cmd, null, server); + } + var srv = server.GetRedisServer(null); + if (!srv.IsConnected) + { + throw ExceptionFactory.NoConnectionAvailable(this, null, server, GetServerSnapshot(), command: cmd); + } + + const CommandFlags flags = CommandFlags.NoRedirect; + Message msg; + + log?.LogInformationCheckingServerAvailable(new(srv.EndPoint)); + try + { + await srv.PingAsync(flags).ForAwait(); // if it isn't happy, we're not happy + } + catch (Exception ex) + { + log?.LogErrorOperationFailedOnServer(ex, new(srv.EndPoint), ex.Message); + throw; + } + + var nodes = _serverSnapshot; // same as GetServerSnapshot(), but doesn't force span + RedisValue newPrimary = Format.ToString(server.EndPoint); + + // try and write this everywhere; don't worry if some folks reject our advances + if (RawConfig.TryGetTieBreaker(out var tieBreakerKey) + && options.HasFlag(ReplicationChangeOptions.SetTiebreaker) + && CommandMap.IsAvailable(RedisCommand.SET)) + { + foreach (var node in nodes) + { + if (!node.IsConnected || node.IsReplica) continue; + log?.LogInformationAttemptingToSetTieBreaker(new(node.EndPoint)); + msg = Message.Create(0, flags | CommandFlags.FireAndForget, RedisCommand.SET, tieBreakerKey, newPrimary); + try + { + await node.WriteDirectAsync(msg, ResultProcessor.DemandOK).ForAwait(); + } + catch { } + } + } + + // stop replicating, promote to a standalone primary + log?.LogInformationMakingServerPrimary(new(srv.EndPoint)); + try + { + await srv.ReplicaOfAsync(null, flags).ForAwait(); + } + catch (Exception ex) + { + log?.LogErrorOperationFailedOnServer(ex, new(srv.EndPoint), ex.Message); + throw; + } + + // also, in case it was a replica a moment ago, and hasn't got the tie-breaker yet, we re-send the tie-breaker to this one + if (!tieBreakerKey.IsNull && !server.IsReplica) + { + log?.LogInformationResendingTieBreaker(new(server.EndPoint)); + msg = Message.Create(0, flags | CommandFlags.FireAndForget, RedisCommand.SET, tieBreakerKey, newPrimary); + try + { + await server.WriteDirectAsync(msg, ResultProcessor.DemandOK).ForAwait(); + } + catch { } + } + + // There's an inherent race here in zero-latency environments (e.g. when Redis is on localhost) when a broadcast is specified + // The broadcast can get back from redis and trigger a reconfigure before we get a chance to get to ReconfigureAsync() below + // This results in running an outdated reconfiguration and the .CompareExchange() (due to already running a reconfiguration) + // failing...making our needed reconfiguration a no-op. + // If we don't block *that* run, then *our* run (at low latency) gets blocked. Then we're waiting on the + // ConfigurationOptions.ConfigCheckSeconds interval to identify the current (created by this method call) topology correctly. + var blockingReconfig = Interlocked.CompareExchange(ref activeConfigCause, "Block: Pending Primary Reconfig", null) == null; + + // Try and broadcast the fact a change happened to all members + // We want everyone possible to pick it up. + // We broadcast before *and after* the change to remote members, so that they don't go without detecting a change happened. + // This eliminates the race of pub/sub *then* re-slaving happening, since a method both precedes and follows. + async Task BroadcastAsync(ServerSnapshot serverNodes) + { + if (options.HasFlag(ReplicationChangeOptions.Broadcast) + && ConfigurationChangedChannel != null + && CommandMap.IsAvailable(RedisCommand.PUBLISH)) + { + RedisValue channel = ConfigurationChangedChannel; + foreach (var node in serverNodes) + { + if (!node.IsConnected) continue; + log?.LogInformationBroadcastingViaNode(new(node.EndPoint)); + msg = Message.Create(-1, flags | CommandFlags.FireAndForget, RedisCommand.PUBLISH, channel, newPrimary); + await node.WriteDirectAsync(msg, ResultProcessor.Int64).ForAwait(); + } + } + } + + // Send a message before it happens - because afterwards a new replica may be unresponsive + await BroadcastAsync(nodes).ForAwait(); + + if (options.HasFlag(ReplicationChangeOptions.ReplicateToOtherEndpoints)) + { + foreach (var node in nodes) + { + if (node == server || node.ServerType != ServerType.Standalone) continue; + + log?.LogInformationReplicatingToNode(new(node.EndPoint)); + msg = RedisServer.CreateReplicaOfMessage(node, server.EndPoint, flags); + await node.WriteDirectAsync(msg, ResultProcessor.DemandOK).ForAwait(); + } + } + + // ...and send one after it happens - because the first broadcast may have landed on a secondary client + // and it can reconfigure before any topology change actually happened. This is most likely to happen + // in low-latency environments. + await BroadcastAsync(nodes).ForAwait(); + + // and reconfigure the muxer + log?.LogInformationReconfiguringAllEndpoints(); + // Yes, there is a tiny latency race possible between this code and the next call, but it's far more minute than before. + // The effective gap between 0 and > 0 (likely off-box) latency is something that may never get hit here by anyone. + if (blockingReconfig) + { + Interlocked.Exchange(ref activeConfigCause, null); + } + if (!await ReconfigureAsync(first: false, reconfigureAll: true, log, srv.EndPoint, cause: nameof(MakePrimaryAsync)).ForAwait()) + { + log?.LogInformationVerifyingConfigurationIncomplete(); + } + } + + internal void CheckMessage(Message message) + { + if (!RawConfig.AllowAdmin && message.IsAdmin) + { + throw ExceptionFactory.AdminModeNotEnabled(RawConfig.IncludeDetailInExceptions, message.Command, message, null); + } + if (message.Command != RedisCommand.UNKNOWN) + { + CommandMap.AssertAvailable(message.Command); + } + + // using >= here because we will be adding 1 for the command itself (which is an argument for the purposes of the multi-bulk protocol) + if (message.ArgCount >= PhysicalConnection.REDIS_MAX_ARGS) + { + throw ExceptionFactory.TooManyArgs(message.CommandAndKey, message.ArgCount); + } + } + + internal bool TryResend(int hashSlot, Message message, EndPoint endpoint, bool isMoved) + { + // If we're being told to re-send something because the hash slot moved, that means our topology is out of date + // ...and we should re-evaluate what's what. + // Allow for a 5-second back-off so we don't hammer this in a loop though + if (isMoved && LastReconfigureSecondsAgo > 5) + { + // Async kickoff a reconfigure + ReconfigureIfNeeded(endpoint, false, "MOVED encountered"); + } + + return ServerSelectionStrategy.TryResend(hashSlot, message, endpoint, isMoved); + } + + /// + /// Wait for a given asynchronous operation to complete (or timeout). + /// + /// The task to wait on. + public void Wait(Task task) + { + _ = task ?? throw new ArgumentNullException(nameof(task)); + try + { + if (!task.Wait(TimeoutMilliseconds)) + { + throw new TimeoutException(); + } + } + catch (AggregateException aex) when (IsSingle(aex)) + { + throw aex.InnerExceptions[0]; + } + } + + /// + /// Wait for a given asynchronous operation to complete (or timeout). + /// + /// The type contains in the task to wait on. + /// The task to wait on. + public T Wait(Task task) + { + _ = task ?? throw new ArgumentNullException(nameof(task)); + try + { + if (!task.Wait(TimeoutMilliseconds)) + { + throw new TimeoutException(); + } + } + catch (AggregateException aex) when (IsSingle(aex)) + { + throw aex.InnerExceptions[0]; + } + return task.Result; + } + + private static bool IsSingle(AggregateException aex) + { + try + { + return aex?.InnerExceptions.Count == 1; + } + catch + { + return false; + } + } + + /// + /// Wait for the given asynchronous operations to complete (or timeout). + /// + /// The tasks to wait on. + public void WaitAll(params Task[] tasks) + { + _ = tasks ?? throw new ArgumentNullException(nameof(tasks)); + if (tasks.Length == 0) return; + if (!Task.WaitAll(tasks, TimeoutMilliseconds)) + { + throw new TimeoutException(); + } + } + + private bool WaitAllIgnoreErrors(Task[] tasks) + { + _ = tasks ?? throw new ArgumentNullException(nameof(tasks)); + if (tasks.Length == 0) return true; + var watch = ValueStopwatch.StartNew(); + try + { + // If no error, great + if (Task.WaitAll(tasks, TimeoutMilliseconds)) return true; + } + catch + { } + // If we get problems, need to give the non-failing ones time to be fair and reasonable + for (int i = 0; i < tasks.Length; i++) + { + var task = tasks[i]; + if (!task.IsCanceled && !task.IsCompleted && !task.IsFaulted) + { + var remaining = TimeoutMilliseconds - watch.ElapsedMilliseconds; + if (remaining <= 0) return false; + try + { + task.Wait(remaining); + } + catch + { } + } + } + return false; + } + + private static async Task WaitAllIgnoreErrorsAsync(string name, Task[] tasks, int timeoutMilliseconds, ILogger? log, [CallerMemberName] string? caller = null, [CallerLineNumber] int callerLineNumber = 0) + { + _ = tasks ?? throw new ArgumentNullException(nameof(tasks)); + if (tasks.Length == 0) + { + log?.LogInformationNoTasksToAwait(); + return true; + } + if (AllComplete(tasks)) + { + log?.LogInformationAllTasksComplete(); + return true; + } + + var watch = ValueStopwatch.StartNew(); + log?.LogWithThreadPoolStats($"Awaiting {tasks.Length} {name} task completion(s) for {timeoutMilliseconds}ms"); + try + { + // if none error, great + var remaining = timeoutMilliseconds - watch.ElapsedMilliseconds; + if (remaining <= 0) + { + log.LogWithThreadPoolStats("Timeout before awaiting for tasks"); + return false; + } + + var allTasks = Task.WhenAll(tasks).ObserveErrors(); + bool all = await allTasks.TimeoutAfter(timeoutMs: remaining).ObserveErrors().ForAwait(); + log?.LogWithThreadPoolStats(all ? $"All {tasks.Length} {name} tasks completed cleanly" : $"Not all {name} tasks completed cleanly (from {caller}#{callerLineNumber}, timeout {timeoutMilliseconds}ms)"); + return all; + } + catch + { } + + // if we get problems, need to give the non-failing ones time to finish + // to be fair and reasonable + for (int i = 0; i < tasks.Length; i++) + { + var task = tasks[i]; + if (!task.IsCanceled && !task.IsCompleted && !task.IsFaulted) + { + var remaining = timeoutMilliseconds - watch.ElapsedMilliseconds; + if (remaining <= 0) + { + log.LogWithThreadPoolStats("Timeout awaiting tasks"); + return false; + } + try + { + await task.TimeoutAfter(remaining).ObserveErrors().ForAwait(); + } + catch + { } + } + } + log.LogWithThreadPoolStats("Finished awaiting tasks"); + return false; + } + + private static bool AllComplete(Task[] tasks) + { + for (int i = 0; i < tasks.Length; i++) + { + var task = tasks[i]; + if (!task.IsCanceled && !task.IsCompleted && !task.IsFaulted) + return false; + } + return true; + } + + internal Exception? AuthException { get; private set; } + internal void SetAuthSuspect(Exception authException) => AuthException ??= authException; + + /// + /// Creates a new instance. + /// + /// The string configuration to use for this multiplexer. + /// The to log to. + public static Task ConnectAsync(string configuration, TextWriter? log = null) => + ConnectAsync(ConfigurationOptions.Parse(configuration), log); + + /// + /// Creates a new instance. + /// + /// The string configuration to use for this multiplexer. + /// Action to further modify the parsed configuration options. + /// The to log to. + public static Task ConnectAsync(string configuration, Action configure, TextWriter? log = null) => + ConnectAsync(ConfigurationOptions.Parse(configuration).Apply(configure), log); + + /// + /// Creates a new instance. + /// + /// The configuration options to use for this multiplexer. + /// The to log to. + /// Note: For Sentinel, do not specify a - this is handled automatically. + public static Task ConnectAsync(ConfigurationOptions configuration, TextWriter? log = null) + { + SocketConnection.AssertDependencies(); + Validate(configuration); + + return configuration.IsSentinel + ? SentinelPrimaryConnectAsync(configuration, log) + : ConnectImplAsync(configuration, log); + } + + private static async Task ConnectImplAsync(ConfigurationOptions configuration, TextWriter? writer = null, ServerType? serverType = null) + { + IDisposable? killMe = null; + EventHandler? connectHandler = null; + ConnectionMultiplexer? muxer = null; + var configLogger = configuration.LoggerFactory?.CreateLogger(); + var log = configLogger.With(writer); + try + { + var sw = ValueStopwatch.StartNew(); + log?.LogInformationConnectingAsync(RuntimeInformation.FrameworkDescription, Utils.GetLibVersion()); + + muxer = CreateMultiplexer(configuration, log, serverType, out connectHandler); + killMe = muxer; + Interlocked.Increment(ref muxer._connectAttemptCount); + bool configured = await muxer.ReconfigureAsync(first: true, reconfigureAll: false, log, null, "connect").ObserveErrors().ForAwait(); + if (!configured) + { + throw ExceptionFactory.UnableToConnect(muxer, muxer.failureMessage); + } + killMe = null; + Interlocked.Increment(ref muxer._connectCompletedCount); + + if (muxer.ServerSelectionStrategy.ServerType == ServerType.Sentinel) + { + // Initialize the Sentinel handlers + muxer.InitializeSentinel(log); + } + + await configuration.AfterConnectAsync(muxer, s => log?.LogInformationAfterConnect(s)).ForAwait(); + + log?.LogInformationTotalConnectTime(sw.ElapsedMilliseconds); + + return muxer; + } + finally + { + if (connectHandler != null && muxer != null) muxer.ConnectionFailed -= connectHandler; + if (killMe != null) try { killMe.Dispose(); } catch { } + if (log is TextWriterLogger twLogger) twLogger.Release(); + } + } + + private static void Validate([NotNull] ConfigurationOptions? config) + { + if (config is null) + { + throw new ArgumentNullException(nameof(config)); + } + if (config.EndPoints.Count == 0) + { + throw new ArgumentException("No endpoints specified", nameof(config)); + } + } + + /// + /// Creates a new instance. + /// + /// The string configuration to use for this multiplexer. + /// The to log to. + public static ConnectionMultiplexer Connect(string configuration, TextWriter? log = null) => + Connect(ConfigurationOptions.Parse(configuration), log); + + /// + /// Creates a new instance. + /// + /// The string configuration to use for this multiplexer. + /// Action to further modify the parsed configuration options. + /// The to log to. + public static ConnectionMultiplexer Connect(string configuration, Action configure, TextWriter? log = null) => + Connect(ConfigurationOptions.Parse(configuration).Apply(configure), log); + + /// + /// Creates a new instance. + /// + /// The configuration options to use for this multiplexer. + /// The to log to. + /// Note: For Sentinel, do not specify a - this is handled automatically. + public static ConnectionMultiplexer Connect(ConfigurationOptions configuration, TextWriter? log = null) + { + SocketConnection.AssertDependencies(); + Validate(configuration); + + return configuration.IsSentinel + ? SentinelPrimaryConnect(configuration, log) + : ConnectImpl(configuration, log); + } + + private static ConnectionMultiplexer ConnectImpl(ConfigurationOptions configuration, TextWriter? writer, ServerType? serverType = null, EndPointCollection? endpoints = null) + { + IDisposable? killMe = null; + EventHandler? connectHandler = null; + ConnectionMultiplexer? muxer = null; + var configLogger = configuration.LoggerFactory?.CreateLogger(); + var log = configLogger.With(writer); + try + { + var sw = ValueStopwatch.StartNew(); + log?.LogInformationConnectingSync(RuntimeInformation.FrameworkDescription, Utils.GetLibVersion()); + + muxer = CreateMultiplexer(configuration, log, serverType, out connectHandler, endpoints); + killMe = muxer; + Interlocked.Increment(ref muxer._connectAttemptCount); + // note that task has timeouts internally, so it might take *just over* the regular timeout + var task = muxer.ReconfigureAsync(first: true, reconfigureAll: false, log, null, "connect"); + + if (task.Wait(muxer.SyncConnectTimeout(true))) + { + // completed promptly - we can check the outcome; hard failures + // (such as password problems) should be reported promptly - it + // won't magically start working + if (!task.Result) throw ExceptionFactory.UnableToConnect(muxer, muxer.failureMessage); + } + else + { + // incomplete - most likely slow initial connection; optionally + // allow a soft failure mode + task.ObserveErrors(); + if (muxer.RawConfig.AbortOnConnectFail) + { + throw ExceptionFactory.UnableToConnect(muxer, "ConnectTimeout"); + } + else + { + var ex = ExceptionFactory.UnableToConnect(muxer, "ConnectTimeout"); + muxer.LastException = ex; + muxer.Logger?.LogErrorSyncConnectTimeout(ex, ex.Message); + } + } + + killMe = null; + Interlocked.Increment(ref muxer._connectCompletedCount); + + if (muxer.ServerSelectionStrategy.ServerType == ServerType.Sentinel) + { + // Initialize the Sentinel handlers + muxer.InitializeSentinel(log); + } + + configuration.AfterConnectAsync(muxer, s => log?.LogInformationAfterConnect(s)).Wait(muxer.SyncConnectTimeout(true)); + + log?.LogInformationTotalConnectTime(sw.ElapsedMilliseconds); + + return muxer; + } + finally + { + if (connectHandler != null && muxer != null) muxer.ConnectionFailed -= connectHandler; + if (killMe != null) try { killMe.Dispose(); } catch { } + if (log is TextWriterLogger twLogger) twLogger.Release(); + } + } + + ReadOnlySpan IInternalConnectionMultiplexer.GetServerSnapshot() => _serverSnapshot.AsSpan(); + internal ReadOnlySpan GetServerSnapshot() => _serverSnapshot.AsSpan(); + internal sealed class ServerSnapshot : IEnumerable + { + public static ServerSnapshot Empty { get; } = new ServerSnapshot(Array.Empty(), 0); + private ServerSnapshot(ServerEndPoint[] endpoints, int count) + { + _endpoints = endpoints; + _count = count; + } + private readonly ServerEndPoint[] _endpoints; + private readonly int _count; + public ReadOnlySpan AsSpan() => new ReadOnlySpan(_endpoints, 0, _count); + public ReadOnlyMemory AsMemory() => new ReadOnlyMemory(_endpoints, 0, _count); + + internal ServerSnapshot Add(ServerEndPoint value) + { + if (value == null) + { + return this; + } + + ServerEndPoint[] nextEndpoints; + if (_endpoints.Length > _count) + { + nextEndpoints = _endpoints; + } + else + { + // no more room; need a new array + int newLen = _endpoints.Length << 1; + if (newLen == 0) newLen = 4; + nextEndpoints = new ServerEndPoint[newLen]; + _endpoints.CopyTo(nextEndpoints, 0); + } + nextEndpoints[_count] = value; + return new ServerSnapshot(nextEndpoints, _count + 1); + } + + internal EndPoint[] GetEndPoints() + { + if (_count == 0) return Array.Empty(); + + var arr = new EndPoint[_count]; + for (int i = 0; i < _count; i++) + { + arr[i] = _endpoints[i].EndPoint; + } + return arr; + } + + public Enumerator GetEnumerator() => new(_endpoints, _count); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public struct Enumerator : IEnumerator + { + private readonly ServerEndPoint[] _endpoints; + private readonly Func? _predicate; + private readonly int _count; + private int _index; + + public ServerEndPoint Current { get; private set; } + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + while (_index < _count && ++_index < _count) + { + Current = _endpoints[_index]; + if (_predicate is null || _predicate(Current)) + { + return true; + } + } + Current = default!; + return false; + } + void IDisposable.Dispose() { } + void IEnumerator.Reset() + { + _index = -1; + Current = default!; + } + + public Enumerator(ServerEndPoint[] endpoints, int count, Func? predicate = null) + { + _index = -1; + _endpoints = endpoints; + _count = count; + _predicate = predicate; + Current = default!; + } + } + + public int Count => _count; + + public bool Any(Func? predicate = null) + { + if (_count > 0) + { + if (predicate is null) return true; + foreach (var item in AsSpan()) // span for bounds elision + { + if (predicate(item)) return true; + } + } + return false; + } + + public ServerSnapshotFiltered Where(CommandFlags flags) + { + var effectiveFlags = flags & (CommandFlags.DemandMaster | CommandFlags.DemandReplica); + return effectiveFlags switch + { + CommandFlags.DemandMaster => Where(static s => !s.IsReplica), + CommandFlags.DemandReplica => Where(static s => s.IsReplica), + _ => Where(null!), + // note we don't need to consider "both", since the composition of the flags-enum precludes that + }; + } + + public ServerSnapshotFiltered Where(Func predicate) + => new ServerSnapshotFiltered(_endpoints, _count, predicate); + + public readonly struct ServerSnapshotFiltered : IEnumerable + { + private readonly ServerEndPoint[] _endpoints; + private readonly Func? _predicate; + private readonly int _count; + + public ServerSnapshotFiltered(ServerEndPoint[] endpoints, int count, Func? predicate) + { + _endpoints = endpoints; + _count = count; + _predicate = predicate; + } + + public Enumerator GetEnumerator() => new(_endpoints, _count, _predicate); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + } + + ServerEndPoint IInternalConnectionMultiplexer.GetServerEndPoint(EndPoint endpoint) => GetServerEndPoint(endpoint); + + [return: NotNullIfNotNull(nameof(endpoint))] + internal ServerEndPoint? GetServerEndPoint(EndPoint? endpoint, ILogger? log = null, bool activate = true) + { + if (endpoint == null) return null; + var server = (ServerEndPoint?)servers[endpoint]; + if (server == null) + { + bool isNew = false; + lock (servers) + { + server = (ServerEndPoint?)servers[endpoint]; + if (server == null) + { + if (_isDisposed) throw new ObjectDisposedException(ToString()); + + server = new ServerEndPoint(this, endpoint); + servers.Add(endpoint, server); + isNew = true; + _serverSnapshot = _serverSnapshot.Add(server); + } + } + // spin up the connection if this is new + if (isNew && activate) + { + server.Activate(ConnectionType.Interactive, log); + if (server.SupportsSubscriptions && !server.KnowOrAssumeResp3()) + { + // Intentionally not logging the sub connection + server.Activate(ConnectionType.Subscription, null); + } + } + } + return server; + } + + internal void Root() => pulse?.Root(this); + + // note that this also acts (conditionally) as the GC root for the multiplexer + // when there are in-flight messages; the timer can then acts as the heartbeat + // to make sure that everything *eventually* completes + private sealed class TimerToken : IDisposable + { + private TimerToken(ConnectionMultiplexer muxer) + { + _weakRef = new(muxer); + } + private Timer? _timer; + public void SetTimer(Timer timer) => _timer = timer; + + private readonly WeakReference _weakRef; + + private object StrongRefSyncLock => _weakRef; // private and readonly? it'll do + private ConnectionMultiplexer? _strongRef; + private int _strongRefToken; + + private static readonly TimerCallback Heartbeat = state => + { + var token = (TimerToken)state!; + if (token._weakRef.TryGetTarget(out var muxer)) + { + muxer.OnHeartbeat(); + } + else + { + // the muxer got disposed from out of us; kill the timer + token.Dispose(); + } + }; + + internal static TimerToken Create(ConnectionMultiplexer connection) + { + var token = new TimerToken(connection); + var heartbeatMilliseconds = (int)connection.RawConfig.HeartbeatInterval.TotalMilliseconds; + var timer = new Timer(Heartbeat, token, heartbeatMilliseconds, heartbeatMilliseconds); + token.SetTimer(timer); + return token; + } + + public void Dispose() + { + var tmp = _timer; + _timer = null; + if (tmp is not null) try { tmp.Dispose(); } catch { } + + _strongRef = null; // note that this shouldn't be relevant since we've unrooted the TimerToken + } + + // explanation of rooting model: + // + // the timer has a reference to the TimerToken; this *always* has a weak-ref, + // and *may* sometimes have a strong-ref; this is so that if a consumer + // drops a multiplexer, it can be garbage collected, i.e. the heartbeat timer + // doesn't keep the entire thing alive forever; instead, if the heartbeat detects + // the weak-ref has been collected, it can cancel the timer and *itself* go away; + // however: this leaves a problem where there is *in flight work* when the consumer + // drops the multiplexer; in particular, if that happens when disconnected, there + // could be consumer-visible pending TCS items *in the backlog queue*; we don't want + // to leave those incomplete, as that fails the contractual expectations of async/await; + // instead we need to root ourselves. The natural place to do this is by rooting the + // multiplexer, allowing the heartbeat to keep poking things, so that the usual + // message-processing and timeout rules apply. This is why we *sometimes* also keep + // a strong-ref to the same multiplexer. + // + // The TimerToken is rooted by the timer callback; this then roots the multiplexer, + // which keeps our bridges and connections in scope - until we're sure we're done + // with them. + // + // 1) any bridge or connection will trigger rooting by calling Root when + // they change from "empty" to "non-empty" i.e. whenever there + // in-flight items; this always changes the token; this includes both the + // backlog and awaiting-reply queues. + // + // 2) the heartbeat is responsible for unrooting, after processing timeouts + // etc; first it checks whether it is needed (IsRooted), which also gives + // it the current token. + // + // 3) if so, the heartbeat will (outside of the lock) query all sources to + // see if they still have outstanding work; if everyone reports negatively, + // then the heartbeat calls UnRoot passing in the old token; if this still + // matches (i.e. no new work came in while we were looking away), then the + // strong reference is removed; note that "has outstanding work" ignores + // internal-call messages; we are only interested in consumer-facing items + // (but we need to check this *here* rather than when adding, as otherwise + // the definition of "is empty, should root" becomes more complicated, which + // impacts the write path, rather than the heartbeat path. + // + // This means that the multiplexer (via the timer) lasts as long as there are + // outstanding messages; if the consumer has dropped the multiplexer, then + // there will be no new incoming messages, and after timeouts: everything + // should drop. + public void Root(ConnectionMultiplexer multiplexer) + { + lock (StrongRefSyncLock) + { + _strongRef = multiplexer; + _strongRefToken++; + } + } + + public bool IsRooted(out int token) + { + lock (StrongRefSyncLock) + { + token = _strongRefToken; + return _strongRef is not null; + } + } + + public void UnRoot(int token) + { + lock (StrongRefSyncLock) + { + if (token == _strongRefToken) + { + _strongRef = null; + } + } + } + } + + private void OnHeartbeat() + { + try + { + int now = Environment.TickCount; + Interlocked.Exchange(ref lastHeartbeatTicks, now); + Interlocked.Exchange(ref lastGlobalHeartbeatTicks, now); + Trace("heartbeat"); + + var tmp = GetServerSnapshot(); + int token = 0; + bool isRooted = pulse?.IsRooted(out token) ?? false, hasPendingCallerFacingItems = false; + + for (int i = 0; i < tmp.Length; i++) + { + tmp[i].OnHeartbeat(); + if (isRooted && !hasPendingCallerFacingItems) + { + hasPendingCallerFacingItems = tmp[i].HasPendingCallerFacingItems(); + } + } + if (isRooted && !hasPendingCallerFacingItems) + { + // release the GC root on the heartbeat *if* the token still matches + pulse?.UnRoot(token); + } + } + catch (Exception ex) + { + if (Interlocked.CompareExchange(ref _activeHeartbeatErrors, 1, 0) == 0) + { + try + { + OnInternalError(ex); + } + finally + { + Interlocked.Exchange(ref _activeHeartbeatErrors, 0); + } + } + } + } + + /// + /// Obtain a pub/sub subscriber connection to the specified server. + /// + /// The async state object to pass to the created . + public ISubscriber GetSubscriber(object? asyncState = null) + { + if (!RawConfig.Proxy.SupportsPubSub()) + { + throw new NotSupportedException($"The pub/sub API is not available via {RawConfig.Proxy}"); + } + return new RedisSubscriber(this, asyncState); + } + + /// + /// Applies common DB number defaults and rules. + /// + internal int ApplyDefaultDatabase(int db) + { + if (db == -1) + { + db = RawConfig.DefaultDatabase.GetValueOrDefault(); + } + else if (db < 0) + { + throw new ArgumentOutOfRangeException(nameof(db)); + } + + if (db != 0 && !RawConfig.Proxy.SupportsDatabases()) + { + throw new NotSupportedException($"{RawConfig.Proxy} only supports database 0"); + } + + return db; + } + + /// + /// Obtain an interactive connection to a database inside redis. + /// + /// The ID to get a database for. + /// The async state to pass into the resulting . + public IDatabase GetDatabase(int db = -1, object? asyncState = null) + { + db = ApplyDefaultDatabase(db); + + // if there's no async-state, and the DB is suitable, we can hand out a re-used instance + return (asyncState == null && db <= MaxCachedDatabaseInstance) + ? GetCachedDatabaseInstance(db) : new RedisDatabase(this, db, asyncState); + } + + // DB zero is stored separately, since 0-only is a massively common use-case + private const int MaxCachedDatabaseInstance = 16; // 17 items - [0,16] + // Side note: "databases 16" is the default in redis.conf; happy to store one extra to get nice alignment etc + private IDatabase? dbCacheZero; + private IDatabase[]? dbCacheLow; + private IDatabase GetCachedDatabaseInstance(int db) // note that we already trust db here; only caller checks range + { + // Note: we don't need to worry about *always* returning the same instance. + // If two threads ask for db 3 at the same time, it is OK for them to get + // different instances, one of which (arbitrarily) ends up cached for later use. + if (db == 0) + { + return dbCacheZero ??= new RedisDatabase(this, 0, null); + } + var arr = dbCacheLow ??= new IDatabase[MaxCachedDatabaseInstance]; + return arr[db - 1] ??= new RedisDatabase(this, db, null); + } + + /// + /// Compute the hash-slot of a specified key. + /// + /// The key to get a hash slot ID for. + public int HashSlot(RedisKey key) => ServerSelectionStrategy.HashSlot(key); + + internal ServerEndPoint? AnyServer(ServerType serverType, uint startOffset, RedisCommand command, CommandFlags flags, bool allowDisconnected) + { + var tmp = GetServerSnapshot(); + int len = tmp.Length; + ServerEndPoint? fallback = null; + for (int i = 0; i < len; i++) + { + var server = tmp[(int)(((uint)i + startOffset) % len)]; + if (server != null && server.ServerType == serverType && server.IsSelectable(command, allowDisconnected)) + { + if (server.IsReplica) + { + switch (flags) + { + case CommandFlags.DemandReplica: + case CommandFlags.PreferReplica: + return server; + case CommandFlags.PreferMaster: + fallback = server; + break; + } + } + else + { + switch (flags) + { + case CommandFlags.DemandMaster: + case CommandFlags.PreferMaster: + return server; + case CommandFlags.PreferReplica: + fallback = server; + break; + } + } + } + } + return fallback; + } + + /// + /// Obtain a configuration API for an individual server. + /// + /// The host to get a server for. + /// The port for to get a server for. + /// The async state to pass into the resulting . + public IServer GetServer(string host, int port, object? asyncState = null) => + GetServer(Format.ParseEndPoint(host, port), asyncState); + + /// + /// Obtain a configuration API for an individual server. + /// + /// The "host:port" string to get a server for. + /// The async state to pass into the resulting . + public IServer GetServer(string hostAndPort, object? asyncState = null) => + Format.TryParseEndPoint(hostAndPort, out var ep) + ? GetServer(ep, asyncState) + : throw new ArgumentException($"The specified host and port could not be parsed: {hostAndPort}", nameof(hostAndPort)); + + /// + /// Obtain a configuration API for an individual server. + /// + /// The host to get a server for. + /// The port for to get a server for. + public IServer GetServer(IPAddress host, int port) => GetServer(new IPEndPoint(host, port)); + + /// + /// Obtain a configuration API for an individual server. + /// + /// The endpoint to get a server for. + /// The async state to pass into the resulting . + public IServer GetServer(EndPoint? endpoint, object? asyncState = null) + { + _ = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); + if (!RawConfig.Proxy.SupportsServerApi()) + { + throw new NotSupportedException($"The server API is not available via {RawConfig.Proxy}"); + } + var server = servers[endpoint] as ServerEndPoint ?? throw new ArgumentException("The specified endpoint is not defined", nameof(endpoint)); + return server.GetRedisServer(asyncState); + } + + /// +#pragma warning disable RS0026 + public IServer GetServer(RedisKey key, object? asyncState = null, CommandFlags flags = CommandFlags.None) +#pragma warning restore RS0026 + { + // We'll spoof the GET command for this; we're not supporting ad-hoc access to the pub/sub channel, because: bad things. + // Any read-only-replica vs writable-primary concerns should be managed by the caller via "flags"; the default is PreferPrimary. + // Note that ServerSelectionStrategy treats "null" (default) keys as NoSlot, aka Any. + return (SelectServer(RedisCommand.GET, flags, key) ?? Throw()).GetRedisServer(asyncState); + + [DoesNotReturn] + static ServerEndPoint Throw() => throw new InvalidOperationException("It was not possible to resolve a connection to the server owning the specified key"); + } + + /// + /// Obtain configuration APIs for all servers in this multiplexer. + /// + public IServer[] GetServers() + { + var snapshot = GetServerSnapshot(); + var result = new IServer[snapshot.Length]; + for (var i = 0; i < snapshot.Length; i++) + { + result[i] = snapshot[i].GetRedisServer(null); + } + return result; + } + + /// + /// Get the hash-slot associated with a given key, if applicable. + /// This can be useful for grouping operations. + /// + /// The to determine the hash slot for. + public int GetHashSlot(RedisKey key) => ServerSelectionStrategy.HashSlot(key); + + /// + /// The number of operations that have been performed on all connections. + /// + public long OperationCount + { + get + { + long total = 0; + var snapshot = GetServerSnapshot(); + for (int i = 0; i < snapshot.Length; i++) total += snapshot[i].OperationCount; + return total; + } + } + + /// + /// Reconfigure the current connections based on the existing configuration. + /// + /// The to log to. + public bool Configure(TextWriter? log = null) + { + // Note we expect ReconfigureAsync to internally allow [n] duration, + // so to avoid near misses, here we wait 2*[n]. + var task = ReconfigureAsync(first: false, reconfigureAll: true, Logger.With(log), null, "configure"); + if (!task.Wait(SyncConnectTimeout(false))) + { + task.ObserveErrors(); + if (RawConfig.AbortOnConnectFail) + { + throw new TimeoutException(); + } + else + { + LastException = new TimeoutException("ConnectTimeout"); + } + return false; + } + return task.Result; + } + + /// + /// Reconfigure the current connections based on the existing configuration. + /// + /// The to log to. + public async Task ConfigureAsync(TextWriter? log = null) + { + return await ReconfigureAsync(first: false, reconfigureAll: true, Logger.With(log), null, "configure").ObserveErrors().ForAwait(); + } + + internal int SyncConnectTimeout(bool forConnect) + { + int retryCount = forConnect ? RawConfig.ConnectRetry : 1; + if (retryCount <= 0) retryCount = 1; + + int timeout = RawConfig.ConnectTimeout; + if (timeout >= int.MaxValue / retryCount) return int.MaxValue; + + timeout *= retryCount; + if (timeout >= int.MaxValue - 500) return int.MaxValue; + return timeout + Math.Min(500, timeout); + } + + /// + /// Provides a text overview of the status of all connections. + /// + public string GetStatus() + { + using var sw = new StringWriter(); + GetStatus(sw); + return sw.ToString(); + } + + /// + /// Provides a text overview of the status of all connections. + /// + /// The to log to. + public void GetStatus(TextWriter log) => GetStatus(new TextWriterLogger(log, null)); + + internal void GetStatus(ILogger? log) + { + if (log == null) return; + + var tmp = GetServerSnapshot(); + log.LogInformationEndpointSummaryHeader(); + foreach (var server in tmp) + { + log.LogInformationServerSummary(server.Summary(), server.GetCounters(), server.GetProfile()); + } + log.LogInformationTimeoutsSummary( + Interlocked.Read(ref syncTimeouts), + Interlocked.Read(ref asyncTimeouts), + Interlocked.Read(ref fireAndForgets), + LastHeartbeatSecondsAgo); + } + + private void ActivateAllServers(ILogger? log) + { + foreach (var server in GetServerSnapshot()) + { + server.Activate(ConnectionType.Interactive, log); + if (server.SupportsSubscriptions && !server.KnowOrAssumeResp3()) + { + // Intentionally not logging the sub connection + server.Activate(ConnectionType.Subscription, null); + } + } + } + + internal bool ReconfigureIfNeeded(EndPoint? blame, bool fromBroadcast, string cause, bool publishReconfigure = false, CommandFlags flags = CommandFlags.None) + { + if (fromBroadcast) + { + OnConfigurationChangedBroadcast(blame!); + } + string? activeCause = Volatile.Read(ref activeConfigCause); + if (activeCause is null) + { + bool reconfigureAll = fromBroadcast || publishReconfigure; + Trace("Configuration change detected; checking nodes", "Configuration"); + ReconfigureAsync(first: false, reconfigureAll, Logger, blame, cause, publishReconfigure, flags).ObserveErrors(); + return true; + } + else + { + Trace("Configuration change skipped; already in progress via " + activeCause, "Configuration"); + return false; + } + } + + /// + /// Triggers a reconfigure of this multiplexer. + /// This re-assessment of all server endpoints to get the current topology and adjust, the same as if we had first connected. + /// + public Task ReconfigureAsync(string reason) => + ReconfigureAsync(first: false, reconfigureAll: false, log: Logger, blame: null, cause: reason); + + internal async Task ReconfigureAsync(bool first, bool reconfigureAll, ILogger? log, EndPoint? blame, string cause, bool publishReconfigure = false, CommandFlags publishReconfigureFlags = CommandFlags.None) + { + if (_isDisposed) throw new ObjectDisposedException(ToString()); + bool showStats = log is not null; + + bool ranThisCall = false; + try + { + // Note that we *always* exchange the reason (first one counts) to prevent duplicate runs + ranThisCall = Interlocked.CompareExchange(ref activeConfigCause, cause, null) == null; + + if (!ranThisCall) + { + log?.LogInformationReconfigurationInProgress(activeConfigCause, cause); + return false; + } + Trace("Starting reconfiguration..."); + Trace(blame != null, "Blaming: " + Format.ToString(blame)); + Interlocked.Exchange(ref lastReconfigiureTicks, Environment.TickCount); + + log?.LogInformationConfiguration(new(RawConfig)); + + if (first) + { + if (RawConfig.ResolveDns && EndPoints.HasDnsEndPoints()) + { + var dns = EndPoints.ResolveEndPointsAsync(this, log).ObserveErrors(); + if (!await dns.TimeoutAfter(TimeoutMilliseconds).ForAwait()) + { + throw new TimeoutException("Timeout resolving endpoints"); + } + } + foreach (var endpoint in EndPoints) + { + GetServerEndPoint(endpoint, log, false); + } + ActivateAllServers(log); + } + int attemptsLeft = first ? RawConfig.ConnectRetry : 1; + + bool healthy = false; + do + { + if (first) + { + attemptsLeft--; + } + int standaloneCount = 0, clusterCount = 0, sentinelCount = 0; + var endpoints = EndPoints; + bool useTieBreakers = RawConfig.TryGetTieBreaker(out var tieBreakerKey); + log?.LogInformationUniqueNodesSpecified(endpoints.Count, useTieBreakers ? "with" : "without"); + + if (endpoints.Count == 0) + { + throw new InvalidOperationException("No nodes to consider"); + } + List primaries = new List(endpoints.Count); + + ServerEndPoint[]? servers = null; + bool encounteredConnectedClusterServer = false; + ValueStopwatch? watch = null; + + int iterCount = first ? 2 : 1; + // This is fix for https://github.com/StackExchange/StackExchange.Redis/issues/300 + // auto discoverability of cluster nodes is made synchronous. + // We try to connect to endpoints specified inside the user provided configuration + // and when we encounter an endpoint to which we are able to successfully connect, + // we get the list of cluster nodes from that endpoint and try to proactively connect + // to listed nodes instead of relying on auto configure. + for (int iter = 0; iter < iterCount; ++iter) + { + if (endpoints == null) break; + + var available = new Task[endpoints.Count]; + servers = new ServerEndPoint[available.Length]; + + for (int i = 0; i < available.Length; i++) + { + Trace("Testing: " + Format.ToString(endpoints[i])); + + var server = GetServerEndPoint(endpoints[i]); + // server.ReportNextFailure(); + servers[i] = server; + + // This awaits either the endpoint's initial connection, or a tracer if we're already connected + // (which is the reconfigure case, except second iteration which is only for newly discovered cluster members). + var isFirstIteration = iter == 0; + available[i] = server.OnConnectedAsync(log, sendTracerIfConnected: isFirstIteration, autoConfigureIfConnected: reconfigureAll); + } + + watch ??= ValueStopwatch.StartNew(); + var remaining = RawConfig.ConnectTimeout - watch.Value.ElapsedMilliseconds; + log?.LogInformationAllowingEndpointsToRespond(available.Length, TimeSpan.FromMilliseconds(remaining)); + Trace("Allowing endpoints " + TimeSpan.FromMilliseconds(remaining) + " to respond..."); + var allConnected = await WaitAllIgnoreErrorsAsync("available", available, remaining, log).ForAwait(); + + if (!allConnected) + { + // If we failed, log the details so we can debug why per connection + for (var i = 0; i < servers.Length; i++) + { + var server = servers[i]; + var task = available[i]; + var bs = server.GetBridgeStatus(ConnectionType.Interactive); + + log?.LogInformationServerStatus(i, new(server), task.Status, bs.MessagesSinceLastHeartbeat, bs.Connection.MessagesSentAwaitingResponse, bs.Connection.BytesAvailableOnSocket, bs.MessagesSinceLastHeartbeat, bs.IsWriterActive, bs.Connection.BytesInReadPipe, bs.Connection.BytesInWritePipe, bs.BacklogStatus, bs.Connection.ReadStatus, bs.Connection.WriteStatus); + } + } + + log?.LogInformationEndpointSummary(); + // Log current state after await + foreach (var server in servers) + { + log?.LogInformationEndpointState(new(server.EndPoint), server.InteractiveConnectionState, server.SubscriptionConnectionState); + } + + log?.LogInformationTaskSummary(); + EndPointCollection? updatedClusterEndpointCollection = null; + for (int i = 0; i < available.Length; i++) + { + var task = available[i]; + var server = servers[i]; + Trace(Format.ToString(endpoints[i]) + ": " + task.Status); + if (task.IsFaulted) + { + server.SetUnselectable(UnselectableFlags.DidNotRespond); + var aex = task.Exception!; + foreach (var ex in aex.InnerExceptions) + { + log?.LogErrorServerFaulted(ex, new(server), ex.Message); + failureMessage = ex.Message; + } + } + else if (task.IsCanceled) + { + server.SetUnselectable(UnselectableFlags.DidNotRespond); + log?.LogInformationConnectTaskCanceled(new(server)); + } + else if (task.IsCompleted) + { + if (task.Result != "Disconnected") + { + server.ClearUnselectable(UnselectableFlags.DidNotRespond); + log?.LogInformationServerReturnedSuccess(new(server), server.ServerType, server.IsReplica ? "replica" : "primary", task.Result); + + // Count the server types + switch (server.ServerType) + { + case ServerType.Twemproxy: + case ServerType.Envoyproxy: + case ServerType.Standalone: + standaloneCount++; + break; + case ServerType.Sentinel: + sentinelCount++; + break; + case ServerType.Cluster: + clusterCount++; + break; + } + + if (clusterCount > 0 && !encounteredConnectedClusterServer && CommandMap.IsAvailable(RedisCommand.CLUSTER)) + { + // We have encountered a connected server with a cluster type for the first time. + // so we will get list of other nodes from this server using "CLUSTER NODES" command + // and try to connect to these other nodes in the next iteration + encounteredConnectedClusterServer = true; + updatedClusterEndpointCollection = await GetEndpointsFromClusterNodes(server, log).ForAwait(); + } + + // Set the server UnselectableFlags and update primaries list + switch (server.ServerType) + { + case ServerType.Twemproxy: + case ServerType.Envoyproxy: + case ServerType.Sentinel: + case ServerType.Standalone: + case ServerType.Cluster: + server.ClearUnselectable(UnselectableFlags.ServerType); + if (server.IsReplica) + { + server.ClearUnselectable(UnselectableFlags.RedundantPrimary); + } + else + { + primaries.Add(server); + } + break; + default: + server.SetUnselectable(UnselectableFlags.ServerType); + break; + } + } + else + { + server.SetUnselectable(UnselectableFlags.DidNotRespond); + log?.LogInformationServerReturnedIncorrectly(new(server)); + } + } + else + { + server.SetUnselectable(UnselectableFlags.DidNotRespond); + log?.LogInformationServerDidNotRespond(new(server), task.Status); + } + } + + if (encounteredConnectedClusterServer) + { + endpoints = updatedClusterEndpointCollection; + } + else + { + break; // We do not want to repeat the second iteration + } + } + + if (clusterCount == 0) + { + // Set the serverSelectionStrategy + if (RawConfig.Proxy == Proxy.Twemproxy) + { + ServerSelectionStrategy.ServerType = ServerType.Twemproxy; + } + else if (RawConfig.Proxy == Proxy.Envoyproxy) + { + ServerSelectionStrategy.ServerType = ServerType.Envoyproxy; + } + else if (standaloneCount == 0 && sentinelCount > 0) + { + ServerSelectionStrategy.ServerType = ServerType.Sentinel; + } + else if (standaloneCount > 0) + { + ServerSelectionStrategy.ServerType = ServerType.Standalone; + } + + // If multiple primaries are detected, nominate the preferred one + // ...but not if the type of server we're connected to supports and expects multiple primaries + // ...for those cases, we want to allow sending to any primary endpoint. + if (ServerSelectionStrategy.ServerType.HasSinglePrimary()) + { + var preferred = NominatePreferredPrimary(log, servers!, useTieBreakers, primaries); + foreach (var primary in primaries) + { + if (primary == preferred || primary.IsReplica) + { + log?.LogInformationClearingAsRedundantPrimary(new(primary)); + primary.ClearUnselectable(UnselectableFlags.RedundantPrimary); + } + else + { + log?.LogInformationSettingAsRedundantPrimary(new(primary)); + primary.SetUnselectable(UnselectableFlags.RedundantPrimary); + } + } + } + } + else + { + ServerSelectionStrategy.ServerType = ServerType.Cluster; + long coveredSlots = ServerSelectionStrategy.CountCoveredSlots(); + log?.LogInformationClusterSlotsCovered(coveredSlots, ServerSelectionStrategy.TotalSlots); + } + if (!first) + { + // Calling the sync path here because it's all fire and forget + long subscriptionChanges = EnsureSubscriptions(CommandFlags.FireAndForget); + if (subscriptionChanges == 0) + { + log?.LogInformationNoSubscriptionChanges(); + } + else + { + log?.LogInformationSubscriptionsAttemptingReconnect(subscriptionChanges); + } + } + if (showStats) + { + GetStatus(log); + } + + string? stormLog = GetStormLog(); + if (!string.IsNullOrWhiteSpace(stormLog)) + { + log?.LogInformationStormLog(stormLog!); + } + healthy = standaloneCount != 0 || clusterCount != 0 || sentinelCount != 0; + if (first && !healthy && attemptsLeft > 0) + { + log?.LogInformationResettingFailingConnections(); + ResetAllNonConnected(); + log?.LogInformationRetryingAttempts(attemptsLeft); + } + // WTF("?: " + attempts); + } + while (first && !healthy && attemptsLeft > 0); + + if (first && RawConfig.AbortOnConnectFail && !healthy) + { + return false; + } + if (first) + { + log?.LogInformationStartingHeartbeat(); + pulse = TimerToken.Create(this); + } + if (publishReconfigure) + { + try + { + log?.LogInformationBroadcastingReconfigure(); + PublishReconfigureImpl(publishReconfigureFlags); + } + catch + { } + } + return true; + } + catch (Exception ex) + { + Trace(ex.Message); + throw; + } + finally + { + Trace("Exiting reconfiguration..."); + if (ranThisCall) Interlocked.Exchange(ref activeConfigCause, null); + if (!first && blame is not null) OnConfigurationChanged(blame); + Trace("Reconfiguration exited"); + } + } + + /// + /// Gets all endpoints defined on the multiplexer. + /// + /// Whether to get only the endpoints specified explicitly in the config. + public EndPoint[] GetEndPoints(bool configuredOnly = false) => + configuredOnly + ? EndPoints.ToArray() + : _serverSnapshot.GetEndPoints(); + + private async Task GetEndpointsFromClusterNodes(ServerEndPoint server, ILogger? log) + { + var message = Message.Create(-1, CommandFlags.None, RedisCommand.CLUSTER, RedisLiterals.NODES); + try + { + var clusterConfig = await ExecuteAsyncImpl(message, ResultProcessor.ClusterNodes, null, server).ForAwait(); + if (clusterConfig is null) + { + return null; + } + var clusterEndpoints = new EndPointCollection(clusterConfig.Nodes.Where(node => node.EndPoint is not null).Select(node => node.EndPoint!).ToList()); + // Loop through nodes in the cluster and update nodes relations to other nodes + ServerEndPoint? serverEndpoint = null; + foreach (EndPoint endpoint in clusterEndpoints) + { + serverEndpoint = GetServerEndPoint(endpoint); + serverEndpoint?.UpdateNodeRelations(clusterConfig); + } + return clusterEndpoints; + } + catch (Exception ex) + { + log?.LogErrorEncounteredErrorWhileUpdatingClusterConfig(ex, ex.Message); + return null; + } + } + + private void ResetAllNonConnected() + { + var snapshot = GetServerSnapshot(); + foreach (var server in snapshot) + { + server.ResetNonConnected(); + } + } + + private static ServerEndPoint? NominatePreferredPrimary(ILogger? log, ServerEndPoint[] servers, bool useTieBreakers, List primaries) + { + log?.LogInformationElectionSummary(); + + Dictionary? uniques = null; + if (useTieBreakers) + { + // Count the votes + uniques = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (int i = 0; i < servers.Length; i++) + { + var server = servers[i]; + string? serverResult = server.TieBreakerResult; + + if (serverResult.IsNullOrWhiteSpace()) + { + log?.LogInformationElectionNoTiebreaker(new(server)); + } + else + { + log?.LogInformationElectionNominates(new(server), serverResult); + if (!uniques.TryGetValue(serverResult, out int count)) count = 0; + uniques[serverResult] = count + 1; + } + } + } + + switch (primaries.Count) + { + case 0: + log?.LogInformationElectionNoPrimariesDetected(); + return null; + case 1: + log?.LogInformationElectionSinglePrimaryDetected(new(primaries[0].EndPoint)); + return primaries[0]; + default: + log?.LogInformationElectionMultiplePrimariesDetected(); + if (useTieBreakers && uniques != null) + { + switch (uniques.Count) + { + case 0: + log?.LogInformationElectionNoNominationsByTieBreaker(); + break; + case 1: + string unanimous = uniques.Keys.Single(); + log?.LogInformationElectionTieBreakerUnanimous(unanimous); + var found = SelectServerByElection(servers, unanimous, log); + if (found != null) + { + log?.LogInformationElectionElected(new(found.EndPoint)); + return found; + } + break; + default: + log?.LogInformationElectionContested(); + ServerEndPoint? highest = null; + bool arbitrary = false; + foreach (var pair in uniques.OrderByDescending(x => x.Value)) + { + log?.LogInformationElectionVotes(pair.Key, pair.Value); + if (highest == null) + { + highest = SelectServerByElection(servers, pair.Key, log); + if (highest != null) + { + // any more with this vote? if so: arbitrary + arbitrary = uniques.Where(x => x.Value == pair.Value).Skip(1).Any(); + } + } + } + if (highest != null) + { + if (arbitrary) + { + log?.LogInformationElectionChoosingPrimaryArbitrarily(new(highest.EndPoint)); + } + else + { + log?.LogInformationElectionElected(new(highest.EndPoint)); + } + return highest; + } + break; + } + } + break; + } + + log?.LogInformationElectionChoosingPrimaryArbitrarily(new(primaries[0].EndPoint)); + return primaries[0]; + } + + private static ServerEndPoint? SelectServerByElection(ServerEndPoint[] servers, string endpoint, ILogger? log) + { + if (servers == null || string.IsNullOrWhiteSpace(endpoint)) return null; + for (int i = 0; i < servers.Length; i++) + { + if (string.Equals(Format.ToString(servers[i].EndPoint), endpoint, StringComparison.OrdinalIgnoreCase)) + return servers[i]; + } + log?.LogInformationCouldNotFindThatEndpoint(); + var deDottedEndpoint = DeDotifyHost(endpoint); + for (int i = 0; i < servers.Length; i++) + { + if (string.Equals(DeDotifyHost(Format.ToString(servers[i].EndPoint)), deDottedEndpoint, StringComparison.OrdinalIgnoreCase)) + { + log?.LogInformationFoundAlternativeEndpoint(deDottedEndpoint); + return servers[i]; + } + } + return null; + } + + private static string DeDotifyHost(string input) + { + if (string.IsNullOrWhiteSpace(input)) return input; // GIGO + + if (!char.IsLetter(input[0])) return input; // Need first char to be alpha for this to work + + int periodPosition = input.IndexOf('.'); + if (periodPosition <= 0) return input; // No period or starts with a period? Then nothing useful to split + + int colonPosition = input.IndexOf(':'); + if (colonPosition > 0) + { + // Has a port specifier +#if NETCOREAPP + return string.Concat(input.AsSpan(0, periodPosition), input.AsSpan(colonPosition)); +#else + return input.Substring(0, periodPosition) + input.Substring(colonPosition); +#endif + } + else + { + return input.Substring(0, periodPosition); + } + } + + internal void UpdateClusterRange(ClusterConfiguration configuration) + { + if (configuration is null) + { + return; + } + foreach (var node in configuration.Nodes) + { + if (node.IsReplica || node.Slots.Count == 0) continue; + foreach (var slot in node.Slots) + { + if (GetServerEndPoint(node.EndPoint) is ServerEndPoint server) + { + ServerSelectionStrategy.UpdateClusterRange(slot.From, slot.To, server); + } + } + } + } + + internal ServerEndPoint? SelectServer(Message? message) => + message == null ? null : ServerSelectionStrategy.Select(message); + + internal ServerEndPoint? SelectServer(RedisCommand command, CommandFlags flags, in RedisKey key) => + ServerSelectionStrategy.Select(command, key, flags); + + internal ServerEndPoint? SelectServer(RedisCommand command, CommandFlags flags, in RedisChannel channel) => + ServerSelectionStrategy.Select(command, channel, flags); + + private bool PrepareToPushMessageToBridge(Message message, ResultProcessor? processor, IResultBox? resultBox, [NotNullWhen(true)] ref ServerEndPoint? server) + { + message.SetSource(processor, resultBox); + + if (server == null) + { + // Infer a server automatically + server = SelectServer(message); + + // If we didn't find one successfully, and we're allowed, queue for any viable server + if (server == null && RawConfig.BacklogPolicy.QueueWhileDisconnected) + { + server = ServerSelectionStrategy.Select(message, allowDisconnected: true); + } + } + else // A server was specified - do we trust their choice, though? + { + if (message.IsPrimaryOnly() && server.IsReplica) + { + throw ExceptionFactory.PrimaryOnly(RawConfig.IncludeDetailInExceptions, message.Command, message, server); + } + + switch (server.ServerType) + { + case ServerType.Cluster: + if (message.GetHashSlot(ServerSelectionStrategy) == ServerSelectionStrategy.MultipleSlots) + { + throw ExceptionFactory.MultiSlot(RawConfig.IncludeDetailInExceptions, message); + } + break; + } + + // If we're not allowed to queue while disconnected, we'll bomb out below. + if (!server.IsConnected && !RawConfig.BacklogPolicy.QueueWhileDisconnected) + { + // Well, that's no use! + server = null; + } + } + + if (server != null) + { + var profilingSession = _profilingSessionProvider?.Invoke(); + if (profilingSession != null) + { + message.SetProfileStorage(ProfiledCommand.NewWithContext(profilingSession, server)); + } + + if (message.Db >= 0) + { + int availableDatabases = server.Databases; + if (availableDatabases > 0 && message.Db >= availableDatabases) + { + throw ExceptionFactory.DatabaseOutfRange(RawConfig.IncludeDetailInExceptions, message.Db, message, server); + } + } + + Trace("Queuing on server: " + message); + return true; + } + Trace("No server or server unavailable - aborting: " + message); + return false; + } + + private ValueTask TryPushMessageToBridgeAsync(Message message, ResultProcessor? processor, IResultBox? resultBox, [NotNullWhen(true)] ref ServerEndPoint? server) + => PrepareToPushMessageToBridge(message, processor, resultBox, ref server) ? server.TryWriteAsync(message) : new ValueTask(WriteResult.NoConnectionAvailable); + + [Obsolete("prefer async")] + private WriteResult TryPushMessageToBridgeSync(Message message, ResultProcessor? processor, IResultBox? resultBox, [NotNullWhen(true)] ref ServerEndPoint? server) + => PrepareToPushMessageToBridge(message, processor, resultBox, ref server) ? server.TryWriteSync(message) : WriteResult.NoConnectionAvailable; + + /// + /// Gets the client name for this multiplexer. + /// + public override string ToString() => string.IsNullOrWhiteSpace(ClientName) ? GetType().Name : ClientName; + + internal Exception GetException(WriteResult result, Message message, ServerEndPoint? server, PhysicalBridge? bridge = null) => result switch + { + WriteResult.Success => throw new ArgumentOutOfRangeException(nameof(result), "Be sure to check result isn't successful before calling GetException."), + WriteResult.NoConnectionAvailable => ExceptionFactory.NoConnectionAvailable(this, message, server), + WriteResult.TimeoutBeforeWrite => ExceptionFactory.Timeout(this, null, message, server, result, bridge), + _ => ExceptionFactory.ConnectionFailure(RawConfig.IncludeDetailInExceptions, ConnectionFailureType.ProtocolFailure, "An unknown error occurred when writing the message", server), + }; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1816:Dispose methods should call SuppressFinalize", Justification = "Intentional observation")] + internal static void ThrowFailed(TaskCompletionSource? source, Exception unthrownException) + { + try + { + throw unthrownException; + } + catch (Exception ex) + { + if (source is not null) + { + source.TrySetException(ex); + GC.KeepAlive(source.Task.Exception); + GC.SuppressFinalize(source.Task); + } + } + } + + [return: NotNullIfNotNull(nameof(defaultValue))] + internal T? ExecuteSyncImpl(Message message, ResultProcessor? processor, ServerEndPoint? server, T? defaultValue = default) + { + if (_isDisposed) throw new ObjectDisposedException(ToString()); + + if (message is null) // Fire-and forget could involve a no-op, represented by null - for example Increment by 0 + { + return defaultValue; + } + + Interlocked.Increment(ref syncOps); + + if (message.IsFireAndForget) + { +#pragma warning disable CS0618 // Type or member is obsolete + TryPushMessageToBridgeSync(message, processor, null, ref server); +#pragma warning restore CS0618 + Interlocked.Increment(ref fireAndForgets); + return defaultValue; + } + else + { + var source = SimpleResultBox.Get(); + + bool timeout = false; + WriteResult result; + lock (source) + { +#pragma warning disable CS0618 // Type or member is obsolete + result = TryPushMessageToBridgeSync(message, processor, source, ref server); +#pragma warning restore CS0618 + if (!source.IsFaulted) // if we faulted while writing, we don't need to wait + { + if (result != WriteResult.Success) + { + throw GetException(result, message, server); + } + + if (Monitor.Wait(source, TimeoutMilliseconds)) + { + Trace("Timely response to " + message); + } + else + { + Trace("Timeout performing " + message); + timeout = true; + } + } + } + + if (timeout) // note we throw *outside* of the main lock to avoid deadlock scenarios (#2376) + { + Interlocked.Increment(ref syncTimeouts); + // Very important not to return "source" to the pool here + // Also note we return "success" when queueing a messages to the backlog, so we need to manually fake it back here when timing out in the backlog + throw ExceptionFactory.Timeout(this, null, message, server, message.IsBacklogged ? WriteResult.TimeoutBeforeWrite : result, server?.GetBridge(message.Command, create: false)); + } + // Snapshot these so that we can recycle the box + var val = source.GetResult(out var ex, canRecycle: true); // now that we aren't locking it... + if (ex != null) throw ex; + Trace(message + " received " + val); + return val; + } + } + + internal Task ExecuteAsyncImpl(Message? message, ResultProcessor? processor, object? state, ServerEndPoint? server, T defaultValue) + { + static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, ValueTask write, TaskCompletionSource? tcs, Message message, ServerEndPoint? server, T defaultValue) + { + var result = await write.ForAwait(); + if (result != WriteResult.Success) + { + var ex = @this.GetException(result, message, server); + ThrowFailed(tcs, ex); + } + return tcs == null ? defaultValue : await tcs.Task.ForAwait(); + } + + if (_isDisposed) throw new ObjectDisposedException(ToString()); + + if (message == null) + { + return CompletedTask.FromDefault(defaultValue, state); + } + + Interlocked.Increment(ref asyncOps); + + TaskCompletionSource? tcs = null; + IResultBox? source = null; + if (!message.IsFireAndForget) + { + source = TaskResultBox.Create(out tcs, state); + } + var write = TryPushMessageToBridgeAsync(message, processor, source, ref server); + if (!write.IsCompletedSuccessfully) + { + return ExecuteAsyncImpl_Awaited(this, write, tcs, message, server, defaultValue); + } + + if (tcs == null) + { + return CompletedTask.FromDefault(defaultValue, null); // F+F explicitly does not get async-state + } + else + { + var result = write.Result; + if (result != WriteResult.Success) + { + var ex = GetException(result, message, server); + ThrowFailed(tcs, ex); + } + return tcs.Task; + } + } + + internal Task ExecuteAsyncImpl(Message? message, ResultProcessor? processor, object? state, ServerEndPoint? server) + { + [return: NotNullIfNotNull(nameof(tcs))] + static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, ValueTask write, TaskCompletionSource? tcs, Message message, ServerEndPoint? server) + { + var result = await write.ForAwait(); + if (result != WriteResult.Success) + { + var ex = @this.GetException(result, message, server); + ThrowFailed(tcs, ex); + } + return tcs == null ? default : await tcs.Task.ForAwait(); + } + + if (_isDisposed) throw new ObjectDisposedException(ToString()); + + if (message == null) + { + return CompletedTask.Default(state); + } + + Interlocked.Increment(ref asyncOps); + + TaskCompletionSource? tcs = null; + IResultBox? source = null; + if (!message.IsFireAndForget) + { + source = TaskResultBox.Create(out tcs, state); + } + var write = TryPushMessageToBridgeAsync(message, processor, source!, ref server); + if (!write.IsCompletedSuccessfully) + { + return ExecuteAsyncImpl_Awaited(this, write, tcs, message, server); + } + + if (tcs == null) + { + return CompletedTask.Default(null); // F+F explicitly does not get async-state + } + else + { + var result = write.Result; + if (result != WriteResult.Success) + { + var ex = GetException(result, message, server); + ThrowFailed(tcs, ex); + } + return tcs.Task; + } + } + + internal void OnAsyncTimeout() => Interlocked.Increment(ref asyncTimeouts); + + /// + /// Sends request to all compatible clients to reconfigure or reconnect. + /// + /// The command flags to use. + /// The number of instances known to have received the message (however, the actual number can be higher; returns -1 if the operation is pending). + public long PublishReconfigure(CommandFlags flags = CommandFlags.None) + { + if (ConfigurationChangedChannel is not null) + { + return ReconfigureIfNeeded(null, false, "PublishReconfigure", true, flags) + ? -1 + : PublishReconfigureImpl(flags); + } + return 0; + } + + private long PublishReconfigureImpl(CommandFlags flags) => + ConfigurationChangedChannel is byte[] channel + ? GetSubscriber().Publish(RedisChannel.Literal(channel), RedisLiterals.Wildcard, flags) + : 0; + + /// + /// Sends request to all compatible clients to reconfigure or reconnect. + /// + /// The command flags to use. + /// The number of instances known to have received the message (however, the actual number can be higher). + public Task PublishReconfigureAsync(CommandFlags flags = CommandFlags.None) => + ConfigurationChangedChannel is byte[] channel + ? GetSubscriber().PublishAsync(RedisChannel.Literal(channel), RedisLiterals.Wildcard, flags) + : CompletedTask.Default(null); + + /// + /// Release all resources associated with this object. + /// + public void Dispose() + { + GC.SuppressFinalize(this); + Close(!_isDisposed); + sentinelConnection?.Dispose(); + var oldTimer = Interlocked.Exchange(ref sentinelPrimaryReconnectTimer, null); + oldTimer?.Dispose(); + } + + /// + /// Release all resources associated with this object. + /// + public async ValueTask DisposeAsync() + { + GC.SuppressFinalize(this); + await CloseAsync(!_isDisposed).ForAwait(); + if (sentinelConnection is ConnectionMultiplexer sentinel) + { + await sentinel.DisposeAsync().ForAwait(); + } + var oldTimer = Interlocked.Exchange(ref sentinelPrimaryReconnectTimer, null); + oldTimer?.Dispose(); + } + + /// + /// Close all connections and release all resources associated with this object. + /// + /// Whether to allow all in-queue commands to complete first. + public void Close(bool allowCommandsToComplete = true) + { + if (_isDisposed) return; + + OnClosing(false); + _isDisposed = true; + _profilingSessionProvider = null; + using (var tmp = pulse) + { + pulse = null; + } + + if (allowCommandsToComplete) + { + var quits = QuitAllServers(); + WaitAllIgnoreErrors(quits); + } + DisposeAndClearServers(); + OnCloseReaderWriter(); + OnClosing(true); + Interlocked.Increment(ref _connectionCloseCount); + } + + /// + /// Close all connections and release all resources associated with this object. + /// + /// Whether to allow all in-queue commands to complete first. + public async Task CloseAsync(bool allowCommandsToComplete = true) + { + _isDisposed = true; + using (var tmp = pulse) + { + pulse = null; + } + + if (allowCommandsToComplete) + { + var quits = QuitAllServers(); + await WaitAllIgnoreErrorsAsync("quit", quits, RawConfig.AsyncTimeout, null).ForAwait(); + } + + DisposeAndClearServers(); + } + + private void DisposeAndClearServers() + { + lock (servers) + { + var iter = servers.GetEnumerator(); + while (iter.MoveNext()) + { + (iter.Value as ServerEndPoint)?.Dispose(); + } + servers.Clear(); + } + } + + private Task[] QuitAllServers() + { + var quits = new Task[2 * servers.Count]; + lock (servers) + { + var iter = servers.GetEnumerator(); + int index = 0; + while (iter.MoveNext()) + { + var server = (ServerEndPoint)iter.Value!; + quits[index++] = server.Close(ConnectionType.Interactive); + quits[index++] = server.Close(ConnectionType.Subscription); + } + } + return quits; + } + + long? IInternalConnectionMultiplexer.GetConnectionId(EndPoint endpoint, ConnectionType type) + => GetServerEndPoint(endpoint)?.GetBridge(type)?.ConnectionId; + } +} diff --git a/src/StackExchange.Redis/CursorEnumerable.cs b/src/StackExchange.Redis/CursorEnumerable.cs new file mode 100644 index 000000000..921d83ce0 --- /dev/null +++ b/src/StackExchange.Redis/CursorEnumerable.cs @@ -0,0 +1,370 @@ +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + /// + /// Provides the ability to iterate over a cursor-based sequence of redis data, synchronously or asynchronously. + /// + /// The type of the data in the cursor. + internal abstract class CursorEnumerable : IEnumerable, IScanningCursor, IAsyncEnumerable + { + private readonly RedisBase redis; + private readonly ServerEndPoint? server; + private protected readonly int db; + private protected readonly CommandFlags flags; + private protected readonly int pageSize, initialOffset; + private protected readonly RedisValue initialCursor; + private volatile IScanningCursor? activeCursor; + + private protected CursorEnumerable(RedisBase redis, ServerEndPoint? server, int db, int pageSize, in RedisValue cursor, int pageOffset, CommandFlags flags) + { + if (pageOffset < 0) throw new ArgumentOutOfRangeException(nameof(pageOffset)); + this.redis = redis; + this.server = server; + this.db = db; + this.pageSize = pageSize; + this.flags = flags; + initialCursor = cursor; + initialOffset = pageOffset; + } + + /// + /// Gets an enumerator for the sequence. + /// + public Enumerator GetEnumerator() => new Enumerator(this, default); + + /// + /// Gets an enumerator for the sequence. + /// + public Enumerator GetAsyncEnumerator(CancellationToken cancellationToken) => new Enumerator(this, cancellationToken); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken) => GetAsyncEnumerator(cancellationToken); + + internal readonly struct ScanResult + { + public readonly RedisValue Cursor; + public readonly T[]? ValuesOversized; + public readonly int Count; + public readonly bool IsPooled; + public ScanResult(RedisValue cursor, T[]? valuesOversized, int count, bool isPooled) + { + Cursor = cursor; + ValuesOversized = valuesOversized; + Count = count; + IsPooled = isPooled; + } + } + + private protected abstract Message? CreateMessage(in RedisValue cursor); + + private protected abstract ResultProcessor? Processor { get; } + + private protected virtual Task GetNextPageAsync(IScanningCursor obj, RedisValue cursor, out Message? message) + { + activeCursor = obj; + message = CreateMessage(cursor); + return redis.ExecuteAsync(message, Processor, server); + } + + /// + /// Provides the ability to iterate over a cursor-based sequence of redis data, synchronously or asynchronously. + /// + public class Enumerator : IEnumerator, IScanningCursor, IAsyncEnumerator + { + private readonly CursorEnumerable parent; + private readonly CancellationToken cancellationToken; + internal Enumerator(CursorEnumerable parent, CancellationToken cancellationToken) + { + this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); + this.cancellationToken = cancellationToken; + Reset(); + } + + /// + /// Gets the current value of the enumerator. + /// + public T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Debug.Assert(_pageOffset >= 0 & _pageOffset < _pageCount & _pageOversized!.Length >= _pageCount); + return _pageOversized[_pageOffset]; + } + } + + /// + /// Release all resources associated with this enumerator. + /// + public void Dispose() + { + _state = State.Disposed; + SetComplete(); + GC.SuppressFinalize(this); + } + + private void SetComplete() + { + _pageOffset = _pageCount = 0; + Recycle(ref _pageOversized, ref _isPooled); + switch (_state) + { + case State.Initial: + case State.Running: + _state = State.Complete; + break; + } + } + + /// + /// Release all resources associated with this enumerator. + /// + public ValueTask DisposeAsync() + { + Dispose(); + GC.SuppressFinalize(this); + return default; + } + + object? IEnumerator.Current => _pageOversized![_pageOffset]; + + private bool SimpleNext() + { + if (_pageOffset + 1 < _pageCount) + { + cancellationToken.ThrowIfCancellationRequested(); + _pageOffset++; + return true; + } + return false; + } + + private T[]? _pageOversized; + private int _pageCount, _pageOffset, _pageIndex = -1; + private bool _isPooled; + private Task? _pending; + private Message? _pendingMessage; + private RedisValue _currentCursor, _nextCursor; + + private volatile State _state; + private enum State : byte + { + Initial, + Running, + Complete, + Disposed, + } + + private void ProcessReply(in ScanResult result, bool isInitial) + { + _currentCursor = _nextCursor; + _nextCursor = result.Cursor; + _pageOffset = isInitial ? parent.initialOffset - 1 : -1; + Recycle(ref _pageOversized, ref _isPooled); // recycle any existing data + _pageOversized = result.ValuesOversized ?? Array.Empty(); + _isPooled = result.IsPooled; + _pageCount = result.Count; + if (_nextCursor == RedisBase.CursorUtils.Origin) + { + // EOF + _pending = null; + _pendingMessage = null; + } + else + { + // start the next page right away + _pending = parent.GetNextPageAsync(this, _nextCursor, out _pendingMessage); + } + } + + /// + /// Try to move to the next item in the sequence. + /// + public bool MoveNext() => SimpleNext() || SlowNextSync(); + + private bool SlowNextSync() + { + var pending = SlowNextAsync(); + if (pending.IsCompletedSuccessfully) return pending.Result; + return Wait(pending.AsTask(), _pendingMessage!); + } + + private protected TResult Wait(Task pending, Message message) + { + if (!parent.redis.TryWait(pending)) ThrowTimeout(message); + return pending.Result; + } + + /// + /// Try to move to the next item in the sequence. + /// + public ValueTask MoveNextAsync() + { + if (SimpleNext()) return new ValueTask(true); + return SlowNextAsync(); + } + + private ValueTask SlowNextAsync() + { + cancellationToken.ThrowIfCancellationRequested(); + bool isInitial = false; + switch (_state) + { + case State.Initial: + _pending = parent.GetNextPageAsync(this, _nextCursor, out _pendingMessage); + isInitial = true; + _state = State.Running; + goto case State.Running; + case State.Running: + Task? pending; + while ((pending = _pending) != null && _state == State.Running) + { + if (!pending.IsCompleted) return AwaitedNextAsync(isInitial); + ProcessReply(pending.Result, isInitial); + isInitial = false; + if (SimpleNext()) return new ValueTask(true); + } + SetComplete(); + return default; + case State.Complete: + case State.Disposed: + default: + return default; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void ThrowTimeout(Message message) + { + try + { + throw ExceptionFactory.Timeout(parent.redis.multiplexer, null, message, parent.server); + } + catch (Exception ex) + { + TryAppendExceptionState(ex); + throw; + } + } + + private void TryAppendExceptionState(Exception ex) + { + try + { + var data = ex.Data; + data["Redis-page-size"] = parent.pageSize; + data["Redis-page-index"] = _pageIndex; + } + catch { } + } + + private async ValueTask AwaitedNextAsync(bool isInitial) + { + Task? pending; + while ((pending = _pending) != null && _state == State.Running) + { + ScanResult scanResult; + try + { + scanResult = await pending.WaitAsync(cancellationToken).ForAwait(); + } + catch (Exception ex) + { + TryAppendExceptionState(ex); + throw; + } + ProcessReply(scanResult, isInitial); + isInitial = false; + _pageIndex++; + if (SimpleNext()) return true; + } + SetComplete(); + return false; + } + + private static void Recycle(ref T[]? array, ref bool isPooled) + { + var tmp = array; + array = null; + if (tmp != null && tmp.Length != 0 && isPooled) + { + ArrayPool.Shared.Return(tmp); + } + isPooled = false; + } + + /// + /// Reset the enumerator. + /// + public void Reset() + { + if (_state == State.Disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + _nextCursor = _currentCursor = parent.initialCursor; + _pageOffset = parent.initialOffset; // don't -1 here; this makes it look "right" before incremented + _state = State.Initial; + Recycle(ref _pageOversized, ref _isPooled); + _pageOversized = Array.Empty(); + _isPooled = false; + _pageCount = 0; + _pending = null; + _pendingMessage = null; + } + + long IScanningCursor.Cursor => (long)_currentCursor; // this may fail on cluster-proxy; I'm OK with this for now + + int IScanningCursor.PageSize => parent.pageSize; + + int IScanningCursor.PageOffset => _pageOffset; + } + + /// + /// The cursor position. + /// + /// + /// This may fail on cluster-proxy - I'm OK with this for now. + /// + long IScanningCursor.Cursor => activeCursor?.Cursor ?? (long)initialCursor; + + int IScanningCursor.PageSize => pageSize; + + int IScanningCursor.PageOffset => activeCursor?.PageOffset ?? initialOffset; + + internal static CursorEnumerable From(RedisBase redis, ServerEndPoint? server, Task pending, int pageOffset) + => new SingleBlockEnumerable(redis, server, pending, pageOffset); + + private sealed class SingleBlockEnumerable : CursorEnumerable + { + private readonly Task _pending; + public SingleBlockEnumerable(RedisBase redis, ServerEndPoint? server, Task pending, int pageOffset) + : base(redis, server, 0, int.MaxValue, 0, pageOffset, default) + { + _pending = pending; + } + + private protected override Task GetNextPageAsync(IScanningCursor obj, RedisValue cursor, out Message? message) + { + message = null; + return AwaitedGetNextPageAsync(); + } + private async Task AwaitedGetNextPageAsync() + { + var arr = (await _pending.ForAwait()) ?? Array.Empty(); + return new ScanResult(RedisBase.CursorUtils.Origin, arr, arr.Length, false); + } + private protected override ResultProcessor? Processor => null; + private protected override Message? CreateMessage(in RedisValue cursor) => null; + } + } +} diff --git a/src/StackExchange.Redis/DebuggingAids.cs b/src/StackExchange.Redis/DebuggingAids.cs new file mode 100644 index 000000000..46e81611f --- /dev/null +++ b/src/StackExchange.Redis/DebuggingAids.cs @@ -0,0 +1,34 @@ +namespace StackExchange.Redis +{ +#if VERBOSE + partial class ConnectionMultiplexer + { + private readonly int epoch = Environment.TickCount; + + partial void OnTrace(string message, string category) + { + Debug.WriteLine(message, + ((Environment.TickCount - epoch)).ToString().PadLeft(5, ' ') + "ms on " + + Environment.CurrentManagedThreadId + " ~ " + category); + } + static partial void OnTraceWithoutContext(string message, string category) + { + Debug.WriteLine(message, Environment.CurrentManagedThreadId + " ~ " + category); + } + } +#endif + +#if LOGOUTPUT + partial class PhysicalConnection + { + partial void OnWrapForLogging(ref System.IO.Pipelines.IDuplexPipe pipe, string name, SocketManager mgr) + { + foreach(var c in System.IO.Path.GetInvalidFileNameChars()) + { + name = name.Replace(c, '_'); + } + pipe = new LoggingPipe(pipe, $"{name}.in.resp", $"{name}.out.resp", mgr); + } + } +#endif +} diff --git a/src/StackExchange.Redis/EndPointCollection.cs b/src/StackExchange.Redis/EndPointCollection.cs new file mode 100644 index 000000000..359f6d811 --- /dev/null +++ b/src/StackExchange.Redis/EndPointCollection.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Net; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace StackExchange.Redis +{ + /// + /// A list of endpoints. + /// + public sealed class EndPointCollection : Collection, IEnumerable + { + private static class DefaultPorts + { + public static int Standard => 6379; + public static int Ssl => 6380; + public static int Sentinel => 26379; + } + + /// + /// Create a new . + /// + public EndPointCollection() { } + + /// + /// Create a new . + /// + /// The endpoints to add to the collection. + public EndPointCollection(IList endpoints) : base(endpoints) { } + + /// + /// Format an . + /// + /// The endpoint to get a string representation for. + public static string ToString(EndPoint? endpoint) => Format.ToString(endpoint); + + /// + /// Attempt to parse a string into an . + /// + /// The endpoint string to parse. + public static EndPoint? TryParse(string endpoint) => Format.TryParseEndPoint(endpoint, out var result) ? result : null; + + /// + /// Adds a new endpoint to the list. + /// + /// The host:port string to add an endpoint for to the collection. + public void Add(string hostAndPort) + { + if (!Format.TryParseEndPoint(hostAndPort, out var endpoint)) + { + throw new ArgumentException($"Could not parse host and port from '{hostAndPort}'", nameof(hostAndPort)); + } + Add(endpoint); + } + + /// + /// Adds a new endpoint to the list. + /// + /// The host to add. + /// The port for to add. + public void Add(string host, int port) => Add(Format.ParseEndPoint(host, port)); + + /// + /// Adds a new endpoint to the list. + /// + /// The host to add. + /// The port for to add. + public void Add(IPAddress host, int port) => Add(new IPEndPoint(host, port)); + + /// + /// Try adding a new endpoint to the list. + /// + /// The endpoint to add. + /// if the endpoint was added, if not. + public bool TryAdd(EndPoint endpoint) + { + if (endpoint == null) + { + throw new ArgumentNullException(nameof(endpoint)); + } + + if (!Contains(endpoint)) + { + base.InsertItem(Count, endpoint); + return true; + } + else + { + return false; + } + } + + /// + /// See . + /// + /// The index to add into the collection at. + /// The item to insert at . + protected override void InsertItem(int index, EndPoint item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + if (Contains(item)) + { + throw new ArgumentException("EndPoints must be unique", nameof(item)); + } + + base.InsertItem(index, item); + } + + /// + /// See . + /// + /// The index to replace an endpoint at. + /// The item to replace the existing endpoint at . + protected override void SetItem(int index, EndPoint item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + int existingIndex; + try + { + existingIndex = IndexOf(item); + } + catch (NullReferenceException) + { + // mono has a nasty bug in DnsEndPoint.Equals; if they do bad things here: sorry, I can't help + existingIndex = -1; + } + if (existingIndex >= 0 && existingIndex != index) + { + throw new ArgumentException("EndPoints must be unique", nameof(item)); + } + base.SetItem(index, item); + } + + internal void SetDefaultPorts(ServerType? serverType, bool ssl = false) + { + int defaultPort = serverType switch + { + ServerType.Sentinel => DefaultPorts.Sentinel, + _ => ssl ? DefaultPorts.Ssl : DefaultPorts.Standard, + }; + + for (int i = 0; i < Count; i++) + { + switch (this[i]) + { + case DnsEndPoint dns when dns.Port == 0: + this[i] = new DnsEndPoint(dns.Host, defaultPort, dns.AddressFamily); + break; + case IPEndPoint ip when ip.Port == 0: + this[i] = new IPEndPoint(ip.Address, defaultPort); + break; + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public new IEnumerator GetEnumerator() + { + // this does *not* need to handle all threading scenarios; but we do + // want it to at least allow overwrites of existing endpoints without + // breaking the enumerator; in particular, this avoids a problem where + // ResolveEndPointsAsync swaps the addresses on us + for (int i = 0; i < Count; i++) + { + yield return this[i]; + } + } + + internal bool HasDnsEndPoints() + { + foreach (var endpoint in this) + { + if (endpoint is DnsEndPoint) + { + return true; + } + } + return false; + } + + internal async Task ResolveEndPointsAsync(ConnectionMultiplexer multiplexer, ILogger? log) + { + var cache = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (int i = 0; i < Count; i++) + { + if (this[i] is DnsEndPoint dns) + { + try + { + if (dns.Host == ".") + { + this[i] = new IPEndPoint(IPAddress.Loopback, dns.Port); + } + else if (cache.TryGetValue(dns.Host, out IPAddress? ip)) + { // use cache + this[i] = new IPEndPoint(ip, dns.Port); + } + else + { + log?.LogInformationUsingDnsToResolve(dns.Host); + var ips = await Dns.GetHostAddressesAsync(dns.Host).ObserveErrors().ForAwait(); + if (ips.Length == 1) + { + ip = ips[0]; + log?.LogInformationDnsResolutionResult(dns.Host, ip); + cache[dns.Host] = ip; + this[i] = new IPEndPoint(ip, dns.Port); + } + } + } + catch (Exception ex) + { + multiplexer.OnInternalError(ex); + log?.LogErrorDnsResolution(ex, ex.Message); + } + } + } + } + + internal EndPointCollection Clone() => new EndPointCollection(new List(Items)); + } +} diff --git a/src/StackExchange.Redis/EndPointEventArgs.cs b/src/StackExchange.Redis/EndPointEventArgs.cs new file mode 100644 index 000000000..bef0db9b6 --- /dev/null +++ b/src/StackExchange.Redis/EndPointEventArgs.cs @@ -0,0 +1,42 @@ +using System; +using System.Net; +using System.Text; + +namespace StackExchange.Redis +{ + /// + /// Event information related to redis endpoints. + /// + public class EndPointEventArgs : EventArgs, ICompletable + { + private readonly EventHandler? handler; + private readonly object sender; + + internal EndPointEventArgs(EventHandler? handler, object sender, EndPoint endpoint) + { + this.handler = handler; + this.sender = sender; + EndPoint = endpoint; + } + + /// + /// This constructor is only for testing purposes. + /// + /// The source of the event. + /// Redis endpoint. + public EndPointEventArgs(object sender, EndPoint endpoint) + : this(null, sender, endpoint) + { + } + + /// + /// The endpoint involved in this event (this can be null). + /// + public EndPoint EndPoint { get; } + + void ICompletable.AppendStormLog(StringBuilder sb) => + sb.Append("event, endpoint: ").Append(EndPoint != null ? Format.ToString(EndPoint) : "n/a"); + + bool ICompletable.TryComplete(bool isAsync) => ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync); + } +} diff --git a/src/StackExchange.Redis/Enums/Aggregate.cs b/src/StackExchange.Redis/Enums/Aggregate.cs new file mode 100644 index 000000000..41e1d435d --- /dev/null +++ b/src/StackExchange.Redis/Enums/Aggregate.cs @@ -0,0 +1,23 @@ +namespace StackExchange.Redis +{ + /// + /// Specifies how elements should be aggregated when combining sorted sets. + /// + public enum Aggregate + { + /// + /// The values of the combined elements are added. + /// + Sum, + + /// + /// The least value of the combined elements is used. + /// + Min, + + /// + /// The greatest value of the combined elements is used. + /// + Max, + } +} diff --git a/src/StackExchange.Redis/Enums/Bitwise.cs b/src/StackExchange.Redis/Enums/Bitwise.cs new file mode 100644 index 000000000..82e70b38a --- /dev/null +++ b/src/StackExchange.Redis/Enums/Bitwise.cs @@ -0,0 +1,53 @@ +namespace StackExchange.Redis +{ + /// + /// Bitwise operators + /// + public enum Bitwise + { + /// + /// And + /// + And, + + /// + /// Or + /// + Or, + + /// + /// Xor + /// + Xor, + + /// + /// Not + /// + Not, + + /// + /// DIFF operation: members of X that are not members of any of Y1, Y2, ... + /// Equivalent to X ∧ ¬(Y1 ∨ Y2 ∨ ...) + /// + Diff, + + /// + /// DIFF1 operation: members of one or more of Y1, Y2, ... that are not members of X + /// Equivalent to ¬X ∧ (Y1 ∨ Y2 ∨ ...) + /// + Diff1, + + /// + /// ANDOR operation: members of X that are also members of one or more of Y1, Y2, ... + /// Equivalent to X ∧ (Y1 ∨ Y2 ∨ ...) + /// + AndOr, + + /// + /// ONE operation: members of exactly one of X1, X2, ... + /// For two bitmaps this is equivalent to XOR. For more than two bitmaps, + /// this returns bits that are set in exactly one of the input bitmaps. + /// + One, + } +} diff --git a/src/StackExchange.Redis/Enums/ClientFlags.cs b/src/StackExchange.Redis/Enums/ClientFlags.cs new file mode 100644 index 000000000..eb687bba6 --- /dev/null +++ b/src/StackExchange.Redis/Enums/ClientFlags.cs @@ -0,0 +1,178 @@ +using System; +using System.ComponentModel; + +namespace StackExchange.Redis +{ + /// + /// The client flags can be a combination of: + /// + /// + /// A + /// Connection to be closed ASAP. + /// + /// + /// b + /// The client is waiting in a blocking operation. + /// + /// + /// c + /// Connection to be closed after writing entire reply. + /// + /// + /// d + /// A watched keys has been modified - EXEC will fail. + /// + /// + /// i + /// The client is waiting for a VM I/O (deprecated). + /// + /// + /// M + /// The client is a primary. + /// + /// + /// N + /// No specific flag set. + /// + /// + /// O + /// The client is a replica in MONITOR mode. + /// + /// + /// P + /// The client is a Pub/Sub subscriber. + /// + /// + /// r + /// The client is in readonly mode against a cluster node. + /// + /// + /// S + /// The client is a normal replica server. + /// + /// + /// u + /// The client is unblocked. + /// + /// + /// U + /// The client is unblocked. + /// + /// + /// x + /// The client is in a MULTI/EXEC context. + /// + /// + /// t + /// The client enabled keys tracking in order to perform client side caching. + /// + /// + /// R + /// The client tracking target client is invalid. + /// + /// + /// B + /// The client enabled broadcast tracking mode. + /// + /// + /// + /// + [Flags] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Compatibility")] + public enum ClientFlags : long + { + /// + /// No specific flag set. + /// + None = 0, + + /// + /// The client is a replica in MONITOR mode. + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(ReplicaMonitor) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + SlaveMonitor = 1, + + /// + /// The client is a replica in MONITOR mode. + /// + ReplicaMonitor = 1, // as an implementation detail, note that enum.ToString on [Flags] prefers *later* options when naming Flags + + /// + /// The client is a normal replica server. + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(Replica) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Slave = 2, + + /// + /// The client is a normal replica server. + /// + Replica = 2, // as an implementation detail, note that enum.ToString on [Flags] prefers *later* options when naming Flags + + /// + /// The client is a primary. + /// + Master = 4, + + /// + /// The client is in a MULTI/EXEC context. + /// + Transaction = 8, + + /// + /// The client is waiting in a blocking operation. + /// + Blocked = 16, + + /// + /// A watched keys has been modified - EXEC will fail. + /// + TransactionDoomed = 32, + + /// + /// Connection to be closed after writing entire reply. + /// + Closing = 64, + + /// + /// The client is unblocked. + /// + Unblocked = 128, + + /// + /// Connection to be closed ASAP. + /// + CloseASAP = 256, + + /// + /// The client is a Pub/Sub subscriber. + /// + PubSubSubscriber = 512, + + /// + /// The client is in readonly mode against a cluster node. + /// + ReadOnlyCluster = 1024, + + /// + /// The client is connected via a Unix domain socket. + /// + UnixDomainSocket = 2048, + + /// + /// The client enabled keys tracking in order to perform client side caching. + /// + KeysTracking = 4096, + + /// + /// The client tracking target client is invalid. + /// + TrackingTargetInvalid = 8192, + + /// + /// The client enabled broadcast tracking mode. + /// + BroadcastTracking = 16384, + } +} diff --git a/src/StackExchange.Redis/Enums/ClientType.cs b/src/StackExchange.Redis/Enums/ClientType.cs new file mode 100644 index 000000000..c2b003d9a --- /dev/null +++ b/src/StackExchange.Redis/Enums/ClientType.cs @@ -0,0 +1,34 @@ +using System; +using System.ComponentModel; + +namespace StackExchange.Redis +{ + /// + /// The class of the connection. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Compatibility")] + public enum ClientType + { + /// + /// Regular connections, including MONITOR connections. + /// + Normal = 0, + + /// + /// Replication connections. + /// + Replica = 1, // as an implementation detail, note that enum.ToString without [Flags] prefers *earlier* values + + /// + /// Replication connections. + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(Replica) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Slave = 1, + + /// + /// Subscription connections. + /// + PubSub = 2, + } +} diff --git a/src/StackExchange.Redis/Enums/CommandFlags.cs b/src/StackExchange.Redis/Enums/CommandFlags.cs new file mode 100644 index 000000000..83331a3c5 --- /dev/null +++ b/src/StackExchange.Redis/Enums/CommandFlags.cs @@ -0,0 +1,113 @@ +using System; +using System.ComponentModel; + +namespace StackExchange.Redis +{ + /// + /// Behaviour markers associated with a given command. + /// + [Flags] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Compatibility")] + public enum CommandFlags + { + /// + /// Default behaviour. + /// + None = 0, + + /// + /// From 2.0, this flag is not used. + /// + [Obsolete("From 2.0, this flag is not used, this will be removed in 3.0.", false)] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + HighPriority = 1, + + /// + /// The caller is not interested in the result; the caller will immediately receive a default-value + /// of the expected return type (this value is not indicative of anything at the server). + /// + FireAndForget = 2, + + /// + /// This operation should be performed on the primary if it is available, but read operations may + /// be performed on a replica if no primary is available. This is the default option. + /// + PreferMaster = 0, + +#if NET8_0_OR_GREATER + /// + /// This operation should be performed on the replica if it is available, but will be performed on + /// a primary if no replicas are available. Suitable for read operations only. + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(PreferReplica) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + PreferSlave = 8, +#endif + + /// + /// This operation should only be performed on the primary. + /// + DemandMaster = 4, + +#if !NET8_0_OR_GREATER + /// + /// This operation should be performed on the replica if it is available, but will be performed on + /// a primary if no replicas are available. Suitable for read operations only. + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(PreferReplica) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + PreferSlave = 8, +#endif + + /// + /// This operation should be performed on the replica if it is available, but will be performed on + /// a primary if no replicas are available. Suitable for read operations only. + /// + PreferReplica = 8, // note: we're using a 2-bit set here, which [Flags] formatting hates; position is doing the best we can for reasonable outcomes here + +#if NET8_0_OR_GREATER + /// + /// This operation should only be performed on a replica. Suitable for read operations only. + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(DemandReplica) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + DemandSlave = 12, +#endif + + /// + /// This operation should only be performed on a replica. Suitable for read operations only. + /// + DemandReplica = 12, // note: we're using a 2-bit set here, which [Flags] formatting hates; position is doing the best we can for reasonable outcomes here + +#if !NET8_0_OR_GREATER + /// + /// This operation should only be performed on a replica. Suitable for read operations only. + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(DemandReplica) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + DemandSlave = 12, +#endif + + // 16: reserved for additional "demand/prefer" options + + // 32: used for "asking" flag; never user-specified, so not visible on the public API + + /// + /// Indicates that this operation should not be forwarded to other servers as a result of an ASK or MOVED response. + /// + NoRedirect = 64, + + // 128: used for "internal call"; never user-specified, so not visible on the public API + + // 256: used for "script unavailable"; never user-specified, so not visible on the public API + + /// + /// Indicates that script-related operations should use EVAL, not SCRIPT LOAD + EVALSHA. + /// + NoScriptCache = 512, + + // 1024: Removed - was used for async timeout checks; never user-specified, so not visible on the public API + + // 2048: Use subscription connection type; never user-specified, so not visible on the public API + } +} diff --git a/src/StackExchange.Redis/Enums/CommandStatus.cs b/src/StackExchange.Redis/Enums/CommandStatus.cs new file mode 100644 index 000000000..c4de5753d --- /dev/null +++ b/src/StackExchange.Redis/Enums/CommandStatus.cs @@ -0,0 +1,28 @@ +namespace StackExchange.Redis +{ + /// + /// Track status of a command while communicating with Redis. + /// + public enum CommandStatus + { + /// + /// Command status unknown. + /// + Unknown, + + /// + /// ConnectionMultiplexer has not yet started writing this command to Redis. + /// + WaitingToBeSent, + + /// + /// Command has been sent to Redis. + /// + Sent, + + /// + /// Command is in the backlog, waiting to be processed and written to Redis. + /// + WaitingInBacklog, + } +} diff --git a/src/StackExchange.Redis/Enums/ConnectionFailureType.cs b/src/StackExchange.Redis/Enums/ConnectionFailureType.cs new file mode 100644 index 000000000..55eeacef6 --- /dev/null +++ b/src/StackExchange.Redis/Enums/ConnectionFailureType.cs @@ -0,0 +1,63 @@ +namespace StackExchange.Redis +{ + /// + /// The known types of connection failure. + /// + public enum ConnectionFailureType + { + /// + /// This event is not a failure. + /// + None, + + /// + /// No viable connections were available for this operation. + /// + UnableToResolvePhysicalConnection, + + /// + /// The socket for this connection failed. + /// + SocketFailure, + + /// + /// Either SSL Stream or Redis authentication failed. + /// + AuthenticationFailure, + + /// + /// An unexpected response was received from the server. + /// + ProtocolFailure, + + /// + /// An unknown internal error occurred. + /// + InternalFailure, + + /// + /// The socket was closed. + /// + SocketClosed, + + /// + /// The socket was closed. + /// + ConnectionDisposed, + + /// + /// The database is loading and is not available for use. + /// + Loading, + + /// + /// It has not been possible to create an initial connection to the redis server(s). + /// + UnableToConnect, + + /// + /// High-integrity mode was enabled, and a failure was detected. + /// + ResponseIntegrityFailure, + } +} diff --git a/src/StackExchange.Redis/Enums/ConnectionType.cs b/src/StackExchange.Redis/Enums/ConnectionType.cs new file mode 100644 index 000000000..8db655c08 --- /dev/null +++ b/src/StackExchange.Redis/Enums/ConnectionType.cs @@ -0,0 +1,23 @@ +namespace StackExchange.Redis +{ + /// + /// The type of a connection. + /// + public enum ConnectionType + { + /// + /// Not connection-type related. + /// + None = 0, + + /// + /// An interactive connection handles request/response commands for accessing data on demand. + /// + Interactive, + + /// + /// A subscriber connection receives unsolicited messages from the server as pub/sub events occur. + /// + Subscription, + } +} diff --git a/src/StackExchange.Redis/Enums/Exclude.cs b/src/StackExchange.Redis/Enums/Exclude.cs new file mode 100644 index 000000000..912a2af95 --- /dev/null +++ b/src/StackExchange.Redis/Enums/Exclude.cs @@ -0,0 +1,32 @@ +using System; + +namespace StackExchange.Redis +{ + /// + /// When performing a range query, by default the start / stop limits are inclusive; + /// however, both can also be specified separately as exclusive. + /// + [Flags] + public enum Exclude + { + /// + /// Both start and stop are inclusive. + /// + None = 0, + + /// + /// Start is exclusive, stop is inclusive. + /// + Start = 1, + + /// + /// Start is inclusive, stop is exclusive. + /// + Stop = 2, + + /// + /// Both start and stop are exclusive. + /// + Both = Start | Stop, + } +} diff --git a/src/StackExchange.Redis/Enums/ExpireResult.cs b/src/StackExchange.Redis/Enums/ExpireResult.cs new file mode 100644 index 000000000..6211492e6 --- /dev/null +++ b/src/StackExchange.Redis/Enums/ExpireResult.cs @@ -0,0 +1,27 @@ +namespace StackExchange.Redis; + +/// +/// Specifies the result of operation to set expire time. +/// +public enum ExpireResult +{ + /// + /// Field deleted because the specified expiration time is due. + /// + Due = 2, + + /// + /// Expiration time/duration updated successfully. + /// + Success = 1, + + /// + /// Expiration not set because of a specified NX | XX | GT | LT condition not met. + /// + ConditionNotMet = 0, + + /// + /// No such field. + /// + NoSuchField = -2, +} diff --git a/src/StackExchange.Redis/Enums/ExpireWhen.cs b/src/StackExchange.Redis/Enums/ExpireWhen.cs new file mode 100644 index 000000000..2637e7625 --- /dev/null +++ b/src/StackExchange.Redis/Enums/ExpireWhen.cs @@ -0,0 +1,46 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// Specifies when to set the expiry for a key. +/// +public enum ExpireWhen +{ + /// + /// Set expiry whether or not there is an existing expiry. + /// + Always, + + /// + /// Set expiry only when the new expiry is greater than current one. + /// + GreaterThanCurrentExpiry, + + /// + /// Set expiry only when the key has an existing expiry. + /// + HasExpiry, + + /// + /// Set expiry only when the key has no expiry. + /// + HasNoExpiry, + + /// + /// Set expiry only when the new expiry is less than current one. + /// + LessThanCurrentExpiry, +} + +internal static class ExpiryOptionExtensions +{ + internal static RedisValue ToLiteral(this ExpireWhen op) => op switch + { + ExpireWhen.HasNoExpiry => RedisLiterals.NX, + ExpireWhen.HasExpiry => RedisLiterals.XX, + ExpireWhen.GreaterThanCurrentExpiry => RedisLiterals.GT, + ExpireWhen.LessThanCurrentExpiry => RedisLiterals.LT, + _ => throw new ArgumentOutOfRangeException(nameof(op)), + }; +} diff --git a/src/StackExchange.Redis/Enums/ExportOptions.cs b/src/StackExchange.Redis/Enums/ExportOptions.cs new file mode 100644 index 000000000..594651955 --- /dev/null +++ b/src/StackExchange.Redis/Enums/ExportOptions.cs @@ -0,0 +1,41 @@ +using System; + +namespace StackExchange.Redis +{ + /// + /// Which settings to export. + /// + [Flags] + public enum ExportOptions + { + /// + /// No options. + /// + None = 0, + + /// + /// The output of INFO. + /// + Info = 1, + + /// + /// The output of CONFIG GET *. + /// + Config = 2, + + /// + /// The output of CLIENT LIST. + /// + Client = 4, + + /// + /// The output of CLUSTER NODES. + /// + Cluster = 8, + + /// + /// Everything available. + /// + All = -1, + } +} diff --git a/src/StackExchange.Redis/Enums/GeoUnit.cs b/src/StackExchange.Redis/Enums/GeoUnit.cs new file mode 100644 index 000000000..99ab0a143 --- /dev/null +++ b/src/StackExchange.Redis/Enums/GeoUnit.cs @@ -0,0 +1,42 @@ +using System; + +namespace StackExchange.Redis +{ + /// + /// Units associated with Geo Commands. + /// + public enum GeoUnit + { + /// + /// Meters. + /// + Meters, + + /// + /// Kilometers. + /// + Kilometers, + + /// + /// Miles. + /// + Miles, + + /// + /// Feet. + /// + Feet, + } + + internal static class GeoUnitExtensions + { + internal static RedisValue ToLiteral(this GeoUnit unit) => unit switch + { + GeoUnit.Feet => RedisLiterals.ft, + GeoUnit.Kilometers => RedisLiterals.km, + GeoUnit.Meters => RedisLiterals.m, + GeoUnit.Miles => RedisLiterals.mi, + _ => throw new ArgumentOutOfRangeException(nameof(unit)), + }; + } +} diff --git a/src/StackExchange.Redis/Enums/ListSide.cs b/src/StackExchange.Redis/Enums/ListSide.cs new file mode 100644 index 000000000..8d326a8af --- /dev/null +++ b/src/StackExchange.Redis/Enums/ListSide.cs @@ -0,0 +1,30 @@ +using System; + +namespace StackExchange.Redis +{ + /// + /// Specifies what side of the list to refer to. + /// + public enum ListSide + { + /// + /// The head of the list. + /// + Left, + + /// + /// The tail of the list. + /// + Right, + } + + internal static class ListSideExtensions + { + internal static RedisValue ToLiteral(this ListSide side) => side switch + { + ListSide.Left => RedisLiterals.LEFT, + ListSide.Right => RedisLiterals.RIGHT, + _ => throw new ArgumentOutOfRangeException(nameof(side)), + }; + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/MigrateOptions.cs b/src/StackExchange.Redis/Enums/MigrateOptions.cs similarity index 79% rename from StackExchange.Redis/StackExchange/Redis/MigrateOptions.cs rename to src/StackExchange.Redis/Enums/MigrateOptions.cs index 68095bd77..fbfdaa731 100644 --- a/StackExchange.Redis/StackExchange/Redis/MigrateOptions.cs +++ b/src/StackExchange.Redis/Enums/MigrateOptions.cs @@ -3,22 +3,24 @@ namespace StackExchange.Redis { /// - /// Additional options for the MIGRATE command + /// Additional options for the MIGRATE command. /// [Flags] public enum MigrateOptions { /// - /// No options specified + /// No options specified. /// None = 0, + /// /// Do not remove the key from the local instance. /// Copy = 1, + /// /// Replace existing key on the remote instance. /// - Replace = 2 + Replace = 2, } } diff --git a/src/StackExchange.Redis/Enums/Order.cs b/src/StackExchange.Redis/Enums/Order.cs new file mode 100644 index 000000000..99d989006 --- /dev/null +++ b/src/StackExchange.Redis/Enums/Order.cs @@ -0,0 +1,30 @@ +using System; + +namespace StackExchange.Redis +{ + /// + /// The direction in which to sequence elements. + /// + public enum Order + { + /// + /// Ordered from low values to high values. + /// + Ascending, + + /// + /// Ordered from high values to low values. + /// + Descending, + } + + internal static class OrderExtensions + { + internal static RedisValue ToLiteral(this Order order) => order switch + { + Order.Ascending => RedisLiterals.ASC, + Order.Descending => RedisLiterals.DESC, + _ => throw new ArgumentOutOfRangeException(nameof(order)), + }; + } +} diff --git a/src/StackExchange.Redis/Enums/PersistResult.cs b/src/StackExchange.Redis/Enums/PersistResult.cs new file mode 100644 index 000000000..91fdf9fa7 --- /dev/null +++ b/src/StackExchange.Redis/Enums/PersistResult.cs @@ -0,0 +1,22 @@ +namespace StackExchange.Redis; + +/// +/// Specifies the result of operation to remove the expire time. +/// +public enum PersistResult +{ + /// + /// Expiration removed successfully. + /// + Success = 1, + + /// + /// Expiration not removed because of a specified NX | XX | GT | LT condition not met. + /// + ConditionNotMet = -1, + + /// + /// No such field. + /// + NoSuchField = -2, +} diff --git a/src/StackExchange.Redis/Enums/PositionKind.cs b/src/StackExchange.Redis/Enums/PositionKind.cs new file mode 100644 index 000000000..81a705090 --- /dev/null +++ b/src/StackExchange.Redis/Enums/PositionKind.cs @@ -0,0 +1,9 @@ +namespace StackExchange.Redis +{ + internal enum PositionKind + { + Beginning = 0, + Explicit = 1, + New = 2, + } +} diff --git a/src/StackExchange.Redis/Enums/Proxy.cs b/src/StackExchange.Redis/Enums/Proxy.cs new file mode 100644 index 000000000..9dc1d3770 --- /dev/null +++ b/src/StackExchange.Redis/Enums/Proxy.cs @@ -0,0 +1,56 @@ +namespace StackExchange.Redis +{ + /// + /// Specifies the proxy that is being used to communicate to redis. + /// + public enum Proxy + { + /// + /// Direct communication to the redis server(s). + /// + None, + + /// + /// Communication via twemproxy. + /// + Twemproxy, + + /// + /// Communication via envoyproxy. + /// + Envoyproxy, + } + + internal static class ProxyExtensions + { + /// + /// Whether a proxy supports databases (e.g. database > 0). + /// + internal static bool SupportsDatabases(this Proxy proxy) => proxy switch + { + Proxy.Twemproxy => false, + Proxy.Envoyproxy => false, + _ => true, + }; + + /// + /// Whether a proxy supports pub/sub. + /// + internal static bool SupportsPubSub(this Proxy proxy) => proxy switch + { + Proxy.Twemproxy => false, + Proxy.Envoyproxy => false, + _ => true, + }; + + /// + /// Whether a proxy supports the ConnectionMultiplexer.GetServer. + /// + internal static bool SupportsServerApi(this Proxy proxy) => proxy switch + { + Proxy.Twemproxy => false, + Proxy.Envoyproxy => false, + _ => true, + }; + } +} diff --git a/src/StackExchange.Redis/Enums/RedisCommand.cs b/src/StackExchange.Redis/Enums/RedisCommand.cs new file mode 100644 index 000000000..7a0c2f08d --- /dev/null +++ b/src/StackExchange.Redis/Enums/RedisCommand.cs @@ -0,0 +1,545 @@ +using System; + +namespace StackExchange.Redis; + +internal enum RedisCommand +{ + NONE, // must be first for "zero reasons" + + APPEND, + ASKING, + AUTH, + + BGREWRITEAOF, + BGSAVE, + BITCOUNT, + BITOP, + BITPOS, + BLPOP, + BRPOP, + BRPOPLPUSH, + + CLIENT, + CLUSTER, + CONFIG, + COPY, + COMMAND, + + DBSIZE, + DEBUG, + DECR, + DECRBY, + DEL, + DISCARD, + DUMP, + + ECHO, + EVAL, + EVALSHA, + EVAL_RO, + EVALSHA_RO, + EXEC, + EXISTS, + EXPIRE, + EXPIREAT, + EXPIRETIME, + + FLUSHALL, + FLUSHDB, + + GEOADD, + GEODIST, + GEOHASH, + GEOPOS, + GEORADIUS, + GEORADIUSBYMEMBER, + GEOSEARCH, + GEOSEARCHSTORE, + + GET, + GETBIT, + GETDEL, + GETEX, + GETRANGE, + GETSET, + + HDEL, + HELLO, + HEXISTS, + HEXPIRE, + HEXPIREAT, + HEXPIRETIME, + HGET, + HGETEX, + HGETDEL, + HGETALL, + HINCRBY, + HINCRBYFLOAT, + HKEYS, + HLEN, + HMGET, + HMSET, + HPERSIST, + HPEXPIRE, + HPEXPIREAT, + HPEXPIRETIME, + HPTTL, + HRANDFIELD, + HSCAN, + HSET, + HSETEX, + HSETNX, + HSTRLEN, + HVALS, + + INCR, + INCRBY, + INCRBYFLOAT, + INFO, + + KEYS, + + LASTSAVE, + LATENCY, + LCS, + LINDEX, + LINSERT, + LLEN, + LMOVE, + LMPOP, + LPOP, + LPOS, + LPUSH, + LPUSHX, + LRANGE, + LREM, + LSET, + LTRIM, + + MEMORY, + MGET, + MIGRATE, + MONITOR, + MOVE, + MSET, + MSETNX, + MULTI, + + OBJECT, + + PERSIST, + PEXPIRE, + PEXPIREAT, + PEXPIRETIME, + PFADD, + PFCOUNT, + PFMERGE, + PING, + PSETEX, + PSUBSCRIBE, + PTTL, + PUBLISH, + PUBSUB, + PUNSUBSCRIBE, + + QUIT, + + RANDOMKEY, + READONLY, + READWRITE, + RENAME, + RENAMENX, + REPLICAOF, + RESTORE, + ROLE, + RPOP, + RPOPLPUSH, + RPUSH, + RPUSHX, + + SADD, + SAVE, + SCAN, + SCARD, + SCRIPT, + SDIFF, + SDIFFSTORE, + SELECT, + SENTINEL, + SET, + SETBIT, + SETEX, + SETNX, + SETRANGE, + SHUTDOWN, + SINTER, + SINTERCARD, + SINTERSTORE, + SISMEMBER, + SLAVEOF, + SLOWLOG, + SMEMBERS, + SMISMEMBER, + SMOVE, + SORT, + SORT_RO, + SPOP, + SPUBLISH, + SRANDMEMBER, + SREM, + STRLEN, + SUBSCRIBE, + SUNION, + SUNIONSTORE, + SSCAN, + SSUBSCRIBE, + SUNSUBSCRIBE, + SWAPDB, + SYNC, + + TIME, + TOUCH, + TTL, + TYPE, + + UNLINK, + UNSUBSCRIBE, + UNWATCH, + + VADD, + VCARD, + VDIM, + VEMB, + VGETATTR, + VINFO, + VISMEMBER, + VLINKS, + VRANDMEMBER, + VREM, + VSETATTR, + VSIM, + + WATCH, + + XACK, + XACKDEL, + XADD, + XAUTOCLAIM, + XCLAIM, + XDEL, + XDELEX, + XGROUP, + XINFO, + XLEN, + XPENDING, + XRANGE, + XREAD, + XREADGROUP, + XREVRANGE, + XTRIM, + + ZADD, + ZCARD, + ZCOUNT, + ZDIFF, + ZDIFFSTORE, + ZINCRBY, + ZINTER, + ZINTERCARD, + ZINTERSTORE, + ZLEXCOUNT, + ZMPOP, + ZMSCORE, + ZPOPMAX, + ZPOPMIN, + ZRANDMEMBER, + ZRANGE, + ZRANGEBYLEX, + ZRANGEBYSCORE, + ZRANGESTORE, + ZRANK, + ZREM, + ZREMRANGEBYLEX, + ZREMRANGEBYRANK, + ZREMRANGEBYSCORE, + ZREVRANGE, + ZREVRANGEBYLEX, + ZREVRANGEBYSCORE, + ZREVRANK, + ZSCAN, + ZSCORE, + ZUNION, + ZUNIONSTORE, + + UNKNOWN, +} + +internal static class RedisCommandExtensions +{ + /// + /// Gets whether a given command can be issued only to a primary, or if any server is eligible. + /// + /// The to check. + /// if the command is primary-only, otherwise. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "No, it'd be ridiculous.")] + internal static bool IsPrimaryOnly(this RedisCommand command) + { + switch (command) + { + // Commands that can only be issued to a primary (writable) server + // If a command *may* be writable (e.g. an EVAL script), it should *not* be primary-only + // because that'd block a legitimate use case of a read-only script on replica servers, + // for example spreading load via a .DemandReplica flag in the caller. + // Basically: would it fail on a read-only replica in 100% of cases? Then it goes in the list. + case RedisCommand.APPEND: + case RedisCommand.BITOP: + case RedisCommand.BLPOP: + case RedisCommand.BRPOP: + case RedisCommand.BRPOPLPUSH: + case RedisCommand.DECR: + case RedisCommand.DECRBY: + case RedisCommand.DEL: + case RedisCommand.EXPIRE: + case RedisCommand.EXPIREAT: + case RedisCommand.FLUSHALL: + case RedisCommand.FLUSHDB: + case RedisCommand.GEOSEARCHSTORE: + case RedisCommand.GETDEL: + case RedisCommand.GETEX: + case RedisCommand.GETSET: + case RedisCommand.HDEL: + case RedisCommand.HEXPIRE: + case RedisCommand.HEXPIREAT: + case RedisCommand.HGETDEL: + case RedisCommand.HGETEX: + case RedisCommand.HINCRBY: + case RedisCommand.HINCRBYFLOAT: + case RedisCommand.HMSET: + case RedisCommand.HPERSIST: + case RedisCommand.HPEXPIRE: + case RedisCommand.HPEXPIREAT: + case RedisCommand.HSET: + case RedisCommand.HSETEX: + case RedisCommand.HSETNX: + case RedisCommand.INCR: + case RedisCommand.INCRBY: + case RedisCommand.INCRBYFLOAT: + case RedisCommand.LINSERT: + case RedisCommand.LMOVE: + case RedisCommand.LMPOP: + case RedisCommand.LPOP: + case RedisCommand.LPUSH: + case RedisCommand.LPUSHX: + case RedisCommand.LREM: + case RedisCommand.LSET: + case RedisCommand.LTRIM: + case RedisCommand.MIGRATE: + case RedisCommand.MOVE: + case RedisCommand.MSET: + case RedisCommand.MSETNX: + case RedisCommand.PERSIST: + case RedisCommand.PEXPIRE: + case RedisCommand.PEXPIREAT: + case RedisCommand.PFADD: + case RedisCommand.PFMERGE: + case RedisCommand.PSETEX: + case RedisCommand.RENAME: + case RedisCommand.RENAMENX: + case RedisCommand.RESTORE: + case RedisCommand.RPOP: + case RedisCommand.RPOPLPUSH: + case RedisCommand.RPUSH: + case RedisCommand.RPUSHX: + case RedisCommand.SADD: + case RedisCommand.SDIFFSTORE: + case RedisCommand.SET: + case RedisCommand.SETBIT: + case RedisCommand.SETEX: + case RedisCommand.SETNX: + case RedisCommand.SETRANGE: + case RedisCommand.SINTERSTORE: + case RedisCommand.SMOVE: + case RedisCommand.SPOP: + case RedisCommand.SREM: + case RedisCommand.SUNIONSTORE: + case RedisCommand.SWAPDB: + case RedisCommand.TOUCH: + case RedisCommand.UNLINK: + case RedisCommand.VADD: + case RedisCommand.VREM: + case RedisCommand.VSETATTR: + case RedisCommand.XAUTOCLAIM: + case RedisCommand.ZADD: + case RedisCommand.ZDIFFSTORE: + case RedisCommand.ZINTERSTORE: + case RedisCommand.ZINCRBY: + case RedisCommand.ZMPOP: + case RedisCommand.ZPOPMAX: + case RedisCommand.ZPOPMIN: + case RedisCommand.ZRANGESTORE: + case RedisCommand.ZREM: + case RedisCommand.ZREMRANGEBYLEX: + case RedisCommand.ZREMRANGEBYRANK: + case RedisCommand.ZREMRANGEBYSCORE: + case RedisCommand.ZUNIONSTORE: + return true; + // Commands that can be issued anywhere + case RedisCommand.NONE: + case RedisCommand.ASKING: + case RedisCommand.AUTH: + case RedisCommand.BGREWRITEAOF: + case RedisCommand.BGSAVE: + case RedisCommand.BITCOUNT: + case RedisCommand.BITPOS: + case RedisCommand.CLIENT: + case RedisCommand.CLUSTER: + case RedisCommand.COMMAND: + case RedisCommand.CONFIG: + case RedisCommand.DBSIZE: + case RedisCommand.DEBUG: + case RedisCommand.DISCARD: + case RedisCommand.DUMP: + case RedisCommand.ECHO: + case RedisCommand.EVAL: + case RedisCommand.EVALSHA: + case RedisCommand.EVAL_RO: + case RedisCommand.EVALSHA_RO: + case RedisCommand.EXEC: + case RedisCommand.EXISTS: + case RedisCommand.EXPIRETIME: + case RedisCommand.GEODIST: + case RedisCommand.GEOHASH: + case RedisCommand.GEOPOS: + case RedisCommand.GEORADIUS: + case RedisCommand.GEORADIUSBYMEMBER: + case RedisCommand.GEOSEARCH: + case RedisCommand.GET: + case RedisCommand.GETBIT: + case RedisCommand.GETRANGE: + case RedisCommand.HELLO: + case RedisCommand.HEXISTS: + case RedisCommand.HEXPIRETIME: + case RedisCommand.HGET: + case RedisCommand.HGETALL: + case RedisCommand.HKEYS: + case RedisCommand.HLEN: + case RedisCommand.HMGET: + case RedisCommand.HPEXPIRETIME: + case RedisCommand.HPTTL: + case RedisCommand.HRANDFIELD: + case RedisCommand.HSCAN: + case RedisCommand.HSTRLEN: + case RedisCommand.HVALS: + case RedisCommand.INFO: + case RedisCommand.KEYS: + case RedisCommand.LASTSAVE: + case RedisCommand.LATENCY: + case RedisCommand.LCS: + case RedisCommand.LINDEX: + case RedisCommand.LLEN: + case RedisCommand.LPOS: + case RedisCommand.LRANGE: + case RedisCommand.MEMORY: + case RedisCommand.MGET: + case RedisCommand.MONITOR: + case RedisCommand.MULTI: + case RedisCommand.OBJECT: + case RedisCommand.PEXPIRETIME: + case RedisCommand.PFCOUNT: + case RedisCommand.PING: + case RedisCommand.PSUBSCRIBE: + case RedisCommand.PTTL: + case RedisCommand.PUBLISH: + case RedisCommand.PUBSUB: + case RedisCommand.PUNSUBSCRIBE: + case RedisCommand.QUIT: + case RedisCommand.RANDOMKEY: + case RedisCommand.READONLY: + case RedisCommand.READWRITE: + case RedisCommand.REPLICAOF: + case RedisCommand.ROLE: + case RedisCommand.SAVE: + case RedisCommand.SCAN: + case RedisCommand.SCARD: + case RedisCommand.SCRIPT: + case RedisCommand.SDIFF: + case RedisCommand.SELECT: + case RedisCommand.SENTINEL: + case RedisCommand.SHUTDOWN: + case RedisCommand.SINTER: + case RedisCommand.SINTERCARD: + case RedisCommand.SISMEMBER: + case RedisCommand.SLAVEOF: + case RedisCommand.SLOWLOG: + case RedisCommand.SMEMBERS: + case RedisCommand.SMISMEMBER: + case RedisCommand.SORT_RO: + case RedisCommand.SPUBLISH: + case RedisCommand.SRANDMEMBER: + case RedisCommand.SSUBSCRIBE: + case RedisCommand.STRLEN: + case RedisCommand.SUBSCRIBE: + case RedisCommand.SUNION: + case RedisCommand.SUNSUBSCRIBE: + case RedisCommand.SSCAN: + case RedisCommand.SYNC: + case RedisCommand.TIME: + case RedisCommand.TTL: + case RedisCommand.TYPE: + case RedisCommand.UNSUBSCRIBE: + case RedisCommand.UNWATCH: + case RedisCommand.WATCH: + case RedisCommand.XINFO: + case RedisCommand.XLEN: + case RedisCommand.XPENDING: + case RedisCommand.XRANGE: + case RedisCommand.XREAD: + case RedisCommand.XREVRANGE: + case RedisCommand.ZCARD: + case RedisCommand.ZCOUNT: + case RedisCommand.ZDIFF: + case RedisCommand.ZINTER: + case RedisCommand.ZINTERCARD: + case RedisCommand.ZLEXCOUNT: + case RedisCommand.ZMSCORE: + case RedisCommand.ZRANDMEMBER: + case RedisCommand.ZRANGE: + case RedisCommand.ZRANGEBYLEX: + case RedisCommand.ZRANGEBYSCORE: + case RedisCommand.ZRANK: + case RedisCommand.ZREVRANGE: + case RedisCommand.ZREVRANGEBYLEX: + case RedisCommand.ZREVRANGEBYSCORE: + case RedisCommand.ZREVRANK: + case RedisCommand.ZSCAN: + case RedisCommand.ZSCORE: + case RedisCommand.ZUNION: + case RedisCommand.UNKNOWN: + case RedisCommand.VCARD: + case RedisCommand.VDIM: + case RedisCommand.VEMB: + case RedisCommand.VGETATTR: + case RedisCommand.VINFO: + case RedisCommand.VISMEMBER: + case RedisCommand.VLINKS: + case RedisCommand.VRANDMEMBER: + case RedisCommand.VSIM: + // Writable commands, but allowed for the writable-replicas scenario + case RedisCommand.COPY: + case RedisCommand.GEOADD: + case RedisCommand.SORT: + case RedisCommand.XACK: + case RedisCommand.XACKDEL: + case RedisCommand.XADD: + case RedisCommand.XCLAIM: + case RedisCommand.XDEL: + case RedisCommand.XDELEX: + case RedisCommand.XGROUP: + case RedisCommand.XREADGROUP: + case RedisCommand.XTRIM: + return false; + default: + throw new ArgumentOutOfRangeException(nameof(command), $"Every RedisCommand must be defined in Message.IsPrimaryOnly, unknown command '{command}' encountered."); + } + } +} diff --git a/src/StackExchange.Redis/Enums/RedisType.cs b/src/StackExchange.Redis/Enums/RedisType.cs new file mode 100644 index 000000000..f1da87505 --- /dev/null +++ b/src/StackExchange.Redis/Enums/RedisType.cs @@ -0,0 +1,69 @@ +namespace StackExchange.Redis +{ + /// + /// The intrinsic data-types supported by redis. + /// + /// + public enum RedisType + { + /// + /// The specified key does not exist. + /// + None, + + /// + /// Strings are the most basic kind of Redis value. Redis Strings are binary safe, this means that + /// a Redis string can contain any kind of data, for instance a JPEG image or a serialized Ruby object. + /// A String value can be at max 512 Megabytes in length. + /// + /// + String, + + /// + /// Redis Lists are simply lists of strings, sorted by insertion order. + /// It is possible to add elements to a Redis List pushing new elements on the head (on the left) or + /// on the tail (on the right) of the list. + /// + /// + List, + + /// + /// Redis Sets are an unordered collection of Strings. It is possible to add, remove, and test for + /// existence of members in O(1) (constant time regardless of the number of elements contained inside the Set). + /// Redis Sets have the desirable property of not allowing repeated members. + /// Adding the same element multiple times will result in a set having a single copy of this element. + /// Practically speaking this means that adding a member does not require a check if exists then add operation. + /// + /// + Set, + + /// + /// Redis Sorted Sets are, similarly to Redis Sets, non repeating collections of Strings. + /// The difference is that every member of a Sorted Set is associated with score, that is used + /// in order to take the sorted set ordered, from the smallest to the greatest score. + /// While members are unique, scores may be repeated. + /// + /// + SortedSet, + + /// + /// Redis Hashes are maps between string fields and string values, so they are the perfect data type + /// to represent objects (e.g. A User with a number of fields like name, surname, age, and so forth). + /// + /// + Hash, + + /// + /// A Redis Stream is a data structure which models the behavior of an append only log but it has more + /// advanced features for manipulating the data contained within the stream. Each entry in a + /// stream contains a unique message ID and a list of name/value pairs containing the entry's data. + /// + /// + Stream, + + /// + /// The data-type was not recognised by the client library. + /// + Unknown, + } +} diff --git a/src/StackExchange.Redis/Enums/ReplicationChangeOptions.cs b/src/StackExchange.Redis/Enums/ReplicationChangeOptions.cs new file mode 100644 index 000000000..897ebbb6c --- /dev/null +++ b/src/StackExchange.Redis/Enums/ReplicationChangeOptions.cs @@ -0,0 +1,45 @@ +using System; +using System.ComponentModel; + +namespace StackExchange.Redis +{ + /// + /// Additional operations to perform when making a server a primary. + /// + [Flags] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Compatibility")] + public enum ReplicationChangeOptions + { + /// + /// No additional operations. + /// + None = 0, + + /// + /// Set the tie-breaker key on all available primaries, to specify this server. + /// + SetTiebreaker = 1, + + /// + /// Broadcast to the pub-sub channel to listening clients to reconfigure themselves. + /// + Broadcast = 2, + + /// + /// Issue a REPLICAOF to all other known nodes, making this primary of all. + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(ReplicateToOtherEndpoints) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + EnslaveSubordinates = 4, + + /// + /// Issue a REPLICAOF to all other known nodes, making this primary of all. + /// + ReplicateToOtherEndpoints = 4, // note ToString prefers *later* options + + /// + /// All additional operations. + /// + All = SetTiebreaker | Broadcast | ReplicateToOtherEndpoints, + } +} diff --git a/src/StackExchange.Redis/Enums/ResultType.cs b/src/StackExchange.Redis/Enums/ResultType.cs new file mode 100644 index 000000000..63e267a91 --- /dev/null +++ b/src/StackExchange.Redis/Enums/ResultType.cs @@ -0,0 +1,108 @@ +using System; +using System.ComponentModel; + +namespace StackExchange.Redis +{ + /// + /// The underlying result type as defined by Redis. + /// + public enum ResultType : byte + { + /// + /// No value was received. + /// + None = 0, + + // RESP 2 + + /// + /// Basic strings typically represent status results such as "OK". + /// + SimpleString = 1, + + /// + /// Error strings represent invalid operation results from the server. + /// + Error = 2, + + /// + /// Integers are returned for count operations and some integer-based increment operations. + /// + Integer = 3, + + /// + /// Bulk strings represent typical user content values. + /// + BulkString = 4, + + /// + /// Array of results (former Multi-bulk). + /// + Array = 5, + + /// + /// Multi-bulk replies represent complex results such as arrays. + /// + [Obsolete("Please use " + nameof(Array))] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + MultiBulk = 5, + + // RESP3: https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md + + // note: we will arrange the values such as the last 3 bits are the RESP2 equivalent, + // and then we count up from there + + /// + /// A single null value replacing RESP v2 blob and multi-bulk nulls. + /// + Null = (1 << 3) | None, + + /// + /// True or false. + /// + Boolean = (1 << 3) | Integer, + + /// + /// A floating point number. + /// + Double = (1 << 3) | SimpleString, + + /// + /// A large number non representable by the type. + /// + BigInteger = (2 << 3) | SimpleString, + + /// + /// Binary safe error code and message. + /// + BlobError = (1 << 3) | Error, + + /// + /// A binary safe string that should be displayed to humans without any escaping or filtering. For instance the output of LATENCY DOCTOR in Redis. + /// + VerbatimString = (1 << 3) | BulkString, + + /// + /// An unordered collection of key-value pairs. Keys and values can be any other RESP3 type. + /// + Map = (1 << 3) | Array, + + /// + /// An unordered collection of N other types. + /// + Set = (2 << 3) | Array, + + /// + /// Like the type, but the client should keep reading the reply ignoring the attribute type, and return it to the client as additional information. + /// + Attribute = (3 << 3) | Array, + + /// + /// Out of band data. The format is like the type, but the client should just check the first string element, + /// stating the type of the out of band data, a call a callback if there is one registered for this specific type of push information. + /// Push types are not related to replies, since they are information that the server may push at any time in the connection, + /// so the client should keep reading if it is reading the reply of a command. + /// + Push = (4 << 3) | Array, + } +} diff --git a/src/StackExchange.Redis/Enums/RetransmissionReasonType.cs b/src/StackExchange.Redis/Enums/RetransmissionReasonType.cs new file mode 100644 index 000000000..6bd9d43e6 --- /dev/null +++ b/src/StackExchange.Redis/Enums/RetransmissionReasonType.cs @@ -0,0 +1,30 @@ +namespace StackExchange.Redis +{ + /// + /// + /// If an IProfiledCommand is a retransmission of a previous command, this enum + /// is used to indicate what prompted the retransmission. + /// + /// + /// This can be used to distinguish between transient causes (moving hashslots, joining nodes, etc.) + /// and incorrect routing. + /// + /// + public enum RetransmissionReasonType + { + /// + /// No stated reason. + /// + None = 0, + + /// + /// Issued to investigate which node owns a key. + /// + Ask, + + /// + /// A node has indicated that it does *not* own the given key. + /// + Moved, + } +} diff --git a/src/StackExchange.Redis/Enums/SaveType.cs b/src/StackExchange.Redis/Enums/SaveType.cs new file mode 100644 index 000000000..5296d110e --- /dev/null +++ b/src/StackExchange.Redis/Enums/SaveType.cs @@ -0,0 +1,34 @@ +using System; + +namespace StackExchange.Redis +{ + /// + /// The type of save operation to perform. + /// + public enum SaveType + { + /// + /// Instruct Redis to start an Append Only File rewrite process. + /// The rewrite will create a small optimized version of the current Append Only File. + /// + /// + BackgroundRewriteAppendOnlyFile, + + /// + /// Save the DB in background. The OK code is immediately returned. + /// Redis forks, the parent continues to serve the clients, the child saves the DB on disk then exits. + /// A client my be able to check if the operation succeeded using the LASTSAVE command. + /// + /// + BackgroundSave, + + /// + /// Save the DB in foreground. + /// This is almost never a good thing to do, and could cause significant blocking. + /// Only do this if you know you need to save. + /// + /// + [Obsolete("Saving on the foreground can cause significant blocking; use with extreme caution")] + ForegroundSave, + } +} diff --git a/src/StackExchange.Redis/Enums/ServerType.cs b/src/StackExchange.Redis/Enums/ServerType.cs new file mode 100644 index 000000000..ef49a8449 --- /dev/null +++ b/src/StackExchange.Redis/Enums/ServerType.cs @@ -0,0 +1,55 @@ +namespace StackExchange.Redis +{ + /// + /// Indicates the flavor of a particular redis server. + /// + public enum ServerType + { + /// + /// Classic redis-server server. + /// + Standalone, + + /// + /// Monitoring/configuration redis-sentinel server. + /// + Sentinel, + + /// + /// Distributed redis-cluster server. + /// + Cluster, + + /// + /// Distributed redis installation via twemproxy. + /// + Twemproxy, + + /// + /// Redis cluster via envoyproxy. + /// + Envoyproxy, + } + + internal static class ServerTypeExtensions + { + /// + /// Whether a server type can have only a single primary, meaning an election if multiple are found. + /// + internal static bool HasSinglePrimary(this ServerType type) => type switch + { + ServerType.Envoyproxy => false, + _ => true, + }; + + /// + /// Whether a server type supports . + /// + internal static bool SupportsAutoConfigure(this ServerType type) => type switch + { + ServerType.Twemproxy => false, + ServerType.Envoyproxy => false, + _ => true, + }; + } +} diff --git a/src/StackExchange.Redis/Enums/SetOperation.cs b/src/StackExchange.Redis/Enums/SetOperation.cs new file mode 100644 index 000000000..a529d348e --- /dev/null +++ b/src/StackExchange.Redis/Enums/SetOperation.cs @@ -0,0 +1,39 @@ +using System; + +namespace StackExchange.Redis +{ + /// + /// Describes an algebraic set operation that can be performed to combine multiple sets. + /// + public enum SetOperation + { + /// + /// Returns the members of the set resulting from the union of all the given sets. + /// + Union, + + /// + /// Returns the members of the set resulting from the intersection of all the given sets. + /// + Intersect, + + /// + /// Returns the members of the set resulting from the difference between the first set and all the successive sets. + /// + Difference, + } + + internal static class SetOperationExtensions + { + internal static RedisCommand ToCommand(this SetOperation operation, bool store) => operation switch + { + SetOperation.Intersect when store => RedisCommand.ZINTERSTORE, + SetOperation.Intersect => RedisCommand.ZINTER, + SetOperation.Union when store => RedisCommand.ZUNIONSTORE, + SetOperation.Union => RedisCommand.ZUNION, + SetOperation.Difference when store => RedisCommand.ZDIFFSTORE, + SetOperation.Difference => RedisCommand.ZDIFF, + _ => throw new ArgumentOutOfRangeException(nameof(operation)), + }; + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/ShutdownMode.cs b/src/StackExchange.Redis/Enums/ShutdownMode.cs similarity index 81% rename from StackExchange.Redis/StackExchange/Redis/ShutdownMode.cs rename to src/StackExchange.Redis/Enums/ShutdownMode.cs index e7a10f62f..a8b701ea8 100644 --- a/StackExchange.Redis/StackExchange/Redis/ShutdownMode.cs +++ b/src/StackExchange.Redis/Enums/ShutdownMode.cs @@ -1,22 +1,23 @@ namespace StackExchange.Redis { /// - /// Defines the persistence behaviour of the server during shutdown + /// Defines the persistence behaviour of the server during shutdown. /// public enum ShutdownMode { /// - /// The data is persisted if save points are configured + /// The data is persisted if save points are configured. /// Default, + /// - /// The data is NOT persisted even if save points are configured + /// The data is NOT persisted even if save points are configured. /// Never, + /// - /// The data is persisted even if save points are NOT configured + /// The data is persisted even if save points are NOT configured. /// - Always + Always, } - } diff --git a/src/StackExchange.Redis/Enums/SimulatedFailureType.cs b/src/StackExchange.Redis/Enums/SimulatedFailureType.cs new file mode 100644 index 000000000..7f2968eca --- /dev/null +++ b/src/StackExchange.Redis/Enums/SimulatedFailureType.cs @@ -0,0 +1,22 @@ +using System; + +namespace StackExchange.Redis +{ + [Flags] + internal enum SimulatedFailureType + { + None = 0, + InteractiveInbound = 1 << 0, + InteractiveOutbound = 1 << 1, + SubscriptionInbound = 1 << 2, + SubscriptionOutbound = 1 << 3, + + AllInbound = InteractiveInbound | SubscriptionInbound, + AllOutbound = InteractiveOutbound | SubscriptionOutbound, + + AllInteractive = InteractiveInbound | InteractiveOutbound, + AllSubscription = SubscriptionInbound | SubscriptionOutbound, + + All = AllInbound | AllOutbound, + } +} diff --git a/src/StackExchange.Redis/Enums/SortType.cs b/src/StackExchange.Redis/Enums/SortType.cs new file mode 100644 index 000000000..48a3596b6 --- /dev/null +++ b/src/StackExchange.Redis/Enums/SortType.cs @@ -0,0 +1,19 @@ +namespace StackExchange.Redis +{ + /// + /// Specifies how to compare elements for sorting. + /// + public enum SortType + { + /// + /// Elements are interpreted as a double-precision floating point number and sorted numerically. + /// + Numeric, + + /// + /// Elements are sorted using their alphabetic form + /// (Redis is UTF-8 aware as long as the !LC_COLLATE environment variable is set at the server). + /// + Alphabetic, + } +} diff --git a/src/StackExchange.Redis/Enums/SortedSetOrder.cs b/src/StackExchange.Redis/Enums/SortedSetOrder.cs new file mode 100644 index 000000000..474cd3612 --- /dev/null +++ b/src/StackExchange.Redis/Enums/SortedSetOrder.cs @@ -0,0 +1,32 @@ +namespace StackExchange.Redis; + +/// +/// Enum to manage ordering in sorted sets. +/// +public enum SortedSetOrder +{ + /// + /// Bases ordering off of the rank in the sorted set. This means that your start and stop inside the sorted set will be some offset into the set. + /// + ByRank, + + /// + /// Bases ordering off of the score in the sorted set. This means your start/stop will be some number which is the score for each member in the sorted set. + /// + ByScore, + + /// + /// Bases ordering off of lexicographical order, this is only appropriate in an instance where all the members of your sorted set are given the same score. + /// + ByLex, +} + +internal static class SortedSetOrderByExtensions +{ + internal static RedisValue GetLiteral(this SortedSetOrder sortedSetOrder) => sortedSetOrder switch + { + SortedSetOrder.ByLex => RedisLiterals.BYLEX, + SortedSetOrder.ByScore => RedisLiterals.BYSCORE, + _ => RedisValue.Null, + }; +} diff --git a/src/StackExchange.Redis/Enums/SortedSetWhen.cs b/src/StackExchange.Redis/Enums/SortedSetWhen.cs new file mode 100644 index 000000000..517aaeaa5 --- /dev/null +++ b/src/StackExchange.Redis/Enums/SortedSetWhen.cs @@ -0,0 +1,56 @@ +using System; + +namespace StackExchange.Redis +{ + /// + /// Indicates when this operation should be performed (only some variations are legal in a given context). + /// + [Flags] + public enum SortedSetWhen + { + /// + /// The operation won't be prevented. + /// + Always = 0, + + /// + /// The operation should only occur when there is an existing value. + /// + Exists = 1 << 0, + + /// + /// The operation should only occur when the new score is greater than the current score. + /// + GreaterThan = 1 << 1, + + /// + /// The operation should only occur when the new score is less than the current score. + /// + LessThan = 1 << 2, + + /// + /// The operation should only occur when there is not an existing value. + /// + NotExists = 1 << 3, + } + + internal static class SortedSetWhenExtensions + { + internal static uint CountBits(this SortedSetWhen when) + { + uint v = (uint)when; + v -= (v >> 1) & 0x55555555; // reuse input as temporary + v = (v & 0x33333333) + ((v >> 2) & 0x33333333); // temp + uint c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; // count + return c; + } + + internal static SortedSetWhen Parse(When when) => when switch + { + When.Always => SortedSetWhen.Always, + When.Exists => SortedSetWhen.Exists, + When.NotExists => SortedSetWhen.NotExists, + _ => throw new ArgumentOutOfRangeException(nameof(when)), + }; + } +} diff --git a/src/StackExchange.Redis/Enums/StreamTrimMode.cs b/src/StackExchange.Redis/Enums/StreamTrimMode.cs new file mode 100644 index 000000000..2033e8414 --- /dev/null +++ b/src/StackExchange.Redis/Enums/StreamTrimMode.cs @@ -0,0 +1,24 @@ +namespace StackExchange.Redis; + +/// +/// Determines how stream trimming works. +/// +public enum StreamTrimMode +{ + /// + /// Trims the stream according to the specified policy (MAXLEN or MINID) regardless of whether entries are referenced by any consumer groups, but preserves existing references to these entries in all consumer groups' PEL. + /// + KeepReferences = 0, + + /// + /// Trims the stream according to the specified policy and also removes all references to the trimmed entries from all consumer groups' PEL. + /// + /// Requires server 8.2 or above. + DeleteReferences = 1, + + /// + /// With ACKED: Only trims entries that were read and acknowledged by all consumer groups. + /// + /// Requires server 8.2 or above. + Acknowledged = 2, +} diff --git a/src/StackExchange.Redis/Enums/StreamTrimResult.cs b/src/StackExchange.Redis/Enums/StreamTrimResult.cs new file mode 100644 index 000000000..e58c321ab --- /dev/null +++ b/src/StackExchange.Redis/Enums/StreamTrimResult.cs @@ -0,0 +1,24 @@ +namespace StackExchange.Redis; + +/// +/// Determines how stream trimming works. +/// +public enum StreamTrimResult +{ + /// + /// No such id exists in the provided stream key. + /// + NotFound = -1, + + /// + /// Entry was deleted from the stream. + /// + Deleted = 1, + + /// + /// Entry was not deleted because it has either not been delivered to any consumer, or + /// still has references in the consumer groups' Pending Entries List (PEL). + /// + /// This response relates to the mode. + NotDeleted = 2, +} diff --git a/src/StackExchange.Redis/Enums/StringIndexType.cs b/src/StackExchange.Redis/Enums/StringIndexType.cs new file mode 100644 index 000000000..fcb41e391 --- /dev/null +++ b/src/StackExchange.Redis/Enums/StringIndexType.cs @@ -0,0 +1,29 @@ +using System; + +namespace StackExchange.Redis; + +/// +/// Indicates if we index into a string based on bits or bytes. +/// +public enum StringIndexType +{ + /// + /// Indicates the index is the number of bytes into a string. + /// + Byte, + + /// + /// Indicates the index is the number of bits into a string. + /// + Bit, +} + +internal static class StringIndexTypeExtensions +{ + internal static RedisValue ToLiteral(this StringIndexType indexType) => indexType switch + { + StringIndexType.Bit => RedisLiterals.BIT, + StringIndexType.Byte => RedisLiterals.BYTE, + _ => throw new ArgumentOutOfRangeException(nameof(indexType)), + }; +} diff --git a/StackExchange.Redis/StackExchange/Redis/When.cs b/src/StackExchange.Redis/Enums/When.cs similarity index 82% rename from StackExchange.Redis/StackExchange/Redis/When.cs rename to src/StackExchange.Redis/Enums/When.cs index d72deba29..412e4064a 100644 --- a/StackExchange.Redis/StackExchange/Redis/When.cs +++ b/src/StackExchange.Redis/Enums/When.cs @@ -1,21 +1,23 @@ namespace StackExchange.Redis { /// - /// Indicates when this operation should be performed (only some variations are legal in a given context) + /// Indicates when this operation should be performed (only some variations are legal in a given context). /// public enum When { /// - /// The operation should occur whether or not there is an existing value + /// The operation should occur whether or not there is an existing value. /// Always, + /// - /// The operation should only occur when there is an existing value + /// The operation should only occur when there is an existing value. /// Exists, + /// - /// The operation should only occur when there is not an existing value + /// The operation should only occur when there is not an existing value. /// - NotExists + NotExists, } } diff --git a/src/StackExchange.Redis/ExceptionFactory.cs b/src/StackExchange.Redis/ExceptionFactory.cs new file mode 100644 index 000000000..7e4eca49a --- /dev/null +++ b/src/StackExchange.Redis/ExceptionFactory.cs @@ -0,0 +1,446 @@ +using System; +using System.Collections.Generic; +using System.Security.Authentication; +using System.Text; +using System.Threading; + +namespace StackExchange.Redis +{ + internal static partial class ExceptionFactory + { + private const string + DataCommandKey = "redis-command", + DataSentStatusKey = "request-sent-status", + DataServerKey = "redis-server", + TimeoutHelpLink = "https://stackexchange.github.io/StackExchange.Redis/Timeouts"; + + internal static Exception AdminModeNotEnabled(bool includeDetail, RedisCommand command, Message? message, ServerEndPoint? server) + { + string s = GetLabel(includeDetail, command, message); + var ex = new RedisCommandException("This operation is not available unless admin mode is enabled: " + s); + if (includeDetail) AddExceptionDetail(ex, message, server, s); + return ex; + } + + internal static Exception CommandDisabled(RedisCommand command) => CommandDisabled(command.ToString()); + + internal static Exception CommandDisabled(string command) + => new RedisCommandException("This operation has been disabled in the command-map and cannot be used: " + command); + + internal static Exception TooManyArgs(string command, int argCount) + => new RedisCommandException($"This operation would involve too many arguments ({argCount + 1} vs the redis limit of {PhysicalConnection.REDIS_MAX_ARGS}): {command}"); + + internal static Exception ConnectionFailure(bool includeDetail, ConnectionFailureType failureType, string message, ServerEndPoint? server) + { + var ex = new RedisConnectionException(failureType, message); + if (includeDetail) AddExceptionDetail(ex, null, server, null); + return ex; + } + + internal static Exception DatabaseNotRequired(bool includeDetail, RedisCommand command) + { + string s = command.ToString(); + var ex = new RedisCommandException("A target database is not required for " + s); + if (includeDetail) AddExceptionDetail(ex, null, null, s); + return ex; + } + + internal static Exception DatabaseOutfRange(bool includeDetail, int targetDatabase, Message message, ServerEndPoint server) + { + var ex = new RedisCommandException("The database does not exist on the server: " + targetDatabase); + if (includeDetail) AddExceptionDetail(ex, message, server, null); + return ex; + } + + internal static Exception DatabaseRequired(bool includeDetail, RedisCommand command) + { + string s = command.ToString(); + var ex = new RedisCommandException("A target database is required for " + s); + if (includeDetail) AddExceptionDetail(ex, null, null, s); + return ex; + } + + internal static Exception PrimaryOnly(bool includeDetail, RedisCommand command, Message? message, ServerEndPoint? server) + { + string s = GetLabel(includeDetail, command, message); + var ex = new RedisCommandException("Command cannot be issued to a replica: " + s); + if (includeDetail) AddExceptionDetail(ex, message, server, s); + return ex; + } + + internal static Exception MultiSlot(bool includeDetail, Message message) + { + var ex = new RedisCommandException("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot"); + if (includeDetail) AddExceptionDetail(ex, message, null, null); + return ex; + } + + internal static string GetInnerMostExceptionMessage(Exception? e) + { + if (e == null) + { + return ""; + } + else + { + while (e.InnerException != null) + { + e = e.InnerException; + } + return e.Message; + } + } + + internal static Exception NoConnectionAvailable( + ConnectionMultiplexer multiplexer, + Message? message, + ServerEndPoint? server, + ReadOnlySpan serverSnapshot = default, + RedisCommand command = default) + { + string commandLabel = GetLabel(multiplexer.RawConfig.IncludeDetailInExceptions, message?.Command ?? command, message); + + if (server != null) + { + // If we already have the serverEndpoint for connection failure use that, + // otherwise it would output state of all the endpoints. + serverSnapshot = new ServerEndPoint[] { server }; + } + + var innerException = PopulateInnerExceptions(serverSnapshot == default ? multiplexer.GetServerSnapshot() : serverSnapshot); + + // Try to get a useful error message for the user. + long attempts = multiplexer._connectAttemptCount, completions = multiplexer._connectCompletedCount; + string initialMessage; + // We only need to customize the connection if we're aborting on connect fail + // The "never" case would have thrown, if this was true + if (!multiplexer.RawConfig.AbortOnConnectFail && attempts <= multiplexer.RawConfig.ConnectRetry && completions == 0) + { + // Initial attempt, attempted use before an async connection completes + initialMessage = $"Connection to Redis never succeeded (attempts: {attempts} - connection likely in-progress), unable to service operation: "; + } + else if (!multiplexer.RawConfig.AbortOnConnectFail && attempts > multiplexer.RawConfig.ConnectRetry && completions == 0) + { + // Attempted use after a full initial retry connect count # of failures + // This can happen in cloud environments often, where user disables abort and has the wrong config + initialMessage = $"Connection to Redis never succeeded (attempts: {attempts} - check your config), unable to service operation: "; + } + else if (message is not null && message.IsPrimaryOnly() && multiplexer.IsConnected) + { + // If we know it's a primary-only command, indicate that in the error message + initialMessage = "No connection (requires writable - not eligible for replica) is active/available to service this operation: "; + } + else + { + // Default if we don't have a more useful error message here based on circumstances + initialMessage = "No connection is active/available to service this operation: "; + } + + StringBuilder sb = new StringBuilder(initialMessage); + sb.Append(commandLabel); + string innermostExceptionstring = GetInnerMostExceptionMessage(innerException); + if (!string.IsNullOrEmpty(innermostExceptionstring)) + { + sb.Append("; ").Append(innermostExceptionstring); + } + + // Add counters and exception data if we have it + List>? data = null; + if (multiplexer.RawConfig.IncludeDetailInExceptions) + { + data = new List>(); + AddCommonDetail(data, sb, message, multiplexer, server); + } + var ex = new RedisConnectionException(ConnectionFailureType.UnableToResolvePhysicalConnection, sb.ToString(), innerException, message?.Status ?? CommandStatus.Unknown); + if (multiplexer.RawConfig.IncludeDetailInExceptions) + { + CopyDataToException(data, ex); + sb.Append("; ").Append(PerfCounterHelper.GetThreadPoolAndCPUSummary()); + AddExceptionDetail(ex, message, server, commandLabel); + } + return ex; + } + + internal static Exception? PopulateInnerExceptions(ReadOnlySpan serverSnapshot) + { + var innerExceptions = new List(); + + if (serverSnapshot.Length > 0 && serverSnapshot[0].Multiplexer.LastException is Exception ex) + { + innerExceptions.Add(ex); + } + + for (int i = 0; i < serverSnapshot.Length; i++) + { + if (serverSnapshot[i].LastException is Exception lastException) + { + innerExceptions.Add(lastException); + } + } + + if (innerExceptions.Count == 1) + { + return innerExceptions[0]; + } + else if (innerExceptions.Count > 1) + { + return new AggregateException(innerExceptions); + } + return null; + } + + internal static Exception NotSupported(bool includeDetail, RedisCommand command) + { + string s = GetLabel(includeDetail, command, null); + var ex = new RedisCommandException("Command is not available on your server: " + s); + if (includeDetail) AddExceptionDetail(ex, null, null, s); + return ex; + } + + internal static Exception NoCursor(RedisCommand command) + { + string s = GetLabel(false, command, null); + return new RedisCommandException("Command cannot be used with a cursor: " + s); + } + + private static void Add(List> data, StringBuilder sb, string? lk, string? sk, string? v) + { + if (v != null) + { + if (lk != null) data.Add(Tuple.Create(lk, v)); + if (sk != null) sb.Append(", ").Append(sk).Append(": ").Append(v); + } + } + + internal static Exception Timeout(ConnectionMultiplexer multiplexer, string? baseErrorMessage, Message message, ServerEndPoint? server, WriteResult? result = null, PhysicalBridge? bridge = null) + { + List> data = new List> { Tuple.Create("Message", message.CommandAndKey) }; + var sb = new StringBuilder(); + + // We timeout writing messages in quite different ways sync/async - so centralize messaging here. + if (string.IsNullOrEmpty(baseErrorMessage) && result == WriteResult.TimeoutBeforeWrite) + { + baseErrorMessage = message.IsBacklogged + ? "The message timed out in the backlog attempting to send because no connection became available" + : "The timeout was reached before the message could be written to the output buffer, and it was not sent"; + } + + var lastConnectionException = bridge?.LastException as RedisConnectionException; + var logConnectionException = message.IsBacklogged && lastConnectionException is not null; + + if (!string.IsNullOrEmpty(baseErrorMessage)) + { + sb.Append(baseErrorMessage); + + // If we're in the situation where we've never connected + if (logConnectionException && lastConnectionException is not null) + { + sb.Append(" (").Append(Format.ToString(multiplexer.TimeoutMilliseconds)).Append("ms)"); + sb.Append(" - Last Connection Exception: ").Append(lastConnectionException.Message); + } + + if (message != null) + { + sb.Append(", command=").Append(message.CommandString); // no key here, note + } + } + else + { + sb.Append("Timeout performing ").Append(message.CommandString).Append(" (").Append(Format.ToString(multiplexer.TimeoutMilliseconds)).Append("ms)"); + } + + // Add timeout data, if we have it + if (result == WriteResult.TimeoutBeforeWrite) + { + Add(data, sb, "Timeout", "timeout", Format.ToString(multiplexer.TimeoutMilliseconds)); + try + { + if (message != null && message.TryGetPhysicalState(out var ws, out var rs, out var sentDelta, out var receivedDelta)) + { + Add(data, sb, "Write-State", null, ws.ToString()); + Add(data, sb, "Read-State", null, rs.ToString()); + // these might not always be available + if (sentDelta >= 0) + { + Add(data, sb, "OutboundDeltaKB", "outbound", $"{sentDelta >> 10}KiB"); + } + if (receivedDelta >= 0) + { + Add(data, sb, "InboundDeltaKB", "inbound", $"{receivedDelta >> 10}KiB"); + } + } + } + catch { } + } + AddCommonDetail(data, sb, message, multiplexer, server); + + sb.Append(" (Please take a look at this article for some common client-side issues that can cause timeouts: ") + .Append(TimeoutHelpLink) + .Append(')'); + + // If we're from a backlog timeout scenario, we log a more intuitive connection exception for the timeout...because the timeout was a symptom + // and we have a more direct cause: we had no connection to send it on. + Exception ex = logConnectionException && lastConnectionException is not null + ? new RedisConnectionException(lastConnectionException.FailureType, sb.ToString(), lastConnectionException, message?.Status ?? CommandStatus.Unknown) + { + HelpLink = TimeoutHelpLink, + } + : new RedisTimeoutException(sb.ToString(), message?.Status ?? CommandStatus.Unknown) + { + HelpLink = TimeoutHelpLink, + }; + CopyDataToException(data, ex); + + if (multiplexer.RawConfig.IncludeDetailInExceptions) AddExceptionDetail(ex, message, server, null); + return ex; + } + + private static void CopyDataToException(List>? data, Exception ex) + { + if (data != null) + { + var exData = ex.Data; + foreach (var kv in data) + { + exData["Redis-" + kv.Item1] = kv.Item2; + } + } + } + + private static void AddCommonDetail( + List> data, + StringBuilder sb, + Message? message, + ConnectionMultiplexer multiplexer, + ServerEndPoint? server) + { + if (message != null) + { + message.TryGetHeadMessages(out var now, out var next); + if (now != null) Add(data, sb, "Message-Current", "active", multiplexer.RawConfig.IncludeDetailInExceptions ? now.CommandAndKey : now.CommandString); + if (next != null) Add(data, sb, "Message-Next", "next", multiplexer.RawConfig.IncludeDetailInExceptions ? next.CommandAndKey : next.CommandString); + } + + // Add server data, if we have it + if (server != null && message != null) + { + var bs = server.GetBridgeStatus(message.IsForSubscriptionBridge ? ConnectionType.Subscription : ConnectionType.Interactive); + + switch (bs.Connection.ReadStatus) + { + case PhysicalConnection.ReadStatus.CompletePendingMessageAsync: + case PhysicalConnection.ReadStatus.CompletePendingMessageSync: + sb.Append(" ** possible thread-theft indicated; see https://stackexchange.github.io/StackExchange.Redis/ThreadTheft ** "); + break; + } + Add(data, sb, "OpsSinceLastHeartbeat", "inst", bs.MessagesSinceLastHeartbeat.ToString()); + Add(data, sb, "Queue-Awaiting-Write", "qu", bs.BacklogMessagesPending.ToString()); + Add(data, sb, "Queue-Awaiting-Response", "qs", bs.Connection.MessagesSentAwaitingResponse.ToString()); + Add(data, sb, "Active-Writer", "aw", bs.IsWriterActive.ToString()); + Add(data, sb, "Backlog-Writer", "bw", bs.BacklogStatus.ToString()); + if (bs.Connection.ReadStatus != PhysicalConnection.ReadStatus.NA) Add(data, sb, "Read-State", "rs", bs.Connection.ReadStatus.ToString()); + if (bs.Connection.WriteStatus != PhysicalConnection.WriteStatus.NA) Add(data, sb, "Write-State", "ws", bs.Connection.WriteStatus.ToString()); + + if (bs.Connection.BytesAvailableOnSocket >= 0) Add(data, sb, "Inbound-Bytes", "in", bs.Connection.BytesAvailableOnSocket.ToString()); + if (bs.Connection.BytesInReadPipe >= 0) Add(data, sb, "Inbound-Pipe-Bytes", "in-pipe", bs.Connection.BytesInReadPipe.ToString()); + if (bs.Connection.BytesInWritePipe >= 0) Add(data, sb, "Outbound-Pipe-Bytes", "out-pipe", bs.Connection.BytesInWritePipe.ToString()); + Add(data, sb, "Last-Result-Bytes", "last-in", bs.Connection.BytesLastResult.ToString()); + Add(data, sb, "Inbound-Buffer-Bytes", "cur-in", bs.Connection.BytesInBuffer.ToString()); + + Add(data, sb, "Sync-Ops", "sync-ops", multiplexer.syncOps.ToString()); + Add(data, sb, "Async-Ops", "async-ops", multiplexer.asyncOps.ToString()); + + if (multiplexer.StormLogThreshold >= 0 && bs.Connection.MessagesSentAwaitingResponse >= multiplexer.StormLogThreshold && Interlocked.CompareExchange(ref multiplexer.haveStormLog, 1, 0) == 0) + { + var log = server.GetStormLog(message); + if (string.IsNullOrWhiteSpace(log)) Interlocked.Exchange(ref multiplexer.haveStormLog, 0); + else Interlocked.Exchange(ref multiplexer.stormLogSnapshot, log); + } + Add(data, sb, "Server-Endpoint", "serverEndpoint", (server.EndPoint.ToString() ?? "Unknown").Replace("Unspecified/", "")); + Add(data, sb, "Server-Connected-Seconds", "conn-sec", bs.ConnectedAt is DateTime dt ? (DateTime.UtcNow - dt).TotalSeconds.ToString("0.##") : "n/a"); + Add(data, sb, "Abort-On-Connect", "aoc", multiplexer.RawConfig.AbortOnConnectFail ? "1" : "0"); + } + Add(data, sb, "Multiplexer-Connects", "mc", $"{multiplexer._connectAttemptCount}/{multiplexer._connectCompletedCount}/{multiplexer._connectionCloseCount}"); + Add(data, sb, "Manager", "mgr", multiplexer.SocketManager?.GetState()); + + Add(data, sb, "Client-Name", "clientName", multiplexer.ClientName); + if (message != null) + { + var hashSlot = message.GetHashSlot(multiplexer.ServerSelectionStrategy); + // only add keyslot if its a valid cluster key slot + if (hashSlot != ServerSelectionStrategy.NoSlot) + { + Add(data, sb, "Key-HashSlot", "PerfCounterHelperkeyHashSlot", message.GetHashSlot(multiplexer.ServerSelectionStrategy).ToString()); + } + } + int busyWorkerCount = PerfCounterHelper.GetThreadPoolStats(out string iocp, out string worker, out string? workItems); + Add(data, sb, "ThreadPool-IO-Completion", "IOCP", iocp); + Add(data, sb, "ThreadPool-Workers", "WORKER", worker); + if (workItems != null) + { + Add(data, sb, "ThreadPool-Items", "POOL", workItems); + } + data.Add(Tuple.Create("Busy-Workers", busyWorkerCount.ToString())); + + Add(data, sb, "Version", "v", Utils.GetLibVersion()); + } + + private static void AddExceptionDetail(Exception? exception, Message? message, ServerEndPoint? server, string? label) + { + if (exception != null) + { + if (message != null) + { + exception.Data.Add(DataCommandKey, message.CommandAndKey); + exception.Data.Add(DataSentStatusKey, message.Status); + } + else if (label != null) + { + exception.Data.Add(DataCommandKey, label); + } + + if (server != null) exception.Data.Add(DataServerKey, Format.ToString(server.EndPoint)); + } + } + + private static string GetLabel(bool includeDetail, RedisCommand command, Message? message) + { + return message == null ? command.ToString() : (includeDetail ? message.CommandAndKey : message.CommandString); + } + + internal static Exception UnableToConnect(ConnectionMultiplexer muxer, string? failureMessage = null, string? connectionName = null) + { + var sb = new StringBuilder("It was not possible to connect to the redis server(s)"); + if (connectionName is not null) + { + sb.Append(' ').Append(connectionName); + } + sb.Append('.'); + Exception? inner = null; + var failureType = ConnectionFailureType.UnableToConnect; + if (muxer is not null) + { + if (muxer.AuthException is Exception aex) + { + failureType = ConnectionFailureType.AuthenticationFailure; + sb.Append(" There was an authentication failure; check that passwords (or client certificates) are configured correctly: (").Append(aex.GetType().Name).Append(") ").Append(aex.Message); + inner = aex; + if (aex is AuthenticationException && aex.InnerException is Exception iaex) + { + sb.Append(" (Inner - ").Append(iaex.GetType().Name).Append(") ").Append(iaex.Message); + } + } + else if (muxer.RawConfig.AbortOnConnectFail) + { + sb.Append(" Error connecting right now. To allow this multiplexer to continue retrying until it's able to connect, use abortConnect=false in your connection string or AbortOnConnectFail=false; in your code."); + } + } + if (!failureMessage.IsNullOrWhiteSpace()) + { + sb.Append(' ').Append(failureMessage.Trim()); + } + + return new RedisConnectionException(failureType, sb.ToString(), inner); + } + } +} diff --git a/src/StackExchange.Redis/Exceptions.cs b/src/StackExchange.Redis/Exceptions.cs new file mode 100644 index 000000000..1f1c973ce --- /dev/null +++ b/src/StackExchange.Redis/Exceptions.cs @@ -0,0 +1,199 @@ +using System; +using System.ComponentModel; +using System.Runtime.Serialization; + +namespace StackExchange.Redis +{ + /// + /// Indicates that a command was illegal and was not sent to the server. + /// + [Serializable] + public sealed partial class RedisCommandException : Exception + { + /// + /// Creates a new . + /// + /// The message for the exception. + public RedisCommandException(string message) : base(message) { } + + /// + /// Creates a new . + /// + /// The message for the exception. + /// The inner exception. + public RedisCommandException(string message, Exception innerException) : base(message, innerException) { } + +#if NET8_0_OR_GREATER + [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId)] + [EditorBrowsable(EditorBrowsableState.Never)] +#endif + private RedisCommandException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { } + } + + /// + /// Indicates the time allotted for a command or operation has expired. + /// + [Serializable] + public sealed partial class RedisTimeoutException : TimeoutException + { + /// + /// Creates a new . + /// + /// The message for the exception. + /// The command status, as of when the timeout happened. + public RedisTimeoutException(string message, CommandStatus commandStatus) : base(message) + { + Commandstatus = commandStatus; + } + + /// + /// status of the command while communicating with Redis. + /// + public CommandStatus Commandstatus { get; } + +#if NET8_0_OR_GREATER + [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId)] + [EditorBrowsable(EditorBrowsableState.Never)] +#endif + private RedisTimeoutException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) + { + Commandstatus = info.GetValue("commandStatus", typeof(CommandStatus)) as CommandStatus? ?? CommandStatus.Unknown; + } + + /// + /// Serialization implementation; not intended for general usage. + /// + /// Serialization info. + /// Serialization context. +#if NET8_0_OR_GREATER + [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId)] + [EditorBrowsable(EditorBrowsableState.Never)] +#endif + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue("commandStatus", Commandstatus); + } + } + + /// + /// Indicates a connection fault when communicating with redis. + /// + [Serializable] + public sealed partial class RedisConnectionException : RedisException + { + /// + /// Creates a new . + /// + /// The type of connection failure. + /// The message for the exception. + public RedisConnectionException(ConnectionFailureType failureType, string message) : this(failureType, message, null, CommandStatus.Unknown) { } + + /// + /// Creates a new . + /// + /// The type of connection failure. + /// The message for the exception. + /// The inner exception. + public RedisConnectionException(ConnectionFailureType failureType, string message, Exception? innerException) : this(failureType, message, innerException, CommandStatus.Unknown) { } + + /// + /// Creates a new . + /// + /// The type of connection failure. + /// The message for the exception. + /// The inner exception. + /// The status of the command. + public RedisConnectionException(ConnectionFailureType failureType, string message, Exception? innerException, CommandStatus commandStatus) : base(message, innerException) + { + FailureType = failureType; + CommandStatus = commandStatus; + } + + /// + /// The type of connection failure. + /// + public ConnectionFailureType FailureType { get; } + + /// + /// Status of the command while communicating with Redis. + /// + public CommandStatus CommandStatus { get; } + +#if NET8_0_OR_GREATER + [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId)] + [EditorBrowsable(EditorBrowsableState.Never)] +#endif + private RedisConnectionException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) + { + FailureType = (ConnectionFailureType)info.GetInt32("failureType"); + CommandStatus = info.GetValue("commandStatus", typeof(CommandStatus)) as CommandStatus? ?? CommandStatus.Unknown; + } + + /// + /// Serialization implementation; not intended for general usage. + /// + /// Serialization info. + /// Serialization context. +#if NET8_0_OR_GREATER + [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId)] + [EditorBrowsable(EditorBrowsableState.Never)] +#endif + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue("failureType", (int)FailureType); + info.AddValue("commandStatus", CommandStatus); + } + } + + /// + /// Indicates an issue communicating with redis. + /// + [Serializable] + public partial class RedisException : Exception + { + /// + /// Creates a new . + /// + /// The message for the exception. + public RedisException(string message) : base(message) { } + + /// + /// Creates a new . + /// + /// The message for the exception. + /// The inner exception. + public RedisException(string message, Exception? innerException) : base(message, innerException) { } + + /// + /// Deserialization constructor; not intended for general usage. + /// + /// Serialization info. + /// Serialization context. +#if NET8_0_OR_GREATER + [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId)] + [EditorBrowsable(EditorBrowsableState.Never)] +#endif + protected RedisException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { } + } + + /// + /// Indicates an exception raised by a redis server. + /// + [Serializable] + public sealed partial class RedisServerException : RedisException + { + /// + /// Creates a new . + /// + /// The message for the exception. + public RedisServerException(string message) : base(message) { } + +#if NET8_0_OR_GREATER + [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId)] + [EditorBrowsable(EditorBrowsableState.Never)] +#endif + private RedisServerException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { } + } +} diff --git a/src/StackExchange.Redis/Experiments.cs b/src/StackExchange.Redis/Experiments.cs new file mode 100644 index 000000000..577c9f8c9 --- /dev/null +++ b/src/StackExchange.Redis/Experiments.cs @@ -0,0 +1,41 @@ +using System.Diagnostics.CodeAnalysis; + +namespace StackExchange.Redis +{ + // example usage: + // [Experimental(Experiments.SomeFeature, UrlFormat = Experiments.UrlFormat)] + // where SomeFeature has the next label, for example "SER042", and /docs/exp/SER042.md exists + internal static class Experiments + { + public const string UrlFormat = "https://stackexchange.github.io/StackExchange.Redis/exp/"; + public const string VectorSets = "SER001"; + } +} + +#if !NET8_0_OR_GREATER +#pragma warning disable SA1403 +namespace System.Diagnostics.CodeAnalysis +#pragma warning restore SA1403 +{ + [AttributeUsage( + AttributeTargets.Assembly | + AttributeTargets.Module | + AttributeTargets.Class | + AttributeTargets.Struct | + AttributeTargets.Enum | + AttributeTargets.Constructor | + AttributeTargets.Method | + AttributeTargets.Property | + AttributeTargets.Field | + AttributeTargets.Event | + AttributeTargets.Interface | + AttributeTargets.Delegate, + Inherited = false)] + internal sealed class ExperimentalAttribute(string diagnosticId) : Attribute + { + public string DiagnosticId { get; } = diagnosticId; + public string? UrlFormat { get; set; } + public string? Message { get; set; } + } +} +#endif diff --git a/src/StackExchange.Redis/ExponentialRetry.cs b/src/StackExchange.Redis/ExponentialRetry.cs new file mode 100644 index 000000000..5ee10a951 --- /dev/null +++ b/src/StackExchange.Redis/ExponentialRetry.cs @@ -0,0 +1,69 @@ +using System; + +namespace StackExchange.Redis +{ + /// + /// Represents a retry policy that performs retries, using a randomized exponential back off scheme to determine the interval between retries. + /// + public class ExponentialRetry : IReconnectRetryPolicy + { + private readonly int deltaBackOffMilliseconds; + private readonly int maxDeltaBackOffMilliseconds = (int)TimeSpan.FromSeconds(60).TotalMilliseconds; + [ThreadStatic] + private static Random? r; + + /// + /// Initializes a new instance using the specified back off interval with default maxDeltaBackOffMilliseconds of 10 seconds. + /// + /// Time in milliseconds for the back-off interval between retries. + public ExponentialRetry(int deltaBackOffMilliseconds) : this(deltaBackOffMilliseconds, Math.Max(deltaBackOffMilliseconds, (int)TimeSpan.FromSeconds(10).TotalMilliseconds)) { } + + /// + /// Initializes a new instance using the specified back off interval. + /// + /// Time in milliseconds for the back-off interval between retries. + /// Time in milliseconds for the maximum value that the back-off interval can exponentially grow up to. + public ExponentialRetry(int deltaBackOffMilliseconds, int maxDeltaBackOffMilliseconds) + { + if (deltaBackOffMilliseconds < 0) + { + throw new ArgumentOutOfRangeException(nameof(deltaBackOffMilliseconds), $"{nameof(deltaBackOffMilliseconds)} must be greater than or equal to zero"); + } + if (maxDeltaBackOffMilliseconds < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxDeltaBackOffMilliseconds), $"{nameof(maxDeltaBackOffMilliseconds)} must be greater than or equal to zero"); + } + if (maxDeltaBackOffMilliseconds < deltaBackOffMilliseconds) + { + throw new ArgumentOutOfRangeException(nameof(maxDeltaBackOffMilliseconds), $"{nameof(maxDeltaBackOffMilliseconds)} must be greater than or equal to {nameof(deltaBackOffMilliseconds)}"); + } + + this.deltaBackOffMilliseconds = deltaBackOffMilliseconds; + this.maxDeltaBackOffMilliseconds = maxDeltaBackOffMilliseconds; + } + + /// + /// This method is called by the ConnectionMultiplexer to determine if a reconnect operation can be retried now. + /// + /// The number of times reconnect retries have already been made by the ConnectionMultiplexer while it was in the connecting state. + /// Total elapsed time in milliseconds since the last reconnect retry was made. + public bool ShouldRetry(long currentRetryCount, int timeElapsedMillisecondsSinceLastRetry) + { + var exponential = (int)Math.Min(maxDeltaBackOffMilliseconds, deltaBackOffMilliseconds * Math.Pow(1.1, currentRetryCount)); + int random; + r ??= new Random(); + random = r.Next((int)deltaBackOffMilliseconds, exponential); + return timeElapsedMillisecondsSinceLastRetry >= random; + // exponential backoff with deltaBackOff of 5000ms + // deltabackoff exponential + // 5000 5500 + // 5000 6050 + // 5000 6655 + // 5000 8053 + // 5000 10718 + // 5000 17261 + // 5000 37001 + // 5000 127738 + } + } +} diff --git a/src/StackExchange.Redis/ExtensionMethods.Internal.cs b/src/StackExchange.Redis/ExtensionMethods.Internal.cs new file mode 100644 index 000000000..de5a9f2a6 --- /dev/null +++ b/src/StackExchange.Redis/ExtensionMethods.Internal.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace StackExchange.Redis +{ + internal static class ExtensionMethodsInternal + { + internal static bool IsNullOrEmpty([NotNullWhen(false)] this string? s) => + string.IsNullOrEmpty(s); + + internal static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? s) => + string.IsNullOrWhiteSpace(s); + +#if !NETCOREAPP3_1_OR_GREATER + internal static bool TryDequeue(this Queue queue, [NotNullWhen(true)] out T? result) + { + if (queue.Count == 0) + { + result = default; + return false; + } + result = queue.Dequeue()!; + return true; + } + internal static bool TryPeek(this Queue queue, [NotNullWhen(true)] out T? result) + { + if (queue.Count == 0) + { + result = default; + return false; + } + result = queue.Peek()!; + return true; + } +#endif + } +} diff --git a/src/StackExchange.Redis/ExtensionMethods.cs b/src/StackExchange.Redis/ExtensionMethods.cs new file mode 100644 index 000000000..e5a5c4d4d --- /dev/null +++ b/src/StackExchange.Redis/ExtensionMethods.cs @@ -0,0 +1,341 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Net.Security; +using System.Runtime.CompilerServices; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using Pipelines.Sockets.Unofficial.Arenas; + +namespace StackExchange.Redis +{ + /// + /// Utility methods. + /// + public static class ExtensionMethods + { + /// + /// Create a dictionary from an array of HashEntry values. + /// + /// The entry to convert to a dictionary. + [return: NotNullIfNotNull("hash")] + public static Dictionary? ToStringDictionary(this HashEntry[]? hash) + { + if (hash is null) + { + return null; + } + + var result = new Dictionary(hash.Length, StringComparer.Ordinal); + for (int i = 0; i < hash.Length; i++) + { + result.Add(hash[i].name!, hash[i].value!); + } + return result; + } + + /// + /// Create a dictionary from an array of HashEntry values. + /// + /// The entry to convert to a dictionary. + [return: NotNullIfNotNull("hash")] + public static Dictionary? ToDictionary(this HashEntry[]? hash) + { + if (hash is null) + { + return null; + } + + var result = new Dictionary(hash.Length); + for (int i = 0; i < hash.Length; i++) + { + result.Add(hash[i].name, hash[i].value); + } + return result; + } + + /// + /// Create a dictionary from an array of SortedSetEntry values. + /// + /// The set entries to convert to a dictionary. + [return: NotNullIfNotNull("sortedSet")] + public static Dictionary? ToStringDictionary(this SortedSetEntry[]? sortedSet) + { + if (sortedSet is null) + { + return null; + } + + var result = new Dictionary(sortedSet.Length, StringComparer.Ordinal); + for (int i = 0; i < sortedSet.Length; i++) + { + result.Add(sortedSet[i].element!, sortedSet[i].score); + } + return result; + } + + /// + /// Create a dictionary from an array of SortedSetEntry values. + /// + /// The set entries to convert to a dictionary. + [return: NotNullIfNotNull("sortedSet")] + public static Dictionary? ToDictionary(this SortedSetEntry[]? sortedSet) + { + if (sortedSet is null) + { + return null; + } + + var result = new Dictionary(sortedSet.Length); + for (int i = 0; i < sortedSet.Length; i++) + { + result.Add(sortedSet[i].element, sortedSet[i].score); + } + return result; + } + + /// + /// Create a dictionary from an array of key/value pairs. + /// + /// The pairs to convert to a dictionary. + [return: NotNullIfNotNull("pairs")] + public static Dictionary? ToStringDictionary(this KeyValuePair[]? pairs) + { + if (pairs is null) + { + return null; + } + + var result = new Dictionary(pairs.Length, StringComparer.Ordinal); + for (int i = 0; i < pairs.Length; i++) + { + result.Add(pairs[i].Key!, pairs[i].Value!); + } + return result; + } + + /// + /// Create a dictionary from an array of key/value pairs. + /// + /// The pairs to convert to a dictionary. + [return: NotNullIfNotNull("pairs")] + public static Dictionary? ToDictionary(this KeyValuePair[]? pairs) + { + if (pairs is null) + { + return null; + } + + var result = new Dictionary(pairs.Length); + for (int i = 0; i < pairs.Length; i++) + { + result.Add(pairs[i].Key, pairs[i].Value); + } + return result; + } + + /// + /// Create a dictionary from an array of string pairs. + /// + /// The pairs to convert to a dictionary. + [return: NotNullIfNotNull("pairs")] + public static Dictionary? ToDictionary(this KeyValuePair[]? pairs) + { + if (pairs is null) + { + return null; + } + + var result = new Dictionary(pairs.Length, StringComparer.Ordinal); + for (int i = 0; i < pairs.Length; i++) + { + result.Add(pairs[i].Key, pairs[i].Value); + } + return result; + } + + /// + /// Create an array of RedisValues from an array of strings. + /// + /// The string array to convert to RedisValues. + [return: NotNullIfNotNull("values")] + public static RedisValue[]? ToRedisValueArray(this string[]? values) + { + if (values is null) + { + return null; + } + + if (values.Length == 0) return Array.Empty(); + return Array.ConvertAll(values, x => (RedisValue)x); + } + + /// + /// Create an array of strings from an array of values. + /// + /// The values to convert to an array. + [return: NotNullIfNotNull("values")] + public static string?[]? ToStringArray(this RedisValue[]? values) + { + if (values == null) + { + return null; + } + + if (values.Length == 0) return Array.Empty(); + return Array.ConvertAll(values, x => (string?)x); + } + + internal static Task AuthenticateAsClientAsync(this SslStream ssl, string host, SslProtocols? allowedProtocols, bool checkCertificateRevocation) + { + if (!allowedProtocols.HasValue) + { + // Default to the sslProtocols defined by the .NET Framework + return ssl.AuthenticateAsClientAsync(host); + } + + var certificateCollection = new X509CertificateCollection(); + return ssl.AuthenticateAsClientAsync(host, certificateCollection, allowedProtocols.Value, checkCertificateRevocation); + } + + /// + /// Represent a byte-Lease as a read-only Stream. + /// + /// The lease upon which to base the stream. + /// If true, disposing the stream also disposes the lease. + [return: NotNullIfNotNull("bytes")] + public static Stream? AsStream(this Lease? bytes, bool ownsLease = true) + { + if (bytes is null) + { + return null; // GIGO + } + + var segment = bytes.ArraySegment; + if (ownsLease) + { + return new LeaseMemoryStream(segment, bytes); + } + return new MemoryStream(segment.Array!, segment.Offset, segment.Count, false, true); + } + + /// + /// Decode a byte-Lease as a String, optionally specifying the encoding (UTF-8 if omitted). + /// + /// The bytes to decode. + /// The encoding to use. + [return: NotNullIfNotNull("bytes")] + public static string? DecodeString(this Lease bytes, Encoding? encoding = null) + { + if (bytes is null) + { + return null; + } + + encoding ??= Encoding.UTF8; + if (bytes.Length == 0) + { + return ""; + } + var segment = bytes.ArraySegment; + return encoding.GetString(segment.Array!, segment.Offset, segment.Count); + } + + /// + /// Decode a byte-Lease as a String, optionally specifying the encoding (UTF-8 if omitted). + /// + /// The bytes to decode. + /// The encoding to use. + [return: NotNullIfNotNull("bytes")] + public static Lease? DecodeLease(this Lease? bytes, Encoding? encoding = null) + { + if (bytes is null) + { + return null; + } + + encoding ??= Encoding.UTF8; + if (bytes.Length == 0) + { + return Lease.Empty; + } + var bytesSegment = bytes.ArraySegment; + var charCount = encoding.GetCharCount(bytesSegment.Array!, bytesSegment.Offset, bytesSegment.Count); + var chars = Lease.Create(charCount, false); + var charsSegment = chars.ArraySegment; + encoding.GetChars(bytesSegment.Array!, bytesSegment.Offset, bytesSegment.Count, charsSegment.Array!, charsSegment.Offset); + return chars; + } + + private sealed class LeaseMemoryStream : MemoryStream + { + private readonly IDisposable _parent; + + public LeaseMemoryStream(ArraySegment segment, IDisposable parent) : base(segment.Array!, segment.Offset, segment.Count, false, true) => _parent = parent; + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) _parent.Dispose(); + } + } + + // IMPORTANT: System.Numerics.Vectors is just... broken on .NET with anything < net472; the dependency + // indirection routinely fails and causes epic levels of fail. We're going to get around this by simply + // *not using SpanHelpers.IndexOf* (which is what uses it) for net < net472 builds. I've tried every + // trick (including some that are pure evil), and I can't see a better mechanism. Ultimately, the bindings + // fail in unusual and unexpected ways, causing: + // + // Could not load file or assembly 'System.Numerics.Vectors, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' + // or one of its dependencies.The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040) + // + // also; note that the NuGet tools *do not* reliably (or even occasionally) produce the correct + // assembly-binding-redirect entries to fix this up, so; it would present an unreasonable support burden + // otherwise. And yes, I've tried explicitly referencing System.Numerics.Vectors in the manifest to + // force it... nothing. Nada. +#if VECTOR_SAFE + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int VectorSafeIndexOf(this ReadOnlySpan span, byte value) + => span.IndexOf(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int VectorSafeIndexOfCRLF(this ReadOnlySpan span) + => span.IndexOf(CRLF); + + // note that this is *not* actually an array; this is compiled into a .data section + // (confirmed down to net472, which is the lowest TFM that uses this branch) + private static ReadOnlySpan CRLF => new byte[] { (byte)'\r', (byte)'\n' }; +#else + internal static int VectorSafeIndexOf(this ReadOnlySpan span, byte value) + { + // yes, this has zero optimization; I'm OK with this as the fallback strategy + for (int i = 0; i < span.Length; i++) + { + if (span[i] == value) return i; + } + return -1; + } + + internal static int VectorSafeIndexOfCRLF(this ReadOnlySpan span) + { + // yes, this has zero optimization; I'm OK with this as the fallback strategy + for (int i = 1; i < span.Length; i++) + { + if (span[i] == '\n' && span[i - 1] == '\r') return i - 1; + } + return -1; + } +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static T[]? ToArray(in this RawResult result, Projection selector) + => result.IsNull ? null : result.GetItems().ToArray(selector); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static TTo[]? ToArray(in this RawResult result, Projection selector, in TState state) + => result.IsNull ? null : result.GetItems().ToArray(selector, in state); + } +} diff --git a/src/StackExchange.Redis/FastHash.cs b/src/StackExchange.Redis/FastHash.cs new file mode 100644 index 000000000..49eb01b31 --- /dev/null +++ b/src/StackExchange.Redis/FastHash.cs @@ -0,0 +1,137 @@ +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace StackExchange.Redis; + +/// +/// This type is intended to provide fast hashing functions for small strings, for example well-known +/// RESP literals that are usually identifiable by their length and initial bytes; it is not intended +/// for general purpose hashing. All matches must also perform a sequence equality check. +/// +/// See HastHashGenerator.md for more information and intended usage. +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +[Conditional("DEBUG")] // evaporate in release +internal sealed class FastHashAttribute(string token = "") : Attribute +{ + public string Token => token; +} + +internal static class FastHash +{ + /* not sure we need this, but: retain for reference + + // Perform case-insensitive hash by masking (X and x differ by only 1 bit); this halves + // our entropy, but is still useful when case doesn't matter. + private const long CaseMask = ~0x2020202020202020; + + public static long Hash64CI(this ReadOnlySequence value) + => value.Hash64() & CaseMask; + public static long Hash64CI(this scoped ReadOnlySpan value) + => value.Hash64() & CaseMask; +*/ + + public static long Hash64(this ReadOnlySequence value) + { +#if NETCOREAPP3_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + var first = value.FirstSpan; +#else + var first = value.First.Span; +#endif + return first.Length >= sizeof(long) || value.IsSingleSegment + ? first.Hash64() : SlowHash64(value); + + static long SlowHash64(ReadOnlySequence value) + { + Span buffer = stackalloc byte[sizeof(long)]; + if (value.Length < sizeof(long)) + { + value.CopyTo(buffer); + buffer.Slice((int)value.Length).Clear(); + } + else + { + value.Slice(0, sizeof(long)).CopyTo(buffer); + } + return BitConverter.IsLittleEndian + ? Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(buffer)) + : BinaryPrimitives.ReadInt64LittleEndian(buffer); + } + } + + public static long Hash64(this scoped ReadOnlySpan value) + { + if (BitConverter.IsLittleEndian) + { + ref byte data = ref MemoryMarshal.GetReference(value); + return value.Length switch + { + 0 => 0, + 1 => data, // 0000000A + 2 => Unsafe.ReadUnaligned(ref data), // 000000BA + 3 => Unsafe.ReadUnaligned(ref data) | // 000000BA + (Unsafe.Add(ref data, 2) << 16), // 00000C00 + 4 => Unsafe.ReadUnaligned(ref data), // 0000DCBA + 5 => Unsafe.ReadUnaligned(ref data) | // 0000DCBA + ((long)Unsafe.Add(ref data, 4) << 32), // 000E0000 + 6 => Unsafe.ReadUnaligned(ref data) | // 0000DCBA + ((long)Unsafe.ReadUnaligned(ref Unsafe.Add(ref data, 4)) << 32), // 00FE0000 + 7 => Unsafe.ReadUnaligned(ref data) | // 0000DCBA + ((long)Unsafe.ReadUnaligned(ref Unsafe.Add(ref data, 4)) << 32) | // 00FE0000 + ((long)Unsafe.Add(ref data, 6) << 48), // 0G000000 + _ => Unsafe.ReadUnaligned(ref data), // HGFEDCBA + }; + } + +#pragma warning disable CS0618 // Type or member is obsolete + return Hash64Fallback(value); +#pragma warning restore CS0618 // Type or member is obsolete + } + + [Obsolete("Only exists for benchmarks (to show that we don't need to use it) and unit tests (for correctness)")] + internal static unsafe long Hash64Unsafe(scoped ReadOnlySpan value) + { + if (BitConverter.IsLittleEndian) + { + fixed (byte* ptr = &MemoryMarshal.GetReference(value)) + { + return value.Length switch + { + 0 => 0, + 1 => *ptr, // 0000000A + 2 => *(ushort*)ptr, // 000000BA + 3 => *(ushort*)ptr | // 000000BA + (ptr[2] << 16), // 00000C00 + 4 => *(int*)ptr, // 0000DCBA + 5 => (long)*(int*)ptr | // 0000DCBA + ((long)ptr[4] << 32), // 000E0000 + 6 => (long)*(int*)ptr | // 0000DCBA + ((long)*(ushort*)(ptr + 4) << 32), // 00FE0000 + 7 => (long)*(int*)ptr | // 0000DCBA + ((long)*(ushort*)(ptr + 4) << 32) | // 00FE0000 + ((long)ptr[6] << 48), // 0G000000 + _ => *(long*)ptr, // HGFEDCBA + }; + } + } + + return Hash64Fallback(value); + } + + [Obsolete("Only exists for unit tests and fallback")] + internal static long Hash64Fallback(scoped ReadOnlySpan value) + { + if (value.Length < sizeof(long)) + { + Span tmp = stackalloc byte[sizeof(long)]; + value.CopyTo(tmp); // ABC***** + tmp.Slice(value.Length).Clear(); // ABC00000 + return BinaryPrimitives.ReadInt64LittleEndian(tmp); // 00000CBA + } + + return BinaryPrimitives.ReadInt64LittleEndian(value); // HGFEDCBA + } +} diff --git a/src/StackExchange.Redis/Format.cs b/src/StackExchange.Redis/Format.cs new file mode 100644 index 000000000..86aa9910d --- /dev/null +++ b/src/StackExchange.Redis/Format.cs @@ -0,0 +1,549 @@ +using System; +using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Net; +using System.Runtime.CompilerServices; +using System.Text; + +#if UNIX_SOCKET +using System.Net.Sockets; +#endif + +namespace StackExchange.Redis +{ + internal static class Format + { +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER + public static int ParseInt32(ReadOnlySpan s) => int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo); + public static bool TryParseInt32(ReadOnlySpan s, out int value) => int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out value); +#endif + + public static int ParseInt32(string s) => int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo); + + public static long ParseInt64(string s) => long.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo); + + public static string ToString(int value) => value.ToString(NumberFormatInfo.InvariantInfo); + + public static bool TryParseBoolean(string s, out bool value) + { + if (bool.TryParse(s, out value)) return true; + + if (s == "1" || string.Equals(s, "yes", StringComparison.OrdinalIgnoreCase) || string.Equals(s, "on", StringComparison.OrdinalIgnoreCase)) + { + value = true; + return true; + } + if (s == "0" || string.Equals(s, "no", StringComparison.OrdinalIgnoreCase) || string.Equals(s, "off", StringComparison.OrdinalIgnoreCase)) + { + value = false; + return true; + } + value = false; + return false; + } + + public static bool TryParseInt32(string s, out int value) => + int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out value); + + internal static EndPoint ParseEndPoint(string host, int port) + { + if (IPAddress.TryParse(host, out IPAddress? ip)) return new IPEndPoint(ip, port); + return new DnsEndPoint(host, port); + } + + internal static bool TryParseEndPoint(string host, string? port, [NotNullWhen(true)] out EndPoint? endpoint) + { + if (!host.IsNullOrEmpty() && !port.IsNullOrEmpty() && TryParseInt32(port, out int i)) + { + endpoint = ParseEndPoint(host, i); + return true; + } + endpoint = null; + return false; + } + + internal static string ToString(long value) => value.ToString(NumberFormatInfo.InvariantInfo); + + internal static string ToString(ulong value) => value.ToString(NumberFormatInfo.InvariantInfo); + + internal static string ToString(double value) + { + if (double.IsInfinity(value)) + { + if (double.IsPositiveInfinity(value)) return "+inf"; + if (double.IsNegativeInfinity(value)) return "-inf"; + } + return value.ToString("G17", NumberFormatInfo.InvariantInfo); + } + + [return: NotNullIfNotNull("value")] + internal static string? ToString(object? value) => value switch + { + null => "", + long l => ToString(l), + int i => ToString(i), + float f => ToString(f), + double d => ToString(d), + EndPoint e => ToString(e), + _ => Convert.ToString(value, CultureInfo.InvariantCulture), + }; + + internal static string ToString(EndPoint? endpoint) + { + switch (endpoint) + { + case DnsEndPoint dns: + if (dns.Port == 0) return dns.Host; + return dns.Host + ":" + Format.ToString(dns.Port); + case IPEndPoint ip: + var addr = ip.Address.ToString(); + + if (ip.Port == 0) + { + // no port specified; use naked IP + return addr; + } + + if (addr.IndexOf(':') >= 0) + { + // ipv6 with port; use "[IP]:port" notation + return "[" + addr + "]:" + Format.ToString(ip.Port); + } + // ipv4 with port; use "IP:port" notation + return ip.Address + ":" + Format.ToString(ip.Port); +#if UNIX_SOCKET + case UnixDomainSocketEndPoint uds: + return "!" + uds.ToString(); +#endif + default: + return endpoint?.ToString() ?? ""; + } + } + + internal static string ToStringHostOnly(EndPoint endpoint) => + endpoint switch + { + DnsEndPoint dns => dns.Host, + IPEndPoint ip => ip.Address.ToString(), + _ => "", + }; + + internal static bool TryGetHostPort(EndPoint? endpoint, [NotNullWhen(true)] out string? host, [NotNullWhen(true)] out int? port) + { + if (endpoint is not null) + { + if (endpoint is IPEndPoint ip) + { + host = ip.Address.ToString(); + port = ip.Port; + return true; + } + if (endpoint is DnsEndPoint dns) + { + host = dns.Host; + port = dns.Port; + return true; + } + } + host = null; + port = null; + return false; + } + + internal static bool TryParseDouble(string? s, out double value) + { + if (s is null) + { + value = 0; + return false; + } + switch (s.Length) + { + case 0: + value = 0; + return false; + // single-digits + case 1 when s[0] >= '0' && s[0] <= '9': + value = s[0] - '0'; + return true; + // RESP3 spec demands inf/nan handling + case 3 when TryParseInfNaN(s.AsSpan(), true, out value): + case 4 when s[0] == '+' && TryParseInfNaN(s.AsSpan(1), true, out value): + case 4 when s[0] == '-' && TryParseInfNaN(s.AsSpan(1), false, out value): + return true; + } + return double.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out value); + + static bool TryParseInfNaN(ReadOnlySpan s, bool positive, out double value) + { + switch (s[0]) + { + case 'i': + case 'I': + if (s[1] is 'n' or 'N' && s[2] is 'f' or 'F') + { + value = positive ? double.PositiveInfinity : double.NegativeInfinity; + return true; + } + break; + case 'n': + case 'N': + if (s[1] is 'a' or 'A' && s[2] is 'n' or 'N') + { + value = double.NaN; + return true; + } + break; + } +#if NET6_0_OR_GREATER + Unsafe.SkipInit(out value); +#else + value = 0; +#endif + return false; + } + } + + internal static bool TryParseUInt64(string s, out ulong value) => + ulong.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out value); + + internal static bool TryParseUInt64(ReadOnlySpan s, out ulong value) => + Utf8Parser.TryParse(s, out value, out int bytes, standardFormat: 'D') & bytes == s.Length; + + internal static bool TryParseInt64(ReadOnlySpan s, out long value) => + Utf8Parser.TryParse(s, out value, out int bytes, standardFormat: 'D') & bytes == s.Length; + + internal static bool CouldBeInteger(string s) + { + if (string.IsNullOrEmpty(s) || s.Length > Format.MaxInt64TextLen) return false; + bool isSigned = s[0] == '-'; + for (int i = isSigned ? 1 : 0; i < s.Length; i++) + { + char c = s[i]; + if (c < '0' | c > '9') return false; + } + return true; + } + internal static bool CouldBeInteger(ReadOnlySpan s) + { + if (s.IsEmpty | s.Length > Format.MaxInt64TextLen) return false; + bool isSigned = s[0] == '-'; + for (int i = isSigned ? 1 : 0; i < s.Length; i++) + { + byte c = s[i]; + if (c < (byte)'0' | c > (byte)'9') return false; + } + return true; + } + + internal static bool TryParseInt64(string s, out long value) => + long.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out value); + + internal static bool TryParseDouble(ReadOnlySpan s, out double value) + { + switch (s.Length) + { + case 0: + value = 0; + return false; + // single-digits + case 1 when s[0] >= '0' && s[0] <= '9': + value = s[0] - '0'; + return true; + // RESP3 spec demands inf/nan handling + case 3 when TryParseInfNaN(s, true, out value): + case 4 when s[0] == '+' && TryParseInfNaN(s.Slice(1), true, out value): + case 4 when s[0] == '-' && TryParseInfNaN(s.Slice(1), false, out value): + return true; + } + return Utf8Parser.TryParse(s, out value, out int bytes) & bytes == s.Length; + + static bool TryParseInfNaN(ReadOnlySpan s, bool positive, out double value) + { + switch (s[0]) + { + case (byte)'i': + case (byte)'I': + if (s[1] is (byte)'n' or (byte)'N' && s[2] is (byte)'f' or (byte)'F') + { + value = positive ? double.PositiveInfinity : double.NegativeInfinity; + return true; + } + break; + case (byte)'n': + case (byte)'N': + if (s[1] is (byte)'a' or (byte)'A' && s[2] is (byte)'n' or (byte)'N') + { + value = double.NaN; + return true; + } + break; + } +#if NET6_0_OR_GREATER + Unsafe.SkipInit(out value); +#else + value = 0; +#endif + return false; + } + } + + /// + /// + /// Adapted from IPEndPointParser in Microsoft.AspNetCore + /// Link: . + /// + /// + /// Copyright (c) .NET Foundation. All rights reserved. + /// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + /// + /// + /// If Unix sockets are attempted but not supported. + internal static bool TryParseEndPoint(string? addressWithPort, [NotNullWhen(true)] out EndPoint? endpoint) + { + string addressPart; + string? portPart = null; + if (addressWithPort.IsNullOrEmpty()) + { + endpoint = null; + return false; + } + + if (addressWithPort[0] == '!') + { + if (addressWithPort.Length == 1) + { + endpoint = null; + return false; + } + +#if UNIX_SOCKET + endpoint = new UnixDomainSocketEndPoint(addressWithPort.Substring(1)); + return true; +#else + throw new PlatformNotSupportedException("Unix domain sockets require .NET Core 3 or above"); +#endif + } + var lastColonIndex = addressWithPort.LastIndexOf(':'); + if (lastColonIndex > 0) + { + // IPv4 with port or IPv6 + var closingIndex = addressWithPort.LastIndexOf(']'); + if (closingIndex > 0) + { + // IPv6 with brackets + addressPart = addressWithPort.Substring(1, closingIndex - 1); + if (closingIndex < lastColonIndex) + { + // IPv6 with port [::1]:80 + portPart = addressWithPort.Substring(lastColonIndex + 1); + } + } + else + { + // IPv6 without port or IPv4 + var firstColonIndex = addressWithPort.IndexOf(':'); + if (firstColonIndex != lastColonIndex) + { + // IPv6 ::1 + addressPart = addressWithPort; + } + else + { + // IPv4 with port 127.0.0.1:123 + addressPart = addressWithPort.Substring(0, firstColonIndex); + portPart = addressWithPort.Substring(firstColonIndex + 1); + } + } + } + else + { + // IPv4 without port + addressPart = addressWithPort; + } + + int? port = 0; + if (portPart != null) + { + if (TryParseInt32(portPart, out var portVal)) + { + port = portVal; + } + else + { + // Invalid port, return + endpoint = null; + return false; + } + } + + if (IPAddress.TryParse(addressPart, out IPAddress? address)) + { + endpoint = new IPEndPoint(address, port ?? 0); + return true; + } + else + { + endpoint = new DnsEndPoint(addressPart, port ?? 0); + return true; + } + } + + internal static string GetString(ReadOnlySequence buffer) + { + if (buffer.IsSingleSegment) return GetString(buffer.First.Span); + + var arr = ArrayPool.Shared.Rent(checked((int)buffer.Length)); + var span = new Span(arr, 0, (int)buffer.Length); + buffer.CopyTo(span); + string s = GetString(span); + ArrayPool.Shared.Return(arr); + return s; + } + + internal static unsafe string GetString(ReadOnlySpan span) + { + if (span.IsEmpty) return ""; +#if NETCOREAPP3_1_OR_GREATER + return Encoding.UTF8.GetString(span); +#else + fixed (byte* ptr = span) + { + return Encoding.UTF8.GetString(ptr, span.Length); + } +#endif + } + + [DoesNotReturn] + private static void ThrowFormatFailed() => throw new InvalidOperationException("TryFormat failed"); + + internal const int + MaxInt32TextLen = 11, // -2,147,483,648 (not including the commas) + MaxInt64TextLen = 20, // -9,223,372,036,854,775,808 (not including the commas), + MaxDoubleTextLen = 40; // we use G17, allow for sign/E/and allow plenty of panic room + + internal static int MeasureDouble(double value) + { + if (double.IsInfinity(value)) return 4; // +inf / -inf + +#if NET8_0_OR_GREATER // can use IUtf8Formattable + Span buffer = stackalloc byte[MaxDoubleTextLen]; + if (value.TryFormat(buffer, out int len, "G17", NumberFormatInfo.InvariantInfo)) + { + return len; + } +#endif + // fallback (TFM or unexpected size) + var s = value.ToString("G17", NumberFormatInfo.InvariantInfo); // this looks inefficient, but is how Utf8Formatter works too, just: more direct + return s.Length; + } + + internal static int FormatDouble(double value, Span destination) + { + if (double.IsInfinity(value)) + { + if (!(double.IsPositiveInfinity(value) ? "+inf"u8 : "-inf"u8).TryCopyTo(destination)) ThrowFormatFailed(); + return 4; + } + +#if NET8_0_OR_GREATER // can use IUtf8Formattable + if (!value.TryFormat(destination, out int len, "G17", NumberFormatInfo.InvariantInfo)) + { + ThrowFormatFailed(); + } + + return len; +#else + var s = value.ToString("G17", NumberFormatInfo.InvariantInfo); // this looks inefficient, but is how Utf8Formatter works too, just: more direct + if (s.Length > destination.Length) ThrowFormatFailed(); + + var chars = s.AsSpan(); + for (int i = 0; i < chars.Length; i++) + { + destination[i] = (byte)chars[i]; + } + return chars.Length; +#endif + } + + internal static int MeasureInt64(long value) + { + Span valueSpan = stackalloc byte[MaxInt64TextLen]; + return FormatInt64(value, valueSpan); + } + + internal static int FormatInt64(long value, Span destination) + { + if (!Utf8Formatter.TryFormat(value, destination, out var len)) + ThrowFormatFailed(); + return len; + } + + internal static int MeasureUInt64(ulong value) + { + Span valueSpan = stackalloc byte[MaxInt64TextLen]; + return FormatUInt64(value, valueSpan); + } + + internal static int FormatUInt64(ulong value, Span destination) + { + if (!Utf8Formatter.TryFormat(value, destination, out var len)) + ThrowFormatFailed(); + return len; + } + + internal static int FormatInt32(int value, Span destination) + { + if (!Utf8Formatter.TryFormat(value, destination, out var len)) + ThrowFormatFailed(); + return len; + } + + internal static bool TryParseVersion(ReadOnlySpan input, [NotNullWhen(true)] out Version? version) + { +#if NETCOREAPP3_1_OR_GREATER + if (Version.TryParse(input, out version)) return true; + // allow major-only (Version doesn't do this, because... reasons?) + if (TryParseInt32(input, out int i32)) + { + version = new(i32, 0); + return true; + } + version = null; + return false; +#else + if (input.IsEmpty) + { + version = null; + return false; + } + unsafe + { + fixed (char* ptr = input) + { + string s = new(ptr, 0, input.Length); + return TryParseVersion(s, out version); + } + } +#endif + } + + internal static bool TryParseVersion(string? input, [NotNullWhen(true)] out Version? version) + { + if (input is not null) + { + if (Version.TryParse(input, out version)) return true; + // allow major-only (Version doesn't do this, because... reasons?) + if (TryParseInt32(input, out int i32)) + { + version = new(i32, 0); + return true; + } + } + version = null; + return false; + } + } +} diff --git a/src/StackExchange.Redis/FrameworkShims.cs b/src/StackExchange.Redis/FrameworkShims.cs new file mode 100644 index 000000000..9472df9ae --- /dev/null +++ b/src/StackExchange.Redis/FrameworkShims.cs @@ -0,0 +1,39 @@ +#pragma warning disable SA1403 // single namespace + +#if NET5_0_OR_GREATER +// context: https://github.com/StackExchange/StackExchange.Redis/issues/2619 +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))] +#else +// To support { get; init; } properties +using System.ComponentModel; +using System.Text; + +namespace System.Runtime.CompilerServices +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit { } +} +#endif + +#if !(NETCOREAPP || NETSTANDARD2_1_OR_GREATER) + +namespace System.Text +{ + internal static class EncodingExtensions + { + public static unsafe int GetBytes(this Encoding encoding, ReadOnlySpan source, Span destination) + { + fixed (byte* bPtr = destination) + { + fixed (char* cPtr = source) + { + return encoding.GetBytes(cPtr, source.Length, bPtr, destination.Length); + } + } + } + } +} +#endif + + +#pragma warning restore SA1403 diff --git a/src/StackExchange.Redis/GlobalSuppressions.cs b/src/StackExchange.Redis/GlobalSuppressions.cs new file mode 100644 index 000000000..84d04d110 --- /dev/null +++ b/src/StackExchange.Redis/GlobalSuppressions.cs @@ -0,0 +1,21 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "Pending", Scope = "member", Target = "~P:StackExchange.Redis.Message.IsAdmin")] +[assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.ServerEndPoint.GetBridge(StackExchange.Redis.RedisCommand,System.Boolean)~StackExchange.Redis.PhysicalBridge")] +[assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.RedisValue.op_Equality(StackExchange.Redis.RedisValue,StackExchange.Redis.RedisValue)~System.Boolean")] +[assembly: SuppressMessage("Style", "IDE0075:Simplify conditional expression", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.RedisSubscriber.Unsubscribe(StackExchange.Redis.RedisChannel@,System.Action{StackExchange.Redis.RedisChannel,StackExchange.Redis.RedisValue},StackExchange.Redis.ChannelMessageQueue,StackExchange.Redis.CommandFlags)~System.Boolean")] +[assembly: SuppressMessage("Roslynator", "RCS1104:Simplify conditional expression.", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.RedisSubscriber.Unsubscribe(StackExchange.Redis.RedisChannel@,System.Action{StackExchange.Redis.RedisChannel,StackExchange.Redis.RedisValue},StackExchange.Redis.ChannelMessageQueue,StackExchange.Redis.CommandFlags)~System.Boolean")] +[assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Message.IsPrimaryOnly(StackExchange.Redis.RedisCommand)~System.Boolean")] +[assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Message.RequiresDatabase(StackExchange.Redis.RedisCommand)~System.Boolean")] +[assembly: SuppressMessage("Style", "IDE0180:Use tuple to swap values", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.RedisDatabase.ReverseLimits(StackExchange.Redis.Order,StackExchange.Redis.Exclude@,StackExchange.Redis.RedisValue@,StackExchange.Redis.RedisValue@)")] +[assembly: SuppressMessage("Style", "IDE0180:Use tuple to swap values", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.RedisDatabase.GetSortedSetRangeByScoreMessage(StackExchange.Redis.RedisKey,System.Double,System.Double,StackExchange.Redis.Exclude,StackExchange.Redis.Order,System.Int64,System.Int64,StackExchange.Redis.CommandFlags,System.Boolean)~StackExchange.Redis.Message")] +[assembly: SuppressMessage("Reliability", "CA2012:Use ValueTasks correctly", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.PhysicalConnection.FlushSync(System.Boolean,System.Int32)~StackExchange.Redis.WriteResult")] +[assembly: SuppressMessage("Usage", "CA2219:Do not raise exceptions in finally clauses", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.PhysicalBridge.ProcessBacklogAsync~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Usage", "CA2249:Consider using 'string.Contains' instead of 'string.IndexOf'", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.ClientInfo.AddFlag(StackExchange.Redis.ClientFlags@,System.String,StackExchange.Redis.ClientFlags,System.Char)")] +[assembly: SuppressMessage("Style", "IDE0070:Use 'System.HashCode'", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.CommandBytes.GetHashCode~System.Int32")] +[assembly: SuppressMessage("Roslynator", "RCS1085:Use auto-implemented property.", Justification = "Pending", Scope = "member", Target = "~P:StackExchange.Redis.RedisValue.OverlappedValueInt64")] diff --git a/src/StackExchange.Redis/HashSlotMovedEventArgs.cs b/src/StackExchange.Redis/HashSlotMovedEventArgs.cs new file mode 100644 index 000000000..876088e5c --- /dev/null +++ b/src/StackExchange.Redis/HashSlotMovedEventArgs.cs @@ -0,0 +1,55 @@ +using System; +using System.Net; +using System.Text; + +namespace StackExchange.Redis +{ + /// + /// Contains information about individual hash-slot relocations. + /// + public class HashSlotMovedEventArgs : EventArgs, ICompletable + { + private readonly object sender; + private readonly EventHandler? handler; + + /// + /// The hash-slot that was relocated. + /// + public int HashSlot { get; } + + /// + /// The old endpoint for this hash-slot (if known). + /// + public EndPoint? OldEndPoint { get; } + + /// + /// The new endpoint for this hash-slot (if known). + /// + public EndPoint NewEndPoint { get; } + + internal HashSlotMovedEventArgs(EventHandler? handler, object sender, int hashSlot, EndPoint? old, EndPoint @new) + { + this.handler = handler; + this.sender = sender; + HashSlot = hashSlot; + OldEndPoint = old; + NewEndPoint = @new; + } + + /// + /// This constructor is only for testing purposes. + /// + /// The source of the event. + /// Hash slot. + /// Old endpoint. + /// New endpoint. + public HashSlotMovedEventArgs(object sender, int hashSlot, EndPoint old, EndPoint @new) + : this(null, sender, hashSlot, old, @new) + { + } + + bool ICompletable.TryComplete(bool isAsync) => ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync); + + void ICompletable.AppendStormLog(StringBuilder sb) => sb.Append("event, slot-moved: ").Append(HashSlot); + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/IBatch.cs b/src/StackExchange.Redis/Interfaces/IBatch.cs similarity index 76% rename from StackExchange.Redis/StackExchange/Redis/IBatch.cs rename to src/StackExchange.Redis/Interfaces/IBatch.cs index c98e9013b..a0a71becb 100644 --- a/StackExchange.Redis/StackExchange/Redis/IBatch.cs +++ b/src/StackExchange.Redis/Interfaces/IBatch.cs @@ -1,8 +1,8 @@ namespace StackExchange.Redis { /// - /// Represents a block of operations that will be sent to the server together; - /// this can be useful to reduce packet fragmentation on slow connections - it + /// Represents a block of operations that will be sent to the server together. + /// This can be useful to reduce packet fragmentation on slow connections - it /// can improve the time to get *all* the operations processed, with the trade-off /// of a slower time to get the *first* operation processed; this is usually /// a good thing. Unless this batch is a transaction, there is no guarantee @@ -12,9 +12,8 @@ public interface IBatch : IDatabaseAsync { /// /// Execute the batch operation, sending all queued commands to the server. - /// Note that this operation is neither synchronous nor truly asyncronous - it - /// simply enqueues the buffered messages. To check on completion, you should - /// check the individual responses. + /// Note that this operation is neither synchronous nor truly asynchronous - it simply enqueues the buffered messages. + /// To check on completion, you should check the individual responses. /// void Execute(); } diff --git a/StackExchange.Redis/StackExchange/Redis/ICompletable.cs b/src/StackExchange.Redis/Interfaces/ICompletable.cs similarity index 81% rename from StackExchange.Redis/StackExchange/Redis/ICompletable.cs rename to src/StackExchange.Redis/Interfaces/ICompletable.cs index d469fb57b..875110334 100644 --- a/StackExchange.Redis/StackExchange/Redis/ICompletable.cs +++ b/src/StackExchange.Redis/Interfaces/ICompletable.cs @@ -2,7 +2,7 @@ namespace StackExchange.Redis { - interface ICompletable + internal interface ICompletable { void AppendStormLog(StringBuilder sb); diff --git a/src/StackExchange.Redis/Interfaces/IConnectionMultiplexer.cs b/src/StackExchange.Redis/Interfaces/IConnectionMultiplexer.cs new file mode 100644 index 000000000..96b4ce8f6 --- /dev/null +++ b/src/StackExchange.Redis/Interfaces/IConnectionMultiplexer.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections.Concurrent; +using System.ComponentModel; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using StackExchange.Redis.Maintenance; +using StackExchange.Redis.Profiling; +using static StackExchange.Redis.ConnectionMultiplexer; + +namespace StackExchange.Redis; + +internal interface IInternalConnectionMultiplexer : IConnectionMultiplexer +{ + bool AllowConnect { get; set; } + + bool IgnoreConnect { get; set; } + + ReadOnlySpan GetServerSnapshot(); + ServerEndPoint GetServerEndPoint(EndPoint endpoint); + + ConfigurationOptions RawConfig { get; } + + long? GetConnectionId(EndPoint endPoint, ConnectionType type); + + ServerSelectionStrategy ServerSelectionStrategy { get; } + + int GetSubscriptionsCount(); + ConcurrentDictionary GetSubscriptions(); + + ConnectionMultiplexer UnderlyingMultiplexer { get; } +} + +/// +/// Represents the abstract multiplexer API. +/// +public interface IConnectionMultiplexer : IDisposable, IAsyncDisposable +{ + /// + /// Gets the client-name that will be used on all new connections. + /// + string ClientName { get; } + + /// + /// Gets the configuration of the connection. + /// + string Configuration { get; } + + /// + /// Gets the timeout associated with the connections. + /// + int TimeoutMilliseconds { get; } + + /// + /// The number of operations that have been performed on all connections. + /// + long OperationCount { get; } + + /// + /// Gets or sets whether asynchronous operations should be invoked in a way that guarantees their original delivery order. + /// + [Obsolete("Not supported; if you require ordered pub/sub, please see " + nameof(ChannelMessageQueue), false)] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + bool PreserveAsyncOrder { get; set; } + + /// + /// Indicates whether any servers are connected. + /// + bool IsConnected { get; } + + /// + /// Indicates whether any servers are connecting. + /// + bool IsConnecting { get; } + + /// + /// Should exceptions include identifiable details? (key names, additional annotations). + /// + [Obsolete($"Please use {nameof(ConfigurationOptions)}.{nameof(ConfigurationOptions.IncludeDetailInExceptions)} instead - this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + bool IncludeDetailInExceptions { get; set; } + + /// + /// Limit at which to start recording unusual busy patterns (only one log will be retained at a time. + /// Set to a negative value to disable this feature). + /// + int StormLogThreshold { get; set; } + + /// + /// Register a callback to provide an on-demand ambient session provider based on the calling context. + /// The implementing code is responsible for reliably resolving the same provider + /// based on ambient context, or returning null to not profile. + /// + /// The profiling session provider. + void RegisterProfiler(Func profilingSessionProvider); + + /// + /// Get summary statistics associates with this server. + /// + ServerCounters GetCounters(); + + /// + /// A server replied with an error message. + /// + event EventHandler ErrorMessage; + + /// + /// Raised whenever a physical connection fails. + /// + event EventHandler ConnectionFailed; + + /// + /// Raised whenever an internal error occurs (this is primarily for debugging). + /// + event EventHandler InternalError; + + /// + /// Raised whenever a physical connection is established. + /// + event EventHandler ConnectionRestored; + + /// + /// Raised when configuration changes are detected. + /// + event EventHandler ConfigurationChanged; + + /// + /// Raised when nodes are explicitly requested to reconfigure via broadcast. + /// This usually means primary/replica changes. + /// + event EventHandler ConfigurationChangedBroadcast; + + /// + /// Raised when server indicates a maintenance event is going to happen. + /// + event EventHandler ServerMaintenanceEvent; + + /// + /// Gets all endpoints defined on the multiplexer. + /// + /// Whether to return only the explicitly configured endpoints. + EndPoint[] GetEndPoints(bool configuredOnly = false); + + /// + /// Wait for a given asynchronous operation to complete (or timeout). + /// + /// The task to wait on. + void Wait(Task task); + + /// + /// Wait for a given asynchronous operation to complete (or timeout). + /// + /// The type in . + /// The task to wait on. + T Wait(Task task); + + /// + /// Wait for the given asynchronous operations to complete (or timeout). + /// + /// The tasks to wait on. + void WaitAll(params Task[] tasks); + + /// + /// Raised when a hash-slot has been relocated. + /// + event EventHandler HashSlotMoved; + + /// + /// Compute the hash-slot of a specified key. + /// + /// The key to get a slot ID for. + int HashSlot(RedisKey key); + + /// + /// Obtain a pub/sub subscriber connection to the specified server. + /// + /// The async state to pass to the created . + ISubscriber GetSubscriber(object? asyncState = null); + + /// + /// Obtain an interactive connection to a database inside redis. + /// + /// The database ID to get. + /// The async state to pass to the created . + IDatabase GetDatabase(int db = -1, object? asyncState = null); + + /// + /// Obtain a configuration API for an individual server. + /// + /// The host to get a server for. + /// The specific port for to get a server for. + /// The async state to pass to the created . + IServer GetServer(string host, int port, object? asyncState = null); + + /// + /// Obtain a configuration API for an individual server. + /// + /// The "host:port" string to get a server for. + /// The async state to pass to the created . + IServer GetServer(string hostAndPort, object? asyncState = null); + + /// + /// Obtain a configuration API for an individual server. + /// + /// The host to get a server for. + /// The specific port for to get a server for. + IServer GetServer(IPAddress host, int port); + + /// + /// Obtain a configuration API for an individual server. + /// + /// The endpoint to get a server for. + /// The async state to pass to the created . + IServer GetServer(EndPoint endpoint, object? asyncState = null); + + /// + /// Gets a server that would be used for a given key and flags. + /// + /// The endpoint to get a server for. In a non-cluster environment, this parameter is ignored. A key may be specified + /// on cluster, which will return a connection to an arbitrary server matching the specified flags. + /// The async state to pass to the created . + /// The command flags to use. + /// This method is particularly useful when communicating with a cluster environment, to obtain a connection to the server that owns the specified key + /// and ad-hoc commands with unusual routing requirements. Note that provides a connection that automatically routes commands by + /// looking for parameters, so this method is only necessary when used with commands that do not take a parameter, + /// but require consistent routing using key-like semantics. + IServer GetServer(RedisKey key, object? asyncState = null, CommandFlags flags = CommandFlags.None); + + /// + /// Obtain configuration APIs for all servers in this multiplexer. + /// + IServer[] GetServers(); + + /// + /// Reconfigure the current connections based on the existing configuration. + /// + /// The log to write output to. + Task ConfigureAsync(TextWriter? log = null); + + /// + /// Reconfigure the current connections based on the existing configuration. + /// + /// The log to write output to. + bool Configure(TextWriter? log = null); + + /// + /// Provides a text overview of the status of all connections. + /// + string GetStatus(); + + /// + /// Provides a text overview of the status of all connections. + /// + /// The log to write output to. + void GetStatus(TextWriter log); + + /// + /// See . + /// + string ToString(); + + /// + /// Close all connections and release all resources associated with this object. + /// + /// Whether to allow in-queue commands to complete first. + void Close(bool allowCommandsToComplete = true); + + /// + /// Close all connections and release all resources associated with this object. + /// + /// Whether to allow in-queue commands to complete first. + Task CloseAsync(bool allowCommandsToComplete = true); + + /// + /// Obtains the log of unusual busy patterns. + /// + string? GetStormLog(); + + /// + /// Resets the log of unusual busy patterns. + /// + void ResetStormLog(); + + /// + /// Request all compatible clients to reconfigure or reconnect. + /// + /// The command flags to use. + /// The number of instances known to have received the message (however, the actual number can be higher; returns -1 if the operation is pending). + long PublishReconfigure(CommandFlags flags = CommandFlags.None); + + /// + /// Request all compatible clients to reconfigure or reconnect. + /// + /// The command flags to use. + /// The number of instances known to have received the message (however, the actual number can be higher). + Task PublishReconfigureAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Get the hash-slot associated with a given key, if applicable; this can be useful for grouping operations. + /// + /// The key to get a the slot for. + int GetHashSlot(RedisKey key); + + /// + /// Write the configuration of all servers to an output stream. + /// + /// The destination stream to write the export to. + /// The options to use for this export. + void ExportConfiguration(Stream destination, ExportOptions options = ExportOptions.All); + + /// + /// Append a usage-specific modifier to the advertised library name; suffixes are de-duplicated + /// and sorted alphabetically (so adding 'a', 'b' and 'a' will result in suffix '-a-b'). + /// Connections will be updated as necessary (RESP2 subscription + /// connections will not show updates until those connections next connect). + /// + void AddLibraryNameSuffix(string suffix); +} diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.VectorSets.cs b/src/StackExchange.Redis/Interfaces/IDatabase.VectorSets.cs new file mode 100644 index 000000000..039075ec8 --- /dev/null +++ b/src/StackExchange.Redis/Interfaces/IDatabase.VectorSets.cs @@ -0,0 +1,183 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace StackExchange.Redis; + +/// +/// Describes functionality that is common to both standalone redis servers and redis clusters. +/// +public partial interface IDatabase +{ + // Vector Set operations + + /// + /// Add a vector to a vectorset. + /// + /// The key of the vectorset. + /// The data to add. + /// The flags to use for this operation. + /// if the element was added; if it already existed. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + bool VectorSetAdd( + RedisKey key, + VectorSetAddRequest request, + CommandFlags flags = CommandFlags.None); + + /// + /// Get the cardinality (number of elements) of a vectorset. + /// + /// The key of the vectorset. + /// The flags to use for this operation. + /// The cardinality of the vectorset. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + long VectorSetLength(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Get the dimension of vectors in a vectorset. + /// + /// The key of the vectorset. + /// The flags to use for this operation. + /// The dimension of vectors in the vectorset. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + int VectorSetDimension(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Get the vector for a member. + /// + /// The key of the vectorset. + /// The member name. + /// The flags to use for this operation. + /// The vector as a pooled memory lease. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Lease? VectorSetGetApproximateVector( + RedisKey key, + RedisValue member, + CommandFlags flags = CommandFlags.None); + + /// + /// Get JSON attributes for a member in a vectorset. + /// + /// The key of the vectorset. + /// The member name. + /// The flags to use for this operation. + /// The attributes as a JSON string. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + string? VectorSetGetAttributesJson(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + /// Get information about a vectorset. + /// + /// The key of the vectorset. + /// The flags to use for this operation. + /// Information about the vectorset. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + VectorSetInfo? VectorSetInfo(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Check if a member exists in a vectorset. + /// + /// The key of the vectorset. + /// The member name. + /// The flags to use for this operation. + /// True if the member exists, false otherwise. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + bool VectorSetContains(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + /// Get links/connections for a member in a vectorset. + /// + /// The key of the vectorset. + /// The member name. + /// The flags to use for this operation. + /// The linked members. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Lease? VectorSetGetLinks(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + /// Get links/connections with scores for a member in a vectorset. + /// + /// The key of the vectorset. + /// The member name. + /// The flags to use for this operation. + /// The linked members with their similarity scores. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Lease? VectorSetGetLinksWithScores( + RedisKey key, + RedisValue member, + CommandFlags flags = CommandFlags.None); + + /// + /// Get a random member from a vectorset. + /// + /// The key of the vectorset. + /// The flags to use for this operation. + /// A random member from the vectorset, or null if the vectorset is empty. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + RedisValue VectorSetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Get random members from a vectorset. + /// + /// The key of the vectorset. + /// The number of random members to return. + /// The flags to use for this operation. + /// Random members from the vectorset. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + RedisValue[] VectorSetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + /// Remove a member from a vectorset. + /// + /// The key of the vectorset. + /// The member to remove. + /// The flags to use for this operation. + /// if the member was removed; if it was not found. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + bool VectorSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + /// Set JSON attributes for a member in a vectorset. + /// + /// The key of the vectorset. + /// The member name. + /// The attributes to set as a JSON string. + /// The flags to use for this operation. + /// True if successful. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + bool VectorSetSetAttributesJson( + RedisKey key, + RedisValue member, +#if NET7_0_OR_GREATER + [StringSyntax(StringSyntaxAttribute.Json)] +#endif + string attributesJson, + CommandFlags flags = CommandFlags.None); + + /// + /// Find similar vectors using vector similarity search. + /// + /// The key of the vectorset. + /// The query to execute. + /// The flags to use for this operation. + /// Similar vectors with their similarity scores. + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Lease? VectorSetSimilaritySearch( + RedisKey key, + VectorSetSimilaritySearchRequest query, + CommandFlags flags = CommandFlags.None); +} diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs new file mode 100644 index 000000000..6c52e89bd --- /dev/null +++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs @@ -0,0 +1,3433 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net; + +// ReSharper disable once CheckNamespace +namespace StackExchange.Redis +{ + /// + /// Describes functionality that is common to both standalone redis servers and redis clusters. + /// + public partial interface IDatabase : IRedis, IDatabaseAsync + { + /// + /// The numeric identifier of this database. + /// + int Database { get; } + + /// + /// Allows creation of a group of operations that will be sent to the server as a single unit, + /// but which may or may not be processed on the server contiguously. + /// + /// The async object state to be passed into the created . + /// The created batch. + IBatch CreateBatch(object? asyncState = null); + + /// + /// Allows creation of a group of operations that will be sent to the server as a single unit, + /// and processed on the server as a single unit. + /// + /// The async object state to be passed into the created . + /// The created transaction. + ITransaction CreateTransaction(object? asyncState = null); + + /// + /// Atomically transfer a key from a source Redis instance to a destination Redis instance. + /// On success the key is deleted from the original instance by default, and is guaranteed to exist in the target instance. + /// + /// The key to migrate. + /// The server to migrate the key to. + /// The database to migrate the key to. + /// The timeout to use for the transfer. + /// The options to use for this migration. + /// The flags to use for this operation. + /// + void KeyMigrate(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the raw DEBUG OBJECT output for a key. + /// This command is not fully documented and should be avoided unless you have good reason, and then avoided anyway. + /// + /// The key to debug. + /// The flags to use for this migration. + /// The raw output from DEBUG OBJECT. + /// + RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Add the specified member to the set stored at key. + /// Specified members that are already a member of this set are ignored. + /// If key does not exist, a new set is created before adding the specified members. + /// + /// The key of the set. + /// The longitude of geo entry. + /// The latitude of the geo entry. + /// The value to set at this entry. + /// The flags to use for this operation. + /// if the specified member was not already present in the set, else . + /// + bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + /// Add the specified member to the set stored at key. + /// Specified members that are already a member of this set are ignored. + /// If key does not exist, a new set is created before adding the specified members. + /// + /// The key of the set. + /// The geo value to store. + /// The flags to use for this operation. + /// if the specified member was not already present in the set, else . + /// + bool GeoAdd(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None); + + /// + /// Add the specified members to the set stored at key. + /// Specified members that are already a member of this set are ignored. + /// If key does not exist, a new set is created before adding the specified members. + /// + /// The key of the set. + /// The geo values add to the set. + /// The flags to use for this operation. + /// The number of elements that were added to the set, not including all the elements already present into the set. + /// + long GeoAdd(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None); + + /// + /// Removes the specified member from the geo sorted set stored at key. + /// Non existing members are ignored. + /// + /// The key of the set. + /// The geo value to remove. + /// The flags to use for this operation. + /// if the member existed in the sorted set and was removed, else . + /// + bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + /// Return the distance between two members in the geospatial index represented by the sorted set. + /// + /// The key of the set. + /// The first member to check. + /// The second member to check. + /// The unit of distance to return (defaults to meters). + /// The flags to use for this operation. + /// The command returns the distance as a double (represented as a string) in the specified unit, or if one or both the elements are missing. + /// + double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None); + + /// + /// Return valid Geohash strings representing the position of one or more elements in a sorted set value representing a geospatial index (where elements were added using GEOADD). + /// + /// The key of the set. + /// The members to get. + /// The flags to use for this operation. + /// The command returns an array where each element is the Geohash corresponding to each member name passed as argument to the command. + /// + string?[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); + + /// + /// Return valid Geohash strings representing the position of one or more elements in a sorted set value representing a geospatial index (where elements were added using GEOADD). + /// + /// The key of the set. + /// The member to get. + /// The flags to use for this operation. + /// The command returns an array where each element is the Geohash corresponding to each member name passed as argument to the command. + /// + string? GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + /// Return the positions (longitude,latitude) of all the specified members of the geospatial index represented by the sorted set at key. + /// + /// The key of the set. + /// The members to get. + /// The flags to use for this operation. + /// + /// The command returns an array where each element is a two elements array representing longitude and latitude (x,y) of each member name passed as argument to the command. + /// Non existing elements are reported as NULL elements of the array. + /// + /// + GeoPosition?[] GeoPosition(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); + + /// + /// Return the positions (longitude,latitude) of all the specified members of the geospatial index represented by the sorted set at key. + /// + /// The key of the set. + /// The member to get. + /// The flags to use for this operation. + /// + /// The command returns an array where each element is a two elements array representing longitude and latitude (x,y) of each member name passed as argument to the command. + /// Non existing elements are reported as NULL elements of the array. + /// + /// + GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + /// Return the members of a sorted set populated with geospatial information using GEOADD, which are + /// within the borders of the area specified with the center location and the maximum distance from the center (the radius). + /// + /// The key of the set. + /// The member to get a radius of results from. + /// The radius to check. + /// The unit of (defaults to meters). + /// The count of results to get, -1 for unlimited. + /// The order of the results. + /// The search options to use. + /// The flags to use for this operation. + /// The results found within the radius, if any. + /// + GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None); + + /// + /// Return the members of a sorted set populated with geospatial information using GEOADD, which are + /// within the borders of the area specified with the center location and the maximum distance from the center (the radius). + /// + /// The key of the set. + /// The longitude of the point to get a radius of results from. + /// The latitude of the point to get a radius of results from. + /// The radius to check. + /// The unit of (defaults to meters). + /// The count of results to get, -1 for unlimited. + /// The order of the results. + /// The search options to use. + /// The flags to use for this operation. + /// The results found within the radius, if any. + /// + GeoRadiusResult[] GeoRadius(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None); + + /// + /// Return the members of the geo-encoded sorted set stored at bounded by the provided + /// , centered at the provided set . + /// + /// The key of the set. + /// The set member to use as the center of the shape. + /// The shape to use to bound the geo search. + /// The maximum number of results to pull back. + /// Whether or not to terminate the search after finding results. Must be true of count is -1. + /// The order to sort by (defaults to unordered). + /// The search options to use. + /// The flags for this operation. + /// The results found within the shape, if any. + /// + GeoRadiusResult[] GeoSearch(RedisKey key, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None); + + /// + /// Return the members of the geo-encoded sorted set stored at bounded by the provided + /// , centered at the point provided by the and . + /// + /// The key of the set. + /// The longitude of the center point. + /// The latitude of the center point. + /// The shape to use to bound the geo search. + /// The maximum number of results to pull back. + /// Whether or not to terminate the search after finding results. Must be true of count is -1. + /// The order to sort by (defaults to unordered). + /// The search options to use. + /// The flags for this operation. + /// The results found within the shape, if any. + /// + GeoRadiusResult[] GeoSearch(RedisKey key, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None); + + /// + /// Stores the members of the geo-encoded sorted set stored at bounded by the provided + /// , centered at the provided set . + /// + /// The key of the set. + /// The key to store the result at. + /// The set member to use as the center of the shape. + /// The shape to use to bound the geo search. + /// The maximum number of results to pull back. + /// Whether or not to terminate the search after finding results. Must be true of count is -1. + /// The order to sort by (defaults to unordered). + /// If set to true, the resulting set will be a regular sorted-set containing only distances, rather than a geo-encoded sorted-set. + /// The flags for this operation. + /// The size of the set stored at . + /// + long GeoSearchAndStore(RedisKey sourceKey, RedisKey destinationKey, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None); + + /// + /// Stores the members of the geo-encoded sorted set stored at bounded by the provided + /// , centered at the point provided by the and . + /// + /// The key of the set. + /// The key to store the result at. + /// The longitude of the center point. + /// The latitude of the center point. + /// The shape to use to bound the geo search. + /// The maximum number of results to pull back. + /// Whether or not to terminate the search after finding results. Must be true of count is -1. + /// The order to sort by (defaults to unordered). + /// If set to true, the resulting set will be a regular sorted-set containing only distances, rather than a geo-encoded sorted-set. + /// The flags for this operation. + /// The size of the set stored at . + /// + long GeoSearchAndStore(RedisKey sourceKey, RedisKey destinationKey, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None); + + /// + /// Decrements the number stored at field in the hash stored at key by decrement. + /// If key does not exist, a new key holding a hash is created. + /// If field does not exist the value is set to 0 before the operation is performed. + /// + /// The key of the hash. + /// The field in the hash to decrement. + /// The amount to decrement by. + /// The flags to use for this operation. + /// The value at field after the decrement operation. + /// + /// The range of values supported by HINCRBY is limited to 64 bit signed integers. + /// + /// + long HashDecrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None); + + /// + /// Decrement the specified field of an hash stored at key, and representing a floating point number, by the specified decrement. + /// If the field does not exist, it is set to 0 before performing the operation. + /// + /// The key of the hash. + /// The field in the hash to decrement. + /// The amount to decrement by. + /// The flags to use for this operation. + /// The value at field after the decrement operation. + /// + /// The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. + /// + /// + double HashDecrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None); + + /// + /// Removes the specified fields from the hash stored at key. + /// Non-existing fields are ignored. Non-existing keys are treated as empty hashes and this command returns 0. + /// + /// The key of the hash. + /// The field in the hash to delete. + /// The flags to use for this operation. + /// if the field was removed. + /// + bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// Removes the specified fields from the hash stored at key. + /// Non-existing fields are ignored. Non-existing keys are treated as empty hashes and this command returns 0. + /// + /// The key of the hash. + /// The fields in the hash to delete. + /// The flags to use for this operation. + /// The number of fields that were removed. + /// + long HashDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// Returns if field is an existing field in the hash stored at key. + /// + /// The key of the hash. + /// The field in the hash to check. + /// The flags to use for this operation. + /// if the hash contains field, if the hash does not contain field, or key does not exist. + /// + bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// Set the remaining time to live in milliseconds for the given set of fields of hash + /// After the timeout has expired, the field of the hash will automatically be deleted. + /// + /// The key of the hash. + /// The fields in the hash to set expire time. + /// The timeout to set. + /// under which condition the expiration will be set using . + /// The flags to use for this operation. + /// + /// Empty array if the key does not exist. Otherwise returns an array where each item is the result of operation for given fields: + /// + /// + /// Result + /// Description + /// + /// + /// 2 + /// Field deleted because the specified expiration time is due. + /// + /// + /// 1 + /// Expiration time set/updated. + /// + /// + /// 0 + /// Expiration time is not set/update (a specified ExpireWhen condition is not met). + /// + /// + /// -1 + /// No such field exists. + /// + /// + /// + ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Set the time out on a field of the given set of fields of hash. + /// After the timeout has expired, the field of the hash will automatically be deleted. + /// + /// The key of the hash. + /// The fields in the hash to set expire time. + /// The exact date to expiry to set. + /// under which condition the expiration will be set using . + /// The flags to use for this operation. + /// + /// Empty array if the key does not exist. Otherwise returns an array where each item is the result of operation for given fields: + /// + /// + /// Result + /// Description + /// + /// + /// 2 + /// Field deleted because the specified expiration time is due. + /// + /// + /// 1 + /// Expiration time set/updated. + /// + /// + /// 0 + /// Expiration time is not set/update (a specified ExpireWhen condition is not met). + /// + /// + /// -1 + /// No such field exists. + /// + /// + /// + ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it gets the expiration time as a Unix timestamp in milliseconds (milliseconds since the Unix epoch). + /// + /// The key of the hash. + /// The fields in the hash to get expire time. + /// The flags to use for this operation. + /// + /// Empty array if the key does not exist. Otherwise returns the result of operation for given fields: + /// + /// + /// Result + /// Description + /// + /// + /// > 0 + /// Expiration time, as a Unix timestamp in milliseconds. + /// + /// + /// -1 + /// Field has no associated expiration time. + /// + /// + /// -2 + /// No such field exists. + /// + /// + /// + long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it removes the expiration time. + /// + /// The key of the hash. + /// The fields in the hash to remove expire time. + /// The flags to use for this operation. + /// + /// Empty array if the key does not exist. Otherwise returns the result of operation for given fields: + /// + /// + /// Result + /// Description + /// + /// + /// 1 + /// Expiration time was removed. + /// + /// + /// -1 + /// Field has no associated expiration time. + /// + /// + /// -2 + /// No such field exists. + /// + /// + /// + PersistResult[] HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it gets the remaining time to live in milliseconds. + /// + /// The key of the hash. + /// The fields in the hash to get expire time. + /// The flags to use for this operation. + /// + /// Empty array if the key does not exist. Otherwise returns the result of operation for given fields: + /// + /// + /// Result + /// Description + /// + /// + /// > 0 + /// Time to live, in milliseconds. + /// + /// + /// -1 + /// Field has no associated expiration time. + /// + /// + /// -2 + /// No such field exists. + /// + /// + /// + long[] HashFieldGetTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the value associated with field in the hash stored at key. + /// + /// The key of the hash. + /// The field in the hash to get. + /// The flags to use for this operation. + /// The value associated with field, or when field is not present in the hash or key does not exist. + /// + RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the value associated with field in the hash stored at key. + /// + /// The key of the hash. + /// The field in the hash to get. + /// The flags to use for this operation. + /// The value associated with field, or when field is not present in the hash or key does not exist. + /// + Lease? HashGetLease(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the values associated with the specified fields in the hash stored at key. + /// For every field that does not exist in the hash, a value is returned. + /// Because non-existing keys are treated as empty hashes, running HMGET against a non-existing key will return a list of values. + /// + /// The key of the hash. + /// The fields in the hash to get. + /// The flags to use for this operation. + /// List of values associated with the given fields, in the same order as they are requested. + /// + RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the value associated with field in the hash stored at key. + /// + /// The key of the hash. + /// The field in the hash to get. + /// The flags to use for this operation. + /// The value associated with field, or when field is not present in the hash or key does not exist. + /// + RedisValue HashFieldGetAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the value associated with field in the hash stored at key. + /// + /// The key of the hash. + /// The field in the hash to get. + /// The flags to use for this operation. + /// The value associated with field, or when field is not present in the hash or key does not exist. + /// + Lease? HashFieldGetLeaseAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the values associated with the specified fields in the hash stored at key. + /// For every field that does not exist in the hash, a value is returned. + /// Because non-existing keys are treated as empty hashes, running HMGET against a non-existing key will return a list of values. + /// + /// The key of the hash. + /// The fields in the hash to get. + /// The flags to use for this operation. + /// List of values associated with the given fields, in the same order as they are requested. + /// + RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the value of the specified hash field and sets its expiration time. + /// + /// The key of the hash. + /// The field in the hash to get and set the expiration for. + /// The expiration time to set. + /// If true, the expiration will be removed. And 'expiry' parameter is ignored. + /// The flags to use for this operation. + /// The value of the specified hash field. + RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the value of the specified hash field and sets its expiration time. + /// + /// The key of the hash. + /// The field in the hash to get and set the expiration for. + /// The exact date and time to set the expiration to. + /// The flags to use for this operation. + /// The value of the specified hash field. + RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the value of the specified hash field and sets its expiration time, returning a lease. + /// + /// The key of the hash. + /// The field in the hash to get and set the expiration for. + /// The expiration time to set. + /// If true, the expiration will be removed. And 'expiry' parameter is ignored. + /// The flags to use for this operation. + /// The value of the specified hash field as a lease. + Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the value of the specified hash field and sets its expiration time, returning a lease. + /// + /// The key of the hash. + /// The field in the hash to get and set the expiration for. + /// The exact date and time to set the expiration to. + /// The flags to use for this operation. + /// The value of the specified hash field as a lease. + Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the values of the specified hash fields and sets their expiration times. + /// + /// The key of the hash. + /// The fields in the hash to get and set the expiration for. + /// The expiration time to set. + /// If true, the expiration will be removed. And 'expiry' parameter is ignored. + /// The flags to use for this operation. + /// The values of the specified hash fields. + RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the values of the specified hash fields and sets their expiration times. + /// + /// The key of the hash. + /// The fields in the hash to get and set the expiration for. + /// The exact date and time to set the expiration to. + /// The flags to use for this operation. + /// The values of the specified hash fields. + RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None); + + /// + /// Sets the value of the specified hash field and sets its expiration time. + /// + /// The key of the hash. + /// The field in the hash to set and set the expiration for. + /// The value in the hash to set and set the expiration for. + /// The expiration time to set. + /// Whether to maintain the existing field's TTL (KEEPTTL flag). + /// Which conditions to set the value under (defaults to always). + /// The flags to use for this operation. + /// 0 if no fields were set, 1 if all the fields were set. + RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Sets the value of the specified hash field and sets its expiration time. + /// + /// The key of the hash. + /// The field in the hash to set and set the expiration for. + /// The value in the hash to set and set the expiration for. + /// The exact date and time to set the expiration to. + /// Which conditions to set the value under (defaults to always). + /// The flags to use for this operation. + /// 0 if no fields were set, 1 if all the fields were set. + RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Sets the values of the specified hash fields and sets their expiration times. + /// + /// The key of the hash. + /// The fields in the hash to set and set the expiration for. + /// The expiration time to set. + /// Whether to maintain the existing fields' TTL (KEEPTTL flag). + /// Which conditions to set the values under (defaults to always). + /// The flags to use for this operation. + /// 0 if no fields were set, 1 if all the fields were set. + RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Sets the values of the specified hash fields and sets their expiration times. + /// + /// The key of the hash. + /// The fields in the hash to set and set the expiration for. + /// The exact date and time to set the expiration to. + /// Which conditions to set the values under (defaults to always). + /// The flags to use for this operation. + /// 0 if no fields were set, 1 if all the fields were set. + RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Returns all fields and values of the hash stored at key. + /// + /// The key of the hash to get all entries from. + /// The flags to use for this operation. + /// List of fields and their values stored in the hash, or an empty list when key does not exist. + /// + HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Increments the number stored at field in the hash stored at key by increment. + /// If key does not exist, a new key holding a hash is created. + /// If field does not exist the value is set to 0 before the operation is performed. + /// + /// The key of the hash. + /// The field in the hash to increment. + /// The amount to increment by. + /// The flags to use for this operation. + /// The value at field after the increment operation. + /// + /// The range of values supported by HINCRBY is limited to 64 bit signed integers. + /// + /// + long HashIncrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None); + + /// + /// Increment the specified field of an hash stored at key, and representing a floating point number, by the specified increment. + /// If the field does not exist, it is set to 0 before performing the operation. + /// + /// The key of the hash. + /// The field in the hash to increment. + /// The amount to increment by. + /// The flags to use for this operation. + /// The value at field after the increment operation. + /// + /// The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. + /// + /// + double HashIncrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None); + + /// + /// Returns all field names in the hash stored at key. + /// + /// The key of the hash. + /// The flags to use for this operation. + /// List of fields in the hash, or an empty list when key does not exist. + /// + RedisValue[] HashKeys(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the number of fields contained in the hash stored at key. + /// + /// The key of the hash. + /// The flags to use for this operation. + /// The number of fields in the hash, or 0 when key does not exist. + /// + long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Gets a random field from the hash at . + /// + /// The key of the hash. + /// The flags to use for this operation. + /// A random hash field name or if the hash does not exist. + /// + RedisValue HashRandomField(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Gets field names from the hash at . + /// + /// The key of the hash. + /// The number of fields to return. + /// The flags to use for this operation. + /// An array of hash field names of size of at most , or if the hash does not exist. + /// + RedisValue[] HashRandomFields(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + /// Gets field names and values from the hash at . + /// + /// The key of the hash. + /// The number of fields to return. + /// The flags to use for this operation. + /// An array of hash entries of size of at most , or if the hash does not exist. + /// + HashEntry[] HashRandomFieldsWithValues(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + /// The HSCAN command is used to incrementally iterate over a hash. + /// + /// The key of the hash. + /// The pattern of keys to get entries for. + /// The page size to iterate by. + /// The flags to use for this operation. + /// Yields all elements of the hash matching the pattern. + /// + IEnumerable HashScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags); + + /// + /// The HSCAN command is used to incrementally iterate over a hash. + /// Note: to resume an iteration via cursor, cast the original enumerable or enumerator to . + /// + /// The key of the hash. + /// The pattern of keys to get entries for. + /// The page size to iterate by. + /// The cursor position to start at. + /// The page offset to start at. + /// The flags to use for this operation. + /// Yields all elements of the hash matching the pattern. + /// + IEnumerable HashScan(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None); + + /// + /// The HSCAN command is used to incrementally iterate over a hash and return only field names. + /// Note: to resume an iteration via cursor, cast the original enumerable or enumerator to . + /// + /// The key of the hash. + /// The pattern of keys to get entries for. + /// The page size to iterate by. + /// The cursor position to start at. + /// The page offset to start at. + /// The flags to use for this operation. + /// Yields all elements of the hash matching the pattern. + /// + IEnumerable HashScanNoValues(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None); + + /// + /// Sets the specified fields to their respective values in the hash stored at key. + /// This command overwrites any specified fields that already exist in the hash, leaving other unspecified fields untouched. + /// If key does not exist, a new key holding a hash is created. + /// + /// The key of the hash. + /// The entries to set in the hash. + /// The flags to use for this operation. + /// + void HashSet(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// Sets field in the hash stored at key to value. + /// If key does not exist, a new key holding a hash is created. + /// If field already exists in the hash, it is overwritten. + /// + /// The key of the hash. + /// The field to set in the hash. + /// The value to set. + /// Which conditions under which to set the field value (defaults to always). + /// The flags to use for this operation. + /// if field is a new field in the hash and value was set, if field already exists in the hash and the value was updated. + /// + /// See + /// , + /// . + /// + bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the string length of the value associated with field in the hash stored at key. + /// + /// The key of the hash. + /// The field containing the string. + /// The flags to use for this operation. + /// The length of the string at field, or 0 when key does not exist. + /// + long HashStringLength(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// Returns all values in the hash stored at key. + /// + /// The key of the hash. + /// The flags to use for this operation. + /// List of values in the hash, or an empty list when key does not exist. + /// + RedisValue[] HashValues(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Adds the element to the HyperLogLog data structure stored at the variable name specified as first argument. + /// + /// The key of the hyperloglog. + /// The value to add. + /// The flags to use for this operation. + /// if at least 1 HyperLogLog internal register was altered, otherwise. + /// + bool HyperLogLogAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + /// Adds all the element arguments to the HyperLogLog data structure stored at the variable name specified as first argument. + /// + /// The key of the hyperloglog. + /// The values to add. + /// The flags to use for this operation. + /// if at least 1 HyperLogLog internal register was altered, otherwise. + /// + bool HyperLogLogAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the approximated cardinality computed by the HyperLogLog data structure stored at the specified variable, or 0 if the variable does not exist. + /// + /// The key of the hyperloglog. + /// The flags to use for this operation. + /// The approximated number of unique elements observed via HyperLogLogAdd. + /// + long HyperLogLogLength(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the approximated cardinality of the union of the HyperLogLogs passed, by internally merging the HyperLogLogs stored at the provided keys into a temporary hyperLogLog, or 0 if the variable does not exist. + /// + /// The keys of the hyperloglogs. + /// The flags to use for this operation. + /// The approximated number of unique elements observed via HyperLogLogAdd. + /// + long HyperLogLogLength(RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + /// Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of the observed Sets of the source HyperLogLog structures. + /// + /// The key of the merged hyperloglog. + /// The key of the first hyperloglog to merge. + /// The key of the second hyperloglog to merge. + /// The flags to use for this operation. + /// + void HyperLogLogMerge(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); + + /// + /// Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of the observed Sets of the source HyperLogLog structures. + /// + /// The key of the merged hyperloglog. + /// The keys of the hyperloglogs to merge. + /// The flags to use for this operation. + /// + void HyperLogLogMerge(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None); + + /// + /// Indicate exactly which redis server we are talking to. + /// + /// The key to check. + /// The flags to use for this operation. + /// The endpoint serving the key. + EndPoint? IdentifyEndpoint(RedisKey key = default, CommandFlags flags = CommandFlags.None); + + /// + /// Copies the value from the to the specified . + /// + /// The key of the source value to copy. + /// The destination key to copy the source to. + /// The database ID to store in. If default (-1), current database is used. + /// Whether to overwrite an existing values at . If and the key exists, the copy will not succeed. + /// The flags to use for this operation. + /// if key was copied. if key was not copied. + /// + bool KeyCopy(RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase = -1, bool replace = false, CommandFlags flags = CommandFlags.None); + + /// + /// Removes the specified key. A key is ignored if it does not exist. + /// If UNLINK is available (Redis 4.0+), it will be used. + /// + /// The key to delete. + /// The flags to use for this operation. + /// if the key was removed. + /// + /// See + /// , + /// . + /// + bool KeyDelete(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Removes the specified keys. A key is ignored if it does not exist. + /// If UNLINK is available (Redis 4.0+), it will be used. + /// + /// The keys to delete. + /// The flags to use for this operation. + /// The number of keys that were removed. + /// + /// See + /// , + /// . + /// + long KeyDelete(RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + /// Serialize the value stored at key in a Redis-specific format and return it to the user. + /// The returned value can be synthesized back into a Redis key using the RESTORE command. + /// + /// The key to dump. + /// The flags to use for this operation. + /// The serialized value. + /// + byte[]? KeyDump(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the internal encoding for the Redis object stored at . + /// + /// The key to dump. + /// The flags to use for this operation. + /// The Redis encoding for the value or is the key does not exist. + /// + string? KeyEncoding(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Returns if key exists. + /// + /// The key to check. + /// The flags to use for this operation. + /// if the key exists. if the key does not exist. + /// + bool KeyExists(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Indicates how many of the supplied keys exists. + /// + /// The keys to check. + /// The flags to use for this operation. + /// The number of keys that existed. + /// + long KeyExists(RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + /// Set a timeout on . + /// After the timeout has expired, the key will automatically be deleted. + /// A key with an associated timeout is said to be volatile in Redis terminology. + /// + /// The key to set the expiration for. + /// The timeout to set. + /// The flags to use for this operation. + /// if the timeout was set. if key does not exist or the timeout could not be set. + /// + /// If key is updated before the timeout has expired, then the timeout is removed as if the PERSIST command was invoked on key. + /// + /// For Redis versions < 2.1.3, existing timeouts cannot be overwritten. + /// So, if key already has an associated timeout, it will do nothing and return 0. + /// + /// + /// Since Redis 2.1.3, you can update the timeout of a key. + /// It is also possible to remove the timeout using the PERSIST command. + /// See the page on key expiry for more information. + /// + /// + /// See + /// , + /// , + /// . + /// + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags); + + /// + /// Set a timeout on . + /// After the timeout has expired, the key will automatically be deleted. + /// A key with an associated timeout is said to be volatile in Redis terminology. + /// + /// The key to set the expiration for. + /// The timeout to set. + /// In Redis 7+, we can choose under which condition the expiration will be set using . + /// The flags to use for this operation. + /// if the timeout was set. if key does not exist or the timeout could not be set. + /// + /// See + /// , + /// . + /// + bool KeyExpire(RedisKey key, TimeSpan? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Set a timeout on . + /// After the timeout has expired, the key will automatically be deleted. + /// A key with an associated timeout is said to be volatile in Redis terminology. + /// + /// The key to set the expiration for. + /// The exact date to expiry to set. + /// The flags to use for this operation. + /// if the timeout was set. if key does not exist or the timeout could not be set. + /// + /// If key is updated before the timeout has expired, then the timeout is removed as if the PERSIST command was invoked on key. + /// + /// For Redis versions < 2.1.3, existing timeouts cannot be overwritten. + /// So, if key already has an associated timeout, it will do nothing and return 0. + /// + /// + /// Since Redis 2.1.3, you can update the timeout of a key. + /// It is also possible to remove the timeout using the PERSIST command. + /// See the page on key expiry for more information. + /// + /// + /// See + /// , + /// , + /// . + /// + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags); + + /// + /// Set a timeout on . + /// After the timeout has expired, the key will automatically be deleted. + /// A key with an associated timeout is said to be volatile in Redis terminology. + /// + /// The key to set the expiration for. + /// The timeout to set. + /// In Redis 7+, we choose under which condition the expiration will be set using . + /// The flags to use for this operation. + /// if the timeout was set. if key does not exist or the timeout could not be set. + /// + /// See + /// , + /// . + /// + bool KeyExpire(RedisKey key, DateTime? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the absolute time at which the given will expire, if it exists and has an expiration. + /// + /// The key to get the expiration for. + /// The flags to use for this operation. + /// The time at which the given key will expire, or if the key does not exist or has no associated expiration time. + /// + /// See + /// , + /// . + /// + DateTime? KeyExpireTime(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the logarithmic access frequency counter of the object stored at . + /// The command is only available when the maxmemory-policy configuration directive is set to + /// one of the LFU policies. + /// + /// The key to get a frequency count for. + /// The flags to use for this operation. + /// The number of logarithmic access frequency counter, ( if the key does not exist). + /// + long? KeyFrequency(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the time since the object stored at the specified key is idle (not requested by read or write operations). + /// + /// The key to get the time of. + /// The flags to use for this operation. + /// The time since the object stored at the specified key is idle. + /// + TimeSpan? KeyIdleTime(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Move key from the currently selected database (see SELECT) to the specified destination database. + /// When key already exists in the destination database, or it does not exist in the source database, it does nothing. + /// It is possible to use MOVE as a locking primitive because of this. + /// + /// The key to move. + /// The database to move the key to. + /// The flags to use for this operation. + /// if key was moved. if key was not moved. + /// + bool KeyMove(RedisKey key, int database, CommandFlags flags = CommandFlags.None); + + /// + /// Remove the existing timeout on key, turning the key from volatile (a key with an expire set) to persistent (a key that will never expire as no timeout is associated). + /// + /// The key to persist. + /// The flags to use for this operation. + /// if the timeout was removed. if key does not exist or does not have an associated timeout. + /// + bool KeyPersist(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Return a random key from the currently selected database. + /// + /// The flags to use for this operation. + /// The random key, or when the database is empty. + /// + RedisKey KeyRandom(CommandFlags flags = CommandFlags.None); + + /// + /// Returns the reference count of the object stored at . + /// + /// The key to get a reference count for. + /// The flags to use for this operation. + /// The number of references ( if the key does not exist). + /// + long? KeyRefCount(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Renames to . + /// It returns an error when the source and destination names are the same, or when key does not exist. + /// + /// The key to rename. + /// The key to rename to. + /// What conditions to rename under (defaults to always). + /// The flags to use for this operation. + /// if the key was renamed, otherwise. + /// + /// See + /// , + /// . + /// + bool KeyRename(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Create a key associated with a value that is obtained by deserializing the provided serialized value (obtained via DUMP). + /// If is 0 the key is created without any expire, otherwise the specified expire time (in milliseconds) is set. + /// + /// The key to restore. + /// The value of the key. + /// The expiry to set. + /// The flags to use for this operation. + /// + void KeyRestore(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the remaining time to live of a key that has a timeout. + /// This introspection capability allows a Redis client to check how many seconds a given key will continue to be part of the dataset. + /// + /// The key to check. + /// The flags to use for this operation. + /// TTL, or when key does not exist or does not have a timeout. + /// + TimeSpan? KeyTimeToLive(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Alters the last access time of a key. + /// + /// The key to touch. + /// The flags to use for this operation. + /// if the key was touched, otherwise. + /// + bool KeyTouch(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Alters the last access time of the specified . A key is ignored if it does not exist. + /// + /// The keys to touch. + /// The flags to use for this operation. + /// The number of keys that were touched. + /// + long KeyTouch(RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the string representation of the type of the value stored at key. + /// The different types that can be returned are: string, list, set, zset and hash. + /// + /// The key to get the type of. + /// The flags to use for this operation. + /// Type of key, or none when key does not exist. + /// + RedisType KeyType(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the element at index in the list stored at key. + /// The index is zero-based, so 0 means the first element, 1 the second element and so on. + /// Negative indices can be used to designate elements starting at the tail of the list. + /// Here, -1 means the last element, -2 means the penultimate and so forth. + /// + /// The key of the list. + /// The index position to get the value at. + /// The flags to use for this operation. + /// The requested element, or when index is out of range. + /// + RedisValue ListGetByIndex(RedisKey key, long index, CommandFlags flags = CommandFlags.None); + + /// + /// Inserts value in the list stored at key either before or after the reference value pivot. + /// When key does not exist, it is considered an empty list and no operation is performed. + /// + /// The key of the list. + /// The value to insert after. + /// The value to insert. + /// The flags to use for this operation. + /// The length of the list after the insert operation, or -1 when the value pivot was not found. + /// + long ListInsertAfter(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + /// Inserts value in the list stored at key either before or after the reference value pivot. + /// When key does not exist, it is considered an empty list and no operation is performed. + /// + /// The key of the list. + /// The value to insert before. + /// The value to insert. + /// The flags to use for this operation. + /// The length of the list after the insert operation, or -1 when the value pivot was not found. + /// + long ListInsertBefore(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + /// Removes and returns the first element of the list stored at key. + /// + /// The key of the list. + /// The flags to use for this operation. + /// The value of the first element, or when key does not exist. + /// + RedisValue ListLeftPop(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Removes and returns count elements from the head of the list stored at key. + /// If the list contains less than count elements, removes and returns the number of elements in the list. + /// + /// The key of the list. + /// The number of elements to remove. + /// The flags to use for this operation. + /// Array of values that were popped, or if the key doesn't exist. + /// + RedisValue[] ListLeftPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + /// Removes and returns at most elements from the first non-empty list in . + /// Starts on the left side of the list. + /// + /// The keys to look through for elements to pop. + /// The maximum number of elements to pop from the list. + /// The flags to use for this operation. + /// A span of contiguous elements from the list, or if no non-empty lists are found. + /// + ListPopResult ListLeftPop(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None); + + /// + /// Scans through the list stored at looking for , returning the 0-based + /// index of the first matching element. + /// + /// The key of the list. + /// The element to search for. + /// The rank of the first element to return, within the sub-list of matching indexes in the case of multiple matches. + /// The maximum number of elements to scan through before stopping, defaults to 0 (a full scan of the list.) + /// The flags to use for this operation. + /// The 0-based index of the first matching element, or -1 if not found. + /// + long ListPosition(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None); + + /// + /// Scans through the list stored at looking for instances of , returning the 0-based + /// indexes of any matching elements. + /// + /// The key of the list. + /// The element to search for. + /// The number of matches to find. A count of 0 will return the indexes of all occurrences of the element. + /// The rank of the first element to return, within the sub-list of matching indexes in the case of multiple matches. + /// The maximum number of elements to scan through before stopping, defaults to 0 (a full scan of the list.) + /// The flags to use for this operation. + /// An array of at most of indexes of matching elements. If none are found, and empty array is returned. + /// + long[] ListPositions(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None); + + /// + /// Insert the specified value at the head of the list stored at key. + /// If key does not exist, it is created as empty list before performing the push operations. + /// + /// The key of the list. + /// The value to add to the head of the list. + /// Which conditions to add to the list under (defaults to always). + /// The flags to use for this operation. + /// The length of the list after the push operations. + /// + /// See + /// , + /// . + /// + long ListLeftPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Insert the specified value at the head of the list stored at key. + /// If key does not exist, it is created as empty list before performing the push operations. + /// + /// The key of the list. + /// The value to add to the head of the list. + /// Which conditions to add to the list under (defaults to always). + /// The flags to use for this operation. + /// The length of the list after the push operations. + /// + /// See + /// , + /// . + /// + long ListLeftPush(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Insert all the specified values at the head of the list stored at key. + /// If key does not exist, it is created as empty list before performing the push operations. + /// Elements are inserted one after the other to the head of the list, from the leftmost element to the rightmost element. + /// So for instance the command LPUSH mylist a b c will result into a list containing c as first element, b as second element and a as third element. + /// + /// The key of the list. + /// The values to add to the head of the list. + /// The flags to use for this operation. + /// The length of the list after the push operations. + /// + long ListLeftPush(RedisKey key, RedisValue[] values, CommandFlags flags); + + /// + /// Returns the length of the list stored at key. If key does not exist, it is interpreted as an empty list and 0 is returned. + /// + /// The key of the list. + /// The flags to use for this operation. + /// The length of the list at key. + /// + long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Returns and removes the first or last element of the list stored at , and pushes the element + /// as the first or last element of the list stored at . + /// + /// The key of the list to remove from. + /// The key of the list to move to. + /// What side of the list to remove from. + /// What side of the list to move to. + /// The flags to use for this operation. + /// The element being popped and pushed or if there is no element to move. + /// + RedisValue ListMove(RedisKey sourceKey, RedisKey destinationKey, ListSide sourceSide, ListSide destinationSide, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the specified elements of the list stored at key. + /// The offsets start and stop are zero-based indexes, with 0 being the first element of the list (the head of the list), 1 being the next element and so on. + /// These offsets can also be negative numbers indicating offsets starting at the end of the list.For example, -1 is the last element of the list, -2 the penultimate, and so on. + /// Note that if you have a list of numbers from 0 to 100, LRANGE list 0 10 will return 11 elements, that is, the rightmost item is included. + /// + /// The key of the list. + /// The start index of the list. + /// The stop index of the list. + /// The flags to use for this operation. + /// List of elements in the specified range. + /// + RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None); + + /// + /// Removes the first count occurrences of elements equal to value from the list stored at key. + /// The count argument influences the operation in the following ways: + /// + /// count > 0: Remove elements equal to value moving from head to tail. + /// count < 0: Remove elements equal to value moving from tail to head. + /// count = 0: Remove all elements equal to value. + /// + /// + /// The key of the list. + /// The value to remove from the list. + /// The count behavior (see method summary). + /// The flags to use for this operation. + /// The number of removed elements. + /// + long ListRemove(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None); + + /// + /// Removes and returns the last element of the list stored at key. + /// + /// The key of the list. + /// The flags to use for this operation. + /// The element being popped, or when key does not exist.. + /// + RedisValue ListRightPop(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Removes and returns count elements from the end the list stored at key. + /// If the list contains less than count elements, removes and returns the number of elements in the list. + /// + /// The key of the list. + /// The number of elements to pop. + /// The flags to use for this operation. + /// Array of values that were popped, or if the key doesn't exist. + /// + RedisValue[] ListRightPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + /// Removes and returns at most elements from the first non-empty list in . + /// Starts on the right side of the list. + /// + /// The keys to look through for elements to pop. + /// The maximum number of elements to pop from the list. + /// The flags to use for this operation. + /// A span of contiguous elements from the list, or if no non-empty lists are found. + /// + ListPopResult ListRightPop(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None); + + /// + /// Atomically returns and removes the last element (tail) of the list stored at source, and pushes the element at the first element (head) of the list stored at destination. + /// + /// The key of the source list. + /// The key of the destination list. + /// The flags to use for this operation. + /// The element being popped and pushed. + /// + RedisValue ListRightPopLeftPush(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None); + + /// + /// Insert the specified value at the tail of the list stored at key. + /// If key does not exist, it is created as empty list before performing the push operation. + /// + /// The key of the list. + /// The value to add to the tail of the list. + /// Which conditions to add to the list under. + /// The flags to use for this operation. + /// The length of the list after the push operation. + /// + /// See + /// , + /// . + /// + long ListRightPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Insert the specified value at the tail of the list stored at key. + /// If key does not exist, it is created as empty list before performing the push operation. + /// + /// The key of the list. + /// The values to add to the tail of the list. + /// Which conditions to add to the list under. + /// The flags to use for this operation. + /// The length of the list after the push operation. + /// + /// See + /// , + /// . + /// + long ListRightPush(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Insert all the specified values at the tail of the list stored at key. + /// If key does not exist, it is created as empty list before performing the push operation. + /// Elements are inserted one after the other to the tail of the list, from the leftmost element to the rightmost element. + /// So for instance the command RPUSH mylist a b c will result into a list containing a as first element, b as second element and c as third element. + /// + /// The key of the list. + /// The values to add to the tail of the list. + /// The flags to use for this operation. + /// The length of the list after the push operation. + /// + long ListRightPush(RedisKey key, RedisValue[] values, CommandFlags flags); + + /// + /// Sets the list element at index to value. + /// For more information on the index argument, see . + /// An error is returned for out of range indexes. + /// + /// The key of the list. + /// The index to set the value at. + /// The values to add to the list. + /// The flags to use for this operation. + /// + void ListSetByIndex(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + /// Trim an existing list so that it will contain only the specified range of elements specified. + /// Both start and stop are zero-based indexes, where 0 is the first element of the list (the head), 1 the next element and so on. + /// For example: LTRIM foobar 0 2 will modify the list stored at foobar so that only the first three elements of the list will remain. + /// start and end can also be negative numbers indicating offsets from the end of the list, where -1 is the last element of the list, -2 the penultimate element and so on. + /// + /// The key of the list. + /// The start index of the list to trim to. + /// The end index of the list to trim to. + /// The flags to use for this operation. + /// + void ListTrim(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None); + + /// + /// Extends a lock, if the token value is correct. + /// + /// The key of the lock. + /// The value to set at the key. + /// The expiration of the lock key. + /// The flags to use for this operation. + /// if the lock was successfully extended. + bool LockExtend(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None); + + /// + /// Queries the token held against a lock. + /// + /// The key of the lock. + /// The flags to use for this operation. + /// The current value of the lock, if any. + RedisValue LockQuery(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Releases a lock, if the token value is correct. + /// + /// The key of the lock. + /// The value at the key that must match. + /// The flags to use for this operation. + /// if the lock was successfully released, otherwise. + bool LockRelease(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + /// Takes a lock (specifying a token value) if it is not already taken. + /// + /// The key of the lock. + /// The value to set at the key. + /// The expiration of the lock key. + /// The flags to use for this operation. + /// if the lock was successfully taken, otherwise. + bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None); + + /// + /// Posts a message to the given channel. + /// + /// The channel to publish to. + /// The message to send. + /// The flags to use for this operation. + /// + /// The number of clients that received the message *on the destination server*, + /// note that this doesn't mean much in a cluster as clients can get the message through other nodes. + /// + /// + long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None); + + /// + /// Execute an arbitrary command against the server; this is primarily intended for executing modules, + /// but may also be used to provide access to new features that lack a direct API. + /// + /// The command to run. + /// The arguments to pass for the command. + /// A dynamic representation of the command's result. + /// This API should be considered an advanced feature; inappropriate use can be harmful. + RedisResult Execute(string command, params object[] args); + + /// + /// Execute an arbitrary command against the server; this is primarily intended for executing modules, + /// but may also be used to provide access to new features that lack a direct API. + /// + /// The command to run. + /// The arguments to pass for the command. + /// The flags to use for this operation. + /// A dynamic representation of the command's result. + /// This API should be considered an advanced feature; inappropriate use can be harmful. + RedisResult Execute(string command, ICollection args, CommandFlags flags = CommandFlags.None); + + /// + /// Execute a Lua script against the server. + /// + /// The script to execute. + /// The keys to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + /// See + /// , + /// . + /// + RedisResult ScriptEvaluate(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + /// Execute a Lua script against the server using just the SHA1 hash. + /// + /// The hash of the script to execute. + /// The keys to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + /// Be aware that this method is not resilient to Redis server restarts. Use instead. + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + RedisResult ScriptEvaluate(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + /// Execute a lua script against the server, using previously prepared script. + /// Named parameters, if any, are provided by the `parameters` object. + /// + /// The script to execute. + /// The parameters to pass to the script. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + RedisResult ScriptEvaluate(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); + + /// + /// Execute a lua script against the server, using previously prepared and loaded script. + /// This method sends only the SHA1 hash of the lua script to Redis. + /// Named parameters, if any, are provided by the `parameters` object. + /// + /// The already-loaded script to execute. + /// The parameters to pass to the script. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + RedisResult ScriptEvaluate(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); + + /// + /// Read-only variant of the EVAL command that cannot execute commands that modify data, Execute a Lua script against the server. + /// + /// The script to execute. + /// The keys to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + /// See + /// , + /// . + /// + RedisResult ScriptEvaluateReadOnly(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + /// Read-only variant of the EVALSHA command that cannot execute commands that modify data, Execute a Lua script against the server using just the SHA1 hash. + /// + /// The hash of the script to execute. + /// The keys to execute against. + /// The values to execute against. + /// The flags to use for this operation. + /// A dynamic representation of the script's result. + /// + RedisResult ScriptEvaluateReadOnly(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + /// Add the specified member to the set stored at key. + /// Specified members that are already a member of this set are ignored. + /// If key does not exist, a new set is created before adding the specified members. + /// + /// The key of the set. + /// The value to add to the set. + /// The flags to use for this operation. + /// if the specified member was not already present in the set, else . + /// + bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + /// Add the specified members to the set stored at key. + /// Specified members that are already a member of this set are ignored. + /// If key does not exist, a new set is created before adding the specified members. + /// + /// The key of the set. + /// The values to add to the set. + /// The flags to use for this operation. + /// The number of elements that were added to the set, not including all the elements already present into the set. + /// + long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the members of the set resulting from the specified operation against the given sets. + /// + /// The operation to perform. + /// The key of the first set. + /// The key of the second set. + /// The flags to use for this operation. + /// List with members of the resulting set. + /// + /// See + /// , + /// , + /// . + /// + RedisValue[] SetCombine(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the members of the set resulting from the specified operation against the given sets. + /// + /// The operation to perform. + /// The keys of the sets to operate on. + /// The flags to use for this operation. + /// List with members of the resulting set. + /// + /// See + /// , + /// , + /// . + /// + RedisValue[] SetCombine(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + /// This command is equal to SetCombine, but instead of returning the resulting set, it is stored in destination. + /// If destination already exists, it is overwritten. + /// + /// The operation to perform. + /// The key of the destination set. + /// The key of the first set. + /// The key of the second set. + /// The flags to use for this operation. + /// The number of elements in the resulting set. + /// + /// See + /// , + /// , + /// . + /// + long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); + + /// + /// This command is equal to SetCombine, but instead of returning the resulting set, it is stored in destination. + /// If destination already exists, it is overwritten. + /// + /// The operation to perform. + /// The key of the destination set. + /// The keys of the sets to operate on. + /// The flags to use for this operation. + /// The number of elements in the resulting set. + /// + /// See + /// , + /// , + /// . + /// + long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + /// Returns whether is a member of the set stored at . + /// + /// The key of the set. + /// The value to check for. + /// The flags to use for this operation. + /// + /// if the element is a member of the set. + /// if the element is not a member of the set, or if key does not exist. + /// + /// + bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + /// Returns whether each of is a member of the set stored at . + /// + /// The key of the set. + /// The members to check for. + /// The flags to use for this operation. + /// + /// if the element is a member of the set. + /// if the element is not a member of the set, or if key does not exist. + /// + /// + bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); + + /// + /// + /// Returns the set cardinality (number of elements) of the intersection between the sets stored at the given . + /// + /// + /// If the intersection cardinality reaches partway through the computation, + /// the algorithm will exit and yield as the cardinality. + /// + /// + /// The keys of the sets. + /// The number of elements to check (defaults to 0 and means unlimited). + /// The flags to use for this operation. + /// The cardinality (number of elements) of the set, or 0 if key does not exist. + /// + long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the set cardinality (number of elements) of the set stored at key. + /// + /// The key of the set. + /// The flags to use for this operation. + /// The cardinality (number of elements) of the set, or 0 if key does not exist. + /// + long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Returns all the members of the set value stored at key. + /// + /// The key of the set. + /// The flags to use for this operation. + /// All elements of the set. + /// + RedisValue[] SetMembers(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Move member from the set at source to the set at destination. + /// This operation is atomic. In every given moment the element will appear to be a member of source or destination for other clients. + /// When the specified element already exists in the destination set, it is only removed from the source set. + /// + /// The key of the source set. + /// The key of the destination set. + /// The value to move. + /// The flags to use for this operation. + /// + /// if the element is moved. + /// if the element is not a member of source and no operation was performed. + /// + /// + bool SetMove(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + /// Removes and returns a random element from the set value stored at key. + /// + /// The key of the set. + /// The flags to use for this operation. + /// The removed element, or when key does not exist. + /// + RedisValue SetPop(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Removes and returns the specified number of random elements from the set value stored at key. + /// + /// The key of the set. + /// The number of elements to return. + /// The flags to use for this operation. + /// An array of elements, or an empty array when key does not exist. + /// + RedisValue[] SetPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + /// Return a random element from the set value stored at . + /// + /// The key of the set. + /// The flags to use for this operation. + /// The randomly selected element, or when does not exist. + /// + RedisValue SetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Return an array of count distinct elements if count is positive. + /// If called with a negative count the behavior changes and the command is allowed to return the same element multiple times. + /// In this case the number of returned elements is the absolute value of the specified count. + /// + /// The key of the set. + /// The count of members to get. + /// The flags to use for this operation. + /// An array of elements, or an empty array when does not exist. + /// + RedisValue[] SetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + /// Remove the specified member from the set stored at key. + /// Specified members that are not a member of this set are ignored. + /// + /// The key of the set. + /// The value to remove. + /// The flags to use for this operation. + /// if the specified member was already present in the set, otherwise. + /// + bool SetRemove(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + /// Remove the specified members from the set stored at key. + /// Specified members that are not a member of this set are ignored. + /// + /// The key of the set. + /// The values to remove. + /// The flags to use for this operation. + /// The number of members that were removed from the set, not including non existing members. + /// + long SetRemove(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); + + /// + /// The SSCAN command is used to incrementally iterate over a set. + /// + /// The key of the set. + /// The pattern to match. + /// The page size to iterate by. + /// The flags to use for this operation. + /// Yields all matching elements of the set. + /// + IEnumerable SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags); + + /// + /// The SSCAN command is used to incrementally iterate over set. + /// Note: to resume an iteration via cursor, cast the original enumerable or enumerator to . + /// + /// The key of the set. + /// The pattern to match. + /// The page size to iterate by. + /// The cursor position to start at. + /// The page offset to start at. + /// The flags to use for this operation. + /// Yields all matching elements of the set. + /// + IEnumerable SetScan(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None); + + /// + /// Sorts a list, set or sorted set (numerically or alphabetically, ascending by default). + /// By default, the elements themselves are compared, but the values can also be used to perform external key-lookups using the by parameter. + /// By default, the elements themselves are returned, but external key-lookups (one or many) can be performed instead by specifying + /// the get parameter (note that # specifies the element itself, when used in get). + /// Referring to the redis SORT documentation for examples is recommended. + /// When used in hashes, by and get can be used to specify fields using -> notation (again, refer to redis documentation). + /// Uses SORT_RO when possible. + /// + /// The key of the list, set, or sorted set. + /// How many entries to skip on the return. + /// How many entries to take on the return. + /// The ascending or descending order (defaults to ascending). + /// The sorting method (defaults to numeric). + /// The key pattern to sort by, if any. e.g. ExternalKey_* would sort by ExternalKey_{listvalue} as a lookup. + /// The key pattern to sort by, if any e.g. ExternalKey_* would return the value of ExternalKey_{listvalue} for each entry. + /// The flags to use for this operation. + /// The sorted elements, or the external values if get is specified. + /// + /// See + /// , + /// . + /// + RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None); + + /// + /// Sorts a list, set or sorted set (numerically or alphabetically, ascending by default). + /// By default, the elements themselves are compared, but the values can also be used to perform external key-lookups using the by parameter. + /// By default, the elements themselves are returned, but external key-lookups (one or many) can be performed instead by specifying + /// the get parameter (note that # specifies the element itself, when used in get). + /// Referring to the redis SORT documentation for examples is recommended. + /// When used in hashes, by and get can be used to specify fields using -> notation (again, refer to redis documentation). + /// + /// The destination key to store results in. + /// The key of the list, set, or sorted set. + /// How many entries to skip on the return. + /// How many entries to take on the return. + /// The ascending or descending order (defaults to ascending). + /// The sorting method (defaults to numeric). + /// The key pattern to sort by, if any. e.g. ExternalKey_* would sort by ExternalKey_{listvalue} as a lookup. + /// The key pattern to sort by, if any e.g. ExternalKey_* would return the value of ExternalKey_{listvalue} for each entry. + /// The flags to use for this operation. + /// The number of elements stored in the new list. + /// + long SortAndStore(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + bool SortedSetAdd(RedisKey key, RedisValue member, double score, CommandFlags flags); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when, CommandFlags flags = CommandFlags.None); + + /// + /// Adds the specified member with the specified score to the sorted set stored at key. + /// If the specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. + /// + /// The key of the sorted set. + /// The member to add to the sorted set. + /// The score for the member to add to the sorted set. + /// What conditions to add the element under (defaults to always). + /// The flags to use for this operation. + /// if the value was added. if it already existed (the score is still updated). + /// + bool SortedSetAdd(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + long SortedSetAdd(RedisKey key, SortedSetEntry[] values, CommandFlags flags); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + long SortedSetAdd(RedisKey key, SortedSetEntry[] values, When when, CommandFlags flags = CommandFlags.None); + + /// + /// Adds all the specified members with the specified scores to the sorted set stored at key. + /// If a specified member is already a member of the sorted set, the score is updated and the element reinserted at the right position to ensure the correct ordering. + /// + /// The key of the sorted set. + /// The members and values to add to the sorted set. + /// What conditions to add the element under (defaults to always). + /// The flags to use for this operation. + /// The number of elements added to the sorted sets, not including elements already existing for which the score was updated. + /// + long SortedSetAdd(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Computes a set operation for multiple sorted sets (optionally using per-set ), + /// optionally performing a specific aggregation (defaults to ). + /// cannot be used with weights or aggregation. + /// + /// The operation to perform. + /// The keys of the sorted sets. + /// The optional weights per set that correspond to . + /// The aggregation method (defaults to ). + /// The flags to use for this operation. + /// The resulting sorted set. + /// + /// See + /// , + /// , + /// . + /// + RedisValue[] SortedSetCombine(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None); + + /// + /// Computes a set operation for multiple sorted sets (optionally using per-set ), + /// optionally performing a specific aggregation (defaults to ). + /// cannot be used with weights or aggregation. + /// + /// The operation to perform. + /// The keys of the sorted sets. + /// The optional weights per set that correspond to . + /// The aggregation method (defaults to ). + /// The flags to use for this operation. + /// The resulting sorted set with scores. + /// + /// See + /// , + /// , + /// . + /// + SortedSetEntry[] SortedSetCombineWithScores(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None); + + /// + /// Computes a set operation over two sorted sets, and stores the result in destination, optionally performing + /// a specific aggregation (defaults to sum). + /// cannot be used with aggregation. + /// + /// The operation to perform. + /// The key to store the results in. + /// The key of the first sorted set. + /// The key of the second sorted set. + /// The aggregation method (defaults to sum). + /// The flags to use for this operation. + /// The number of elements in the resulting sorted set at destination. + /// + /// See + /// , + /// , + /// . + /// + long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None); + + /// + /// Computes a set operation over multiple sorted sets (optionally using per-set weights), and stores the result in destination, optionally performing + /// a specific aggregation (defaults to sum). + /// cannot be used with aggregation. + /// + /// The operation to perform. + /// The key to store the results in. + /// The keys of the sorted sets. + /// The optional weights per set that correspond to . + /// The aggregation method (defaults to sum). + /// The flags to use for this operation. + /// The number of elements in the resulting sorted set at destination. + /// + /// See + /// , + /// , + /// . + /// + long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None); + + /// + /// Decrements the score of member in the sorted set stored at key by decrement. + /// If member does not exist in the sorted set, it is added with -decrement as its score (as if its previous score was 0.0). + /// + /// The key of the sorted set. + /// The member to decrement. + /// The amount to decrement by. + /// The flags to use for this operation. + /// The new score of member. + /// + double SortedSetDecrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None); + + /// + /// Increments the score of member in the sorted set stored at key by increment. If member does not exist in the sorted set, it is added with increment as its score (as if its previous score was 0.0). + /// + /// The key of the sorted set. + /// The member to increment. + /// The amount to increment by. + /// The flags to use for this operation. + /// The new score of member. + /// + double SortedSetIncrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the cardinality of the intersection of the sorted sets at . + /// + /// The keys of the sorted sets. + /// If the intersection cardinality reaches partway through the computation, the algorithm will exit and yield as the cardinality (defaults to 0 meaning unlimited). + /// The flags to use for this operation. + /// The number of elements in the resulting intersection. + /// + long SortedSetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the sorted set cardinality (number of elements) of the sorted set stored at key. + /// + /// The key of the sorted set. + /// The min score to filter by (defaults to negative infinity). + /// The max score to filter by (defaults to positive infinity). + /// Whether to exclude and from the range check (defaults to both inclusive). + /// The flags to use for this operation. + /// The cardinality (number of elements) of the sorted set, or 0 if key does not exist. + /// + long SortedSetLength(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); + + /// + /// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering. + /// This command returns the number of elements in the sorted set at key with a value between min and max. + /// + /// The key of the sorted set. + /// The min value to filter by. + /// The max value to filter by. + /// Whether to exclude and from the range check (defaults to both inclusive). + /// The flags to use for this operation. + /// The number of elements in the specified score range. + /// + long SortedSetLengthByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); + + /// + /// Returns a random element from the sorted set value stored at . + /// + /// The key of the sorted set. + /// The flags to use for this operation. + /// The randomly selected element, or when does not exist. + /// + RedisValue SortedSetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Returns an array of random elements from the sorted set value stored at . + /// + /// The key of the sorted set. + /// + /// + /// If the provided count argument is positive, returns an array of distinct elements. + /// The array's length is either or the sorted set's cardinality (ZCARD), whichever is lower. + /// + /// + /// If called with a negative count, the behavior changes and the command is allowed to return the same element multiple times. + /// In this case, the number of returned elements is the absolute value of the specified count. + /// + /// + /// The flags to use for this operation. + /// The randomly selected elements, or an empty array when does not exist. + /// + RedisValue[] SortedSetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + /// Returns an array of random elements from the sorted set value stored at . + /// + /// The key of the sorted set. + /// + /// + /// If the provided count argument is positive, returns an array of distinct elements. + /// The array's length is either or the sorted set's cardinality (ZCARD), whichever is lower. + /// + /// + /// If called with a negative count, the behavior changes and the command is allowed to return the same element multiple times. + /// In this case, the number of returned elements is the absolute value of the specified count. + /// + /// + /// The flags to use for this operation. + /// The randomly selected elements with scores, or an empty array when does not exist. + /// + SortedSetEntry[] SortedSetRandomMembersWithScores(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the specified range of elements in the sorted set stored at key. + /// By default the elements are considered to be ordered from the lowest to the highest score. + /// Lexicographical order is used for elements with equal score. + /// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. + /// They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on. + /// + /// The key of the sorted set. + /// The start index to get. + /// The stop index to get. + /// The order to sort by (defaults to ascending). + /// The flags to use for this operation. + /// List of elements in the specified range. + /// + /// See + /// , + /// . + /// + RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + /// Takes the specified range of elements in the sorted set of the + /// and stores them in a new sorted set at the . + /// + /// The sorted set to take the range from. + /// Where the resulting set will be stored. + /// The starting point in the sorted set. If is , this should be a string. + /// The stopping point in the range of the sorted set. If is , this should be a string. + /// The ordering criteria to use for the range. Choices are , , and (defaults to ). + /// Whether to exclude and from the range check (defaults to both inclusive). + /// + /// The direction to consider the and in. + /// If , the must be smaller than the . + /// If , must be smaller than . + /// + /// The number of elements into the sorted set to skip. Note: this iterates after sorting so incurs O(n) cost for large values. + /// The maximum number of elements to pull into the new () set. + /// The flags to use for this operation. + /// The cardinality of (number of elements in) the newly created sorted set. + /// + long SortedSetRangeAndStore( + RedisKey sourceKey, + RedisKey destinationKey, + RedisValue start, + RedisValue stop, + SortedSetOrder sortedSetOrder = SortedSetOrder.ByRank, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long? take = null, + CommandFlags flags = CommandFlags.None); + + /// + /// Returns the specified range of elements in the sorted set stored at key. + /// By default the elements are considered to be ordered from the lowest to the highest score. + /// Lexicographical order is used for elements with equal score. + /// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. + /// They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on. + /// + /// The key of the sorted set. + /// The start index to get. + /// The stop index to get. + /// The order to sort by (defaults to ascending). + /// The flags to use for this operation. + /// List of elements in the specified range. + /// + /// See + /// , + /// . + /// + SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the specified range of elements in the sorted set stored at key. + /// By default the elements are considered to be ordered from the lowest to the highest score. + /// Lexicographical order is used for elements with equal score. + /// Start and stop are used to specify the min and max range for score values. + /// Similar to other range methods the values are inclusive. + /// + /// The key of the sorted set. + /// The minimum score to filter by. + /// The maximum score to filter by. + /// Which of and to exclude (defaults to both inclusive). + /// The order to sort by (defaults to ascending). + /// How many items to skip. + /// How many items to take. + /// The flags to use for this operation. + /// List of elements in the specified score range. + /// + /// See + /// , + /// . + /// + RedisValue[] SortedSetRangeByScore( + RedisKey key, + double start = double.NegativeInfinity, + double stop = double.PositiveInfinity, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long take = -1, + CommandFlags flags = CommandFlags.None); + + /// + /// Returns the specified range of elements in the sorted set stored at key. + /// By default the elements are considered to be ordered from the lowest to the highest score. + /// Lexicographical order is used for elements with equal score. + /// Start and stop are used to specify the min and max range for score values. + /// Similar to other range methods the values are inclusive. + /// + /// The key of the sorted set. + /// The minimum score to filter by. + /// The maximum score to filter by. + /// Which of and to exclude (defaults to both inclusive). + /// The order to sort by (defaults to ascending). + /// How many items to skip. + /// How many items to take. + /// The flags to use for this operation. + /// List of elements in the specified score range. + /// + /// See + /// , + /// . + /// + SortedSetEntry[] SortedSetRangeByScoreWithScores( + RedisKey key, + double start = double.NegativeInfinity, + double stop = double.PositiveInfinity, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long take = -1, + CommandFlags flags = CommandFlags.None); + + /// + /// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering. + /// This command returns all the elements in the sorted set at key with a value between min and max. + /// + /// The key of the sorted set. + /// The min value to filter by. + /// The max value to filter by. + /// Which of and to exclude (defaults to both inclusive). + /// How many items to skip. + /// How many items to take. + /// The flags to use for this operation. + /// List of elements in the specified score range. + /// + RedisValue[] SortedSetRangeByValue( + RedisKey key, + RedisValue min, + RedisValue max, + Exclude exclude, + long skip, + long take = -1, + CommandFlags flags = CommandFlags.None); // defaults removed to avoid ambiguity with overload with order + + /// + /// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering. + /// This command returns all the elements in the sorted set at key with a value between min and max. + /// + /// The key of the sorted set. + /// The min value to filter by. + /// The max value to filter by. + /// Which of and to exclude (defaults to both inclusive). + /// Whether to order the data ascending or descending. + /// How many items to skip. + /// How many items to take. + /// The flags to use for this operation. + /// List of elements in the specified score range. + /// + /// See + /// , + /// . + /// + RedisValue[] SortedSetRangeByValue( + RedisKey key, + RedisValue min = default, + RedisValue max = default, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long take = -1, + CommandFlags flags = CommandFlags.None); + + /// + /// Returns the rank of member in the sorted set stored at key, by default with the scores ordered from low to high. + /// The rank (or index) is 0-based, which means that the member with the lowest score has rank 0. + /// + /// The key of the sorted set. + /// The member to get the rank of. + /// The order to sort by (defaults to ascending). + /// The flags to use for this operation. + /// If member exists in the sorted set, the rank of member. If member does not exist in the sorted set or key does not exist, . + /// + /// See + /// , + /// . + /// + long? SortedSetRank(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + /// Removes the specified member from the sorted set stored at key. Non existing members are ignored. + /// + /// The key of the sorted set. + /// The member to remove. + /// The flags to use for this operation. + /// if the member existed in the sorted set and was removed. otherwise. + /// + bool SortedSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + /// Removes the specified members from the sorted set stored at key. Non existing members are ignored. + /// + /// The key of the sorted set. + /// The members to remove. + /// The flags to use for this operation. + /// The number of members removed from the sorted set, not including non existing members. + /// + long SortedSetRemove(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); + + /// + /// Removes all elements in the sorted set stored at key with rank between start and stop. + /// Both start and stop are 0 -based indexes with 0 being the element with the lowest score. + /// These indexes can be negative numbers, where they indicate offsets starting at the element with the highest score. + /// For example: -1 is the element with the highest score, -2 the element with the second highest score and so forth. + /// + /// The key of the sorted set. + /// The minimum rank to remove. + /// The maximum rank to remove. + /// The flags to use for this operation. + /// The number of elements removed. + /// + long SortedSetRemoveRangeByRank(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None); + + /// + /// Removes all elements in the sorted set stored at key with a score between min and max (inclusive by default). + /// + /// The key of the sorted set. + /// The minimum score to remove. + /// The maximum score to remove. + /// Which of and to exclude (defaults to both inclusive). + /// The flags to use for this operation. + /// The number of elements removed. + /// + long SortedSetRemoveRangeByScore(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); + + /// + /// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering. + /// This command removes all elements in the sorted set stored at key between the lexicographical range specified by min and max. + /// + /// The key of the sorted set. + /// The minimum value to remove. + /// The maximum value to remove. + /// Which of and to exclude (defaults to both inclusive). + /// The flags to use for this operation. + /// The number of elements removed. + /// + long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); + + /// + /// The ZSCAN command is used to incrementally iterate over a sorted set. + /// + /// The key of the sorted set. + /// The pattern to match. + /// The page size to iterate by. + /// The flags to use for this operation. + /// Yields all matching elements of the sorted set. + /// + IEnumerable SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags); + + /// + /// The ZSCAN command is used to incrementally iterate over a sorted set + /// Note: to resume an iteration via cursor, cast the original enumerable or enumerator to IScanningCursor. + /// + /// The key of the sorted set. + /// The pattern to match. + /// The page size to iterate by. + /// The cursor position to start at. + /// The page offset to start at. + /// The flags to use for this operation. + /// Yields all matching elements of the sorted set. + /// + IEnumerable SortedSetScan( + RedisKey key, + RedisValue pattern = default, + int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, + long cursor = RedisBase.CursorUtils.Origin, + int pageOffset = 0, + CommandFlags flags = CommandFlags.None); + + /// + /// Returns the score of member in the sorted set at key. + /// If member does not exist in the sorted set, or key does not exist, is returned. + /// + /// The key of the sorted set. + /// The member to get a score for. + /// The flags to use for this operation. + /// The score of the member. + /// + double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the scores of members in the sorted set at . + /// If a member does not exist in the sorted set, or key does not exist, is returned. + /// + /// The key of the sorted set. + /// The members to get a score for. + /// The flags to use for this operation. + /// + /// The scores of the members in the same order as the array. + /// If a member does not exist in the set, is returned. + /// + /// + double?[] SortedSetScores(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); + + /// + /// Removes and returns the first element from the sorted set stored at key, by default with the scores ordered from low to high. + /// + /// The key of the sorted set. + /// The order to sort by (defaults to ascending). + /// The flags to use for this operation. + /// The removed element, or when key does not exist. + /// + /// See + /// , + /// . + /// + SortedSetEntry? SortedSetPop(RedisKey key, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + /// Removes and returns the specified number of first elements from the sorted set stored at key, by default with the scores ordered from low to high. + /// + /// The key of the sorted set. + /// The number of elements to return. + /// The order to sort by (defaults to ascending). + /// The flags to use for this operation. + /// An array of elements, or an empty array when key does not exist. + /// + /// See + /// , + /// . + /// + SortedSetEntry[] SortedSetPop(RedisKey key, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + /// Removes and returns up to entries from the first non-empty sorted set in . + /// Returns if none of the sets exist or contain any elements. + /// + /// The keys to check. + /// The maximum number of records to pop out of the sorted set. + /// The order to sort by when popping items out of the set. + /// The flags to use for the operation. + /// A contiguous collection of sorted set entries with the key they were popped from, or if no non-empty sorted sets are found. + /// + SortedSetPopResult SortedSetPop(RedisKey[] keys, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + /// Same as but return the number of the elements changed. + /// + /// The key of the sorted set. + /// The member to add/update to the sorted set. + /// The score for the member to add/update to the sorted set. + /// What conditions to add the element under (defaults to always). + /// The flags to use for this operation. + /// The number of elements changed. + /// + bool SortedSetUpdate(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Same as but return the number of the elements changed. + /// + /// The key of the sorted set. + /// The members and values to add/update to the sorted set. + /// What conditions to add the element under (defaults to always). + /// The flags to use for this operation. + /// The number of elements changed. + /// + long SortedSetUpdate(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Allow the consumer to mark a pending message as correctly processed. Returns the number of messages acknowledged. + /// + /// The key of the stream. + /// The name of the consumer group that received the message. + /// The ID of the message to acknowledge. + /// The flags to use for this operation. + /// The number of messages acknowledged. + /// + long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None); + + /// + /// Allow the consumer to mark a pending message as correctly processed. Returns the number of messages acknowledged. + /// + /// The key of the stream. + /// The name of the consumer group that received the message. + /// The IDs of the messages to acknowledge. + /// The flags to use for this operation. + /// The number of messages acknowledged. + /// + long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// Allow the consumer to mark a pending message as correctly processed. Returns the number of messages acknowledged. + /// + /// The key of the stream. + /// The name of the consumer group that received the message. + /// The delete mode to use when acknowledging the message. + /// The ID of the message to acknowledge. + /// The flags to use for this operation. + /// The outcome of the delete operation. + /// +#pragma warning disable RS0026 // similar overloads + StreamTrimResult StreamAcknowledgeAndDelete(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue messageId, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0026 + + /// + /// Allow the consumer to mark a pending message as correctly processed. Returns the number of messages acknowledged. + /// + /// The key of the stream. + /// The name of the consumer group that received the message. + /// /// The delete mode to use when acknowledging the message. + /// The IDs of the messages to acknowledge. + /// The flags to use for this operation. + /// The outcome of each delete operation. + /// +#pragma warning disable RS0026 // similar overloads + StreamTrimResult[] StreamAcknowledgeAndDelete(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0026 + + /// + /// Adds an entry using the specified values to the given stream key. + /// If key does not exist, a new key holding a stream is created. + /// The command returns the ID of the newly created stream entry. + /// + /// The key of the stream. + /// The field name for the stream entry. + /// The value to set in the stream entry. + /// The ID to assign to the stream entry, defaults to an auto-generated ID ("*"). + /// The maximum length of the stream. + /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The flags to use for this operation. + /// The ID of the newly created message. + /// + RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags); + + /// + /// Adds an entry using the specified values to the given stream key. + /// If key does not exist, a new key holding a stream is created. + /// The command returns the ID of the newly created stream entry. + /// + /// The key of the stream. + /// The fields and their associated values to set in the stream entry. + /// The ID to assign to the stream entry, defaults to an auto-generated ID ("*"). + /// The maximum length of the stream. + /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The flags to use for this operation. + /// The ID of the newly created message. + /// + RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags); + + /// + /// Adds an entry using the specified values to the given stream key. + /// If key does not exist, a new key holding a stream is created. + /// The command returns the ID of the newly created stream entry. + /// + /// The key of the stream. + /// The field name for the stream entry. + /// The value to set in the stream entry. + /// The ID to assign to the stream entry, defaults to an auto-generated ID ("*"). + /// The maximum length of the stream. + /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// Specifies the maximal count of entries that will be evicted. + /// Determines how stream trimming should be performed. + /// The flags to use for this operation. + /// The ID of the newly created message. + /// +#pragma warning disable RS0026 // different shape + RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0026 + + /// + /// Adds an entry using the specified values to the given stream key. + /// If key does not exist, a new key holding a stream is created. + /// The command returns the ID of the newly created stream entry. + /// + /// The key of the stream. + /// The fields and their associated values to set in the stream entry. + /// The ID to assign to the stream entry, defaults to an auto-generated ID ("*"). + /// The maximum length of the stream. + /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// Specifies the maximal count of entries that will be evicted. + /// Determines how stream trimming should be performed. + /// The flags to use for this operation. + /// The ID of the newly created message. + /// +#pragma warning disable RS0026 // different shape + RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0026 + + /// + /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. + /// Messages that have been idle for more than will be claimed. + /// + /// The key of the stream. + /// The consumer group. + /// The consumer claiming the messages(s). + /// The minimum idle time threshold for pending messages to be claimed. + /// The starting ID to scan for pending messages that have an idle time greater than . + /// The upper limit of the number of entries that the command attempts to claim. If , Redis will default the value to 100. + /// The flags to use for this operation. + /// An instance of . + /// + StreamAutoClaimResult StreamAutoClaim(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None); + + /// + /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. + /// Messages that have been idle for more than will be claimed. + /// The result will contain the claimed message IDs instead of a instance. + /// + /// The key of the stream. + /// The consumer group. + /// The consumer claiming the messages(s). + /// The minimum idle time threshold for pending messages to be claimed. + /// The starting ID to scan for pending messages that have an idle time greater than . + /// The upper limit of the number of entries that the command attempts to claim. If , Redis will default the value to 100. + /// The flags to use for this operation. + /// An instance of . + /// + StreamAutoClaimIdsOnlyResult StreamAutoClaimIdsOnly(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None); + + /// + /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. + /// This method returns the complete message for the claimed message(s). + /// + /// The key of the stream. + /// The consumer group. + /// The consumer claiming the given message(s). + /// The minimum message idle time to allow the reassignment of the message(s). + /// The IDs of the messages to claim for the given consumer. + /// The flags to use for this operation. + /// The messages successfully claimed by the given consumer. + /// + StreamEntry[] StreamClaim(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// Change ownership of messages consumed, but not yet acknowledged, by a different consumer. + /// This method returns the IDs for the claimed message(s). + /// + /// The key of the stream. + /// The consumer group. + /// The consumer claiming the given message(s). + /// The minimum message idle time to allow the reassignment of the message(s). + /// The IDs of the messages to claim for the given consumer. + /// The flags to use for this operation. + /// The message IDs for the messages successfully claimed by the given consumer. + /// + RedisValue[] StreamClaimIdsOnly(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + /// Set the position from which to read a stream for a consumer group. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The position from which to read for the consumer group. + /// The flags to use for this operation. + /// if successful, otherwise. + /// + bool StreamConsumerGroupSetPosition(RedisKey key, RedisValue groupName, RedisValue position, CommandFlags flags = CommandFlags.None); + + /// + /// Retrieve information about the consumers for the given consumer group. + /// This is the equivalent of calling "XINFO GROUPS key group". + /// + /// The key of the stream. + /// The consumer group name. + /// The flags to use for this operation. + /// An instance of for each of the consumer group's consumers. + /// + StreamConsumerInfo[] StreamConsumerInfo(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + + /// + /// Create a consumer group for the given stream. + /// + /// The key of the stream. + /// The name of the group to create. + /// The position to begin reading the stream. Defaults to . + /// The flags to use for this operation. + /// if the group was created, otherwise. + /// + bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue? position, CommandFlags flags); + + /// + /// Create a consumer group for the given stream. + /// + /// The key of the stream. + /// The name of the group to create. + /// The position to begin reading the stream. Defaults to . + /// Create the stream if it does not already exist. + /// The flags to use for this operation. + /// if the group was created, otherwise. + /// + bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue? position = null, bool createStream = true, CommandFlags flags = CommandFlags.None); + + /// + /// Delete messages in the stream. This method does not delete the stream. + /// + /// The key of the stream. + /// The IDs of the messages to delete. + /// The flags to use for this operation. + /// Returns the number of messages successfully deleted from the stream. + /// +#pragma warning disable RS0026 // similar overloads + long StreamDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0026 + + /// + /// Delete messages in the stream. This method does not delete the stream. + /// + /// The key of the stream. + /// The IDs of the messages to delete. + /// Determines how stream trimming should be performed. + /// The flags to use for this operation. + /// Returns the number of messages successfully deleted from the stream. + /// +#pragma warning disable RS0026 // similar overloads + StreamTrimResult[] StreamDelete(RedisKey key, RedisValue[] messageIds, StreamTrimMode mode, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0026 + + /// + /// Delete a consumer from a consumer group. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The name of the consumer. + /// The flags to use for this operation. + /// The number of messages that were pending for the deleted consumer. + /// + long StreamDeleteConsumer(RedisKey key, RedisValue groupName, RedisValue consumerName, CommandFlags flags = CommandFlags.None); + + /// + /// Delete a consumer group. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The flags to use for this operation. + /// if deleted, otherwise. + /// + bool StreamDeleteConsumerGroup(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + + /// + /// Retrieve information about the groups created for the given stream. This is the equivalent of calling "XINFO GROUPS key". + /// + /// The key of the stream. + /// The flags to use for this operation. + /// An instance of for each of the stream's groups. + /// + StreamGroupInfo[] StreamGroupInfo(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Retrieve information about the given stream. This is the equivalent of calling "XINFO STREAM key". + /// + /// The key of the stream. + /// The flags to use for this operation. + /// A instance with information about the stream. + /// + StreamInfo StreamInfo(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Return the number of entries in a stream. + /// + /// The key of the stream. + /// The flags to use for this operation. + /// The number of entries inside the given stream. + /// + long StreamLength(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// View information about pending messages for a stream. + /// A pending message is a message read using StreamReadGroup (XREADGROUP) but not yet acknowledged. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The flags to use for this operation. + /// + /// An instance of . + /// contains the number of pending messages. + /// The highest and lowest ID of the pending messages, and the consumers with their pending message count. + /// + /// The equivalent of calling XPENDING key group. + /// + StreamPendingInfo StreamPending(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + + /// + /// View information about each pending message. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The maximum number of pending messages to return. + /// The consumer name for the pending messages. Pass RedisValue.Null to include pending messages for all consumers. + /// The minimum ID from which to read the stream of pending messages. Pass null to read from the beginning of the stream. + /// The maximum ID to read to within the stream of pending messages. Pass null to read to the end of the stream. + /// The flags to use for this operation. + /// An instance of for each pending message. + /// Equivalent of calling XPENDING key group start-id end-id count consumer-name. + /// + StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId, RedisValue? maxId, CommandFlags flags); + + /// + /// View information about each pending message. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The maximum number of pending messages to return. + /// The consumer name for the pending messages. Pass RedisValue.Null to include pending messages for all consumers. + /// The minimum ID from which to read the stream of pending messages. The method will default to reading from the beginning of the stream. + /// The maximum ID to read to within the stream of pending messages. The method will default to reading to the end of the stream. + /// The minimum idle time threshold for pending messages to be claimed. + /// The flags to use for this operation. + /// An instance of for each pending message. + /// Equivalent of calling XPENDING key group IDLE min-idle-time start-id end-id count consumer-name. + /// + StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, long? minIdleTimeInMs = null, CommandFlags flags = CommandFlags.None); + + /// + /// Read a stream using the given range of IDs. + /// + /// The key of the stream. + /// The minimum ID from which to read the stream. The method will default to reading from the beginning of the stream. + /// The maximum ID to read to within the stream. The method will default to reading to the end of the stream. + /// The maximum number of messages to return. + /// The order of the messages. will execute XRANGE and will execute XREVRANGE. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// + StreamEntry[] StreamRange(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + /// Read from a single stream. + /// + /// The key of the stream. + /// The position from which to read the stream. + /// The maximum number of messages to return. + /// The flags to use for this operation. + /// Returns an instance of for each message returned. + /// + /// Equivalent of calling XREAD COUNT num STREAMS key id. + /// + /// + StreamEntry[] StreamRead(RedisKey key, RedisValue position, int? count = null, CommandFlags flags = CommandFlags.None); + + /// + /// Read from multiple streams. + /// + /// Array of streams and the positions from which to begin reading for each stream. + /// The maximum number of messages to return from each stream. + /// The flags to use for this operation. + /// A value of for each stream. + /// + /// Equivalent of calling XREAD COUNT num STREAMS key1 key2 id1 id2. + /// + /// + RedisStream[] StreamRead(StreamPosition[] streamPositions, int? countPerStream = null, CommandFlags flags = CommandFlags.None); + + /// + /// Read messages from a stream into an associated consumer group. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The consumer name. + /// The position from which to read the stream. Defaults to when . + /// The maximum number of messages to return. + /// The flags to use for this operation. + /// Returns a value of for each message returned. + /// + StreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position, int? count, CommandFlags flags); + + /// + /// Read messages from a stream into an associated consumer group. + /// + /// The key of the stream. + /// The name of the consumer group. + /// The consumer name. + /// The position from which to read the stream. Defaults to when . + /// The maximum number of messages to return. + /// When true, the message will not be added to the pending message list. + /// The flags to use for this operation. + /// Returns a value of for each message returned. + /// + StreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position = null, int? count = null, bool noAck = false, CommandFlags flags = CommandFlags.None); + + /// + /// Read from multiple streams into the given consumer group. + /// The consumer group with the given will need to have been created for each stream prior to calling this method. + /// + /// Array of streams and the positions from which to begin reading for each stream. + /// The name of the consumer group. + /// The name of the consumer. + /// The maximum number of messages to return from each stream. + /// The flags to use for this operation. + /// A value of for each stream. + /// + /// Equivalent of calling XREADGROUP GROUP groupName consumerName COUNT countPerStream STREAMS stream1 stream2 id1 id2. + /// + /// + RedisStream[] StreamReadGroup(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream, CommandFlags flags); + + /// + /// Read from multiple streams into the given consumer group. + /// The consumer group with the given will need to have been created for each stream prior to calling this method. + /// + /// Array of streams and the positions from which to begin reading for each stream. + /// The name of the consumer group. + /// The name of the consumer. + /// The maximum number of messages to return from each stream. + /// When true, the message will not be added to the pending message list. + /// The flags to use for this operation. + /// A value of for each stream. + /// + /// Equivalent of calling XREADGROUP GROUP groupName consumerName COUNT countPerStream STREAMS stream1 stream2 id1 id2. + /// + /// + RedisStream[] StreamReadGroup(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream = null, bool noAck = false, CommandFlags flags = CommandFlags.None); + + /// + /// Trim the stream to a specified maximum length. + /// + /// The key of the stream. + /// The maximum length of the stream. + /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// The flags to use for this operation. + /// The number of messages removed from the stream. + /// + long StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength, CommandFlags flags); + + /// + /// Trim the stream to a specified maximum length. + /// + /// The key of the stream. + /// The maximum length of the stream. + /// If true, the "~" argument is used to allow the stream to exceed max length by a small number. This improves performance when removing messages. + /// Specifies the maximal count of entries that will be evicted. + /// Determines how stream trimming should be performed. + /// The flags to use for this operation. + /// The number of messages removed from the stream. + /// + long StreamTrim(RedisKey key, long maxLength, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + + /// + /// Trim the stream to a specified minimum timestamp. + /// + /// The key of the stream. + /// All entries with an id (timestamp) earlier minId will be removed. + /// If true, the "~" argument is used to allow the stream to exceed minId by a small number. This improves performance when removing messages. + /// The maximum number of entries to remove per call when useApproximateMaxLength = true. If 0, the limiting mechanism is disabled entirely. + /// Determines how stream trimming should be performed. + /// The flags to use for this operation. + /// The number of messages removed from the stream. + /// + long StreamTrimByMinId(RedisKey key, RedisValue minId, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + + /// + /// If key already exists and is a string, this command appends the value at the end of the string. + /// If key does not exist it is created and set as an empty string, so APPEND will be similar to SET in this special case. + /// + /// The key of the string. + /// The value to append to the string. + /// The flags to use for this operation. + /// The length of the string after the append operation. + /// + long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + long StringBitCount(RedisKey key, long start, long end, CommandFlags flags); + + /// + /// Count the number of set bits (population counting) in a string. + /// By default all the bytes contained in the string are examined. + /// It is possible to specify the counting operation only in an interval passing the additional arguments start and end. + /// Like for the GETRANGE command start and end can contain negative values in order to index bytes starting from the end of the string, where -1 is the last byte, -2 is the penultimate, and so forth. + /// + /// The key of the string. + /// The start byte to count at. + /// The end byte to count at. + /// In Redis 7+, we can choose if and specify a bit index or byte index (defaults to ). + /// The flags to use for this operation. + /// The number of bits set to 1. + /// + long StringBitCount(RedisKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None); + + /// + /// Perform a bitwise operation between multiple keys (containing string values) and store the result in the destination key. + /// The BITOP command supports four bitwise operations; note that NOT is a unary operator: the second key should be omitted in this case + /// and only the first key will be considered. + /// The result of the operation is always stored at . + /// + /// The operation to perform. + /// The destination key to store the result in. + /// The first key to get the bit value from. + /// The second key to get the bit value from. + /// The flags to use for this operation. + /// The size of the string stored in the destination key, that is equal to the size of the longest input string. + /// + long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default, CommandFlags flags = CommandFlags.None); + + /// + /// Perform a bitwise operation between multiple keys (containing string values) and store the result in the destination key. + /// The BITOP command supports four bitwise operations; note that NOT is a unary operator. + /// The result of the operation is always stored at . + /// + /// The operation to perform. + /// The destination key to store the result in. + /// The keys to get the bit values from. + /// The flags to use for this operation. + /// The size of the string stored in the destination key, that is equal to the size of the longest input string. + /// + long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + long StringBitPosition(RedisKey key, bool bit, long start, long end, CommandFlags flags); + + /// + /// Return the position of the first bit set to 1 or 0 in a string. + /// The position is returned thinking at the string as an array of bits from left to right where the first byte most significant bit is at position 0, the second byte most significant bit is at position 8 and so forth. + /// A and may be specified - these are in bytes, not bits. + /// and can contain negative values in order to index bytes starting from the end of the string, where -1 is the last byte, -2 is the penultimate, and so forth. + /// + /// The key of the string. + /// True to check for the first 1 bit, false to check for the first 0 bit. + /// The position to start looking (defaults to 0). + /// The position to stop looking (defaults to -1, unlimited). + /// In Redis 7+, we can choose if and specify a bit index or byte index (defaults to ). + /// The flags to use for this operation. + /// + /// The command returns the position of the first bit set to 1 or 0 according to the request. + /// If we look for set bits(the bit argument is 1) and the string is empty or composed of just zero bytes, -1 is returned. + /// + /// + long StringBitPosition(RedisKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None); + + /// + /// Decrements the number stored at key by decrement. + /// If the key does not exist, it is set to 0 before performing the operation. + /// An error is returned if the key contains a value of the wrong type or contains a string that is not representable as integer. + /// This operation is limited to 64 bit signed integers. + /// + /// The key of the string. + /// The amount to decrement by (defaults to 1). + /// The flags to use for this operation. + /// The value of key after the decrement. + /// + /// See + /// , + /// . + /// + long StringDecrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None); + + /// + /// Decrements the string representing a floating point number stored at key by the specified decrement. + /// If the key does not exist, it is set to 0 before performing the operation. + /// The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. + /// + /// The key of the string. + /// The amount to decrement by (defaults to 1). + /// The flags to use for this operation. + /// The value of key after the decrement. + /// + double StringDecrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None); + + /// + /// Get the value of key. If the key does not exist the special value is returned. + /// An error is returned if the value stored at key is not a string, because GET only handles string values. + /// + /// The key of the string. + /// The flags to use for this operation. + /// The value of key, or when key does not exist. + /// + RedisValue StringGet(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the values of all specified keys. + /// For every key that does not hold a string value or does not exist, the special value is returned. + /// + /// The keys of the strings. + /// The flags to use for this operation. + /// The values of the strings with for keys do not exist. + /// + RedisValue[] StringGet(RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + /// Get the value of key. If the key does not exist the special value is returned. + /// An error is returned if the value stored at key is not a string, because GET only handles string values. + /// + /// The key of the string. + /// The flags to use for this operation. + /// The value of key, or when key does not exist. + /// + Lease? StringGetLease(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the bit value at offset in the string value stored at key. + /// When offset is beyond the string length, the string is assumed to be a contiguous space with 0 bits. + /// + /// The key of the string. + /// The offset in the string to get a bit at. + /// The flags to use for this operation. + /// The bit value stored at offset. + /// + bool StringGetBit(RedisKey key, long offset, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive). + /// Negative offsets can be used in order to provide an offset starting from the end of the string. + /// So -1 means the last character, -2 the penultimate and so forth. + /// + /// The key of the string. + /// The start index of the substring to get. + /// The end index of the substring to get. + /// The flags to use for this operation. + /// The substring of the string value stored at key. + /// + RedisValue StringGetRange(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None); + + /// + /// Atomically sets key to value and returns the old value stored at key. + /// + /// The key of the string. + /// The value to replace the existing value with. + /// The flags to use for this operation. + /// The old value stored at key, or when key did not exist. + /// + RedisValue StringGetSet(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the value of and update its (relative) expiry. + /// If the key does not exist, the result will be . + /// + /// The key of the string. + /// The expiry to set. will remove expiry. + /// The flags to use for this operation. + /// The value of key, or when key does not exist. + /// + RedisValue StringGetSetExpiry(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None); + + /// + /// Gets the value of and update its (absolute) expiry. + /// If the key does not exist, the result will be . + /// + /// The key of the string. + /// The exact date and time to expire at. will remove expiry. + /// The flags to use for this operation. + /// The value of key, or when key does not exist. + /// + RedisValue StringGetSetExpiry(RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None); + + /// + /// Get the value of key and delete the key. + /// If the key does not exist the special value is returned. + /// An error is returned if the value stored at key is not a string, because GET only handles string values. + /// + /// The key of the string. + /// The flags to use for this operation. + /// The value of key, or when key does not exist. + /// + RedisValue StringGetDelete(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Get the value of key. + /// If the key does not exist the special value is returned. + /// An error is returned if the value stored at key is not a string, because GET only handles string values. + /// + /// The key of the string. + /// The flags to use for this operation. + /// The value of key and its expiry, or when key does not exist. + /// + RedisValueWithExpiry StringGetWithExpiry(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Increments the number stored at key by increment. + /// If the key does not exist, it is set to 0 before performing the operation. + /// An error is returned if the key contains a value of the wrong type or contains a string that is not representable as integer. + /// This operation is limited to 64 bit signed integers. + /// + /// The key of the string. + /// The amount to increment by (defaults to 1). + /// The flags to use for this operation. + /// The value of key after the increment. + /// + /// See + /// , + /// . + /// + long StringIncrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None); + + /// + /// Increments the string representing a floating point number stored at key by the specified increment. + /// If the key does not exist, it is set to 0 before performing the operation. + /// The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. + /// + /// The key of the string. + /// The amount to increment by (defaults to 1). + /// The flags to use for this operation. + /// The value of key after the increment. + /// + double StringIncrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the length of the string value stored at key. + /// + /// The key of the string. + /// The flags to use for this operation. + /// The length of the string at key, or 0 when key does not exist. + /// + long StringLength(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + /// Implements the longest common subsequence algorithm between the values at and , + /// returning a string containing the common sequence. + /// Note that this is different than the longest common string algorithm, + /// since matching characters in the string does not need to be contiguous. + /// + /// The key of the first string. + /// The key of the second string. + /// The flags to use for this operation. + /// A string (sequence of characters) of the LCS match. + /// + string? StringLongestCommonSubsequence(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); + + /// + /// Implements the longest common subsequence algorithm between the values at and , + /// returning the legnth of the common sequence. + /// Note that this is different than the longest common string algorithm, + /// since matching characters in the string does not need to be contiguous. + /// + /// The key of the first string. + /// The key of the second string. + /// The flags to use for this operation. + /// The length of the LCS match. + /// + long StringLongestCommonSubsequenceLength(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); + + /// + /// Implements the longest common subsequence algorithm between the values at and , + /// returning a list of all common sequences. + /// Note that this is different than the longest common string algorithm, + /// since matching characters in the string does not need to be contiguous. + /// + /// The key of the first string. + /// The key of the second string. + /// Can be used to restrict the list of matches to the ones of a given minimum length. + /// The flags to use for this operation. + /// The result of LCS algorithm, based on the given parameters. + /// + LCSMatchResult StringLongestCommonSubsequenceWithMatches(RedisKey first, RedisKey second, long minLength = 0, CommandFlags flags = CommandFlags.None); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, When when); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags); + + /// + /// Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type. + /// + /// The key of the string. + /// The value to set. + /// The expiry to set. + /// Whether to maintain the existing key's TTL (KEEPTTL flag). + /// Which condition to set the value under (defaults to always). + /// The flags to use for this operation. + /// if the string was set, otherwise. + /// + bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Sets the given keys to their respective values. + /// If is specified, this will not perform any operation at all even if just a single key already exists. + /// + /// The keys and values to set. + /// Which condition to set the value under (defaults to always). + /// The flags to use for this operation. + /// if the keys were set, otherwise. + /// + /// See + /// , + /// . + /// + bool StringSet(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Atomically sets key to value and returns the previous value (if any) stored at . + /// + /// The key of the string. + /// The value to set. + /// The expiry to set. + /// Which condition to set the value under (defaults to ). + /// The flags to use for this operation. + /// The previous value stored at , or when key did not exist. + /// + /// This method uses the SET command with the GET option introduced in Redis 6.2.0 instead of the deprecated GETSET command. + /// + /// + RedisValue StringSetAndGet(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags); + + /// + /// Atomically sets key to value and returns the previous value (if any) stored at . + /// + /// The key of the string. + /// The value to set. + /// The expiry to set. + /// Whether to maintain the existing key's TTL (KEEPTTL flag). + /// Which condition to set the value under (defaults to ). + /// The flags to use for this operation. + /// The previous value stored at , or when key did not exist. + /// This method uses the SET command with the GET option introduced in Redis 6.2.0 instead of the deprecated GETSET command. + /// + RedisValue StringSetAndGet(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + /// Sets or clears the bit at offset in the string value stored at key. + /// The bit is either set or cleared depending on value, which can be either 0 or 1. + /// When key does not exist, a new string value is created.The string is grown to make sure it can hold a bit at offset. + /// + /// The key of the string. + /// The offset in the string to set . + /// The bit value to set, true for 1, false for 0. + /// The flags to use for this operation. + /// The original bit value stored at offset. + /// + bool StringSetBit(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None); + + /// + /// Overwrites part of the string stored at key, starting at the specified offset, for the entire length of value. + /// If the offset is larger than the current length of the string at key, the string is padded with zero-bytes to make offset fit. + /// Non-existing keys are considered as empty strings, so this command will make sure it holds a string large enough to be able to set value at offset. + /// + /// The key of the string. + /// The offset in the string to overwrite. + /// The value to overwrite with. + /// The flags to use for this operation. + /// The length of the string after it was modified by the command. + /// + RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None); + } +} diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.VectorSets.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.VectorSets.cs new file mode 100644 index 000000000..863095140 --- /dev/null +++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.VectorSets.cs @@ -0,0 +1,95 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +// ReSharper disable once CheckNamespace +namespace StackExchange.Redis; + +/// +/// Describes functionality that is common to both standalone redis servers and redis clusters. +/// +public partial interface IDatabaseAsync +{ + // Vector Set operations + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task VectorSetAddAsync( + RedisKey key, + VectorSetAddRequest request, + CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task VectorSetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task VectorSetDimensionAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task?> VectorSetGetApproximateVectorAsync( + RedisKey key, + RedisValue member, + CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task VectorSetGetAttributesJsonAsync( + RedisKey key, + RedisValue member, + CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task VectorSetInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task VectorSetContainsAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task?> VectorSetGetLinksAsync( + RedisKey key, + RedisValue member, + CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task?> VectorSetGetLinksWithScoresAsync( + RedisKey key, + RedisValue member, + CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task VectorSetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task VectorSetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task VectorSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task VectorSetSetAttributesJsonAsync( + RedisKey key, + RedisValue member, +#if NET7_0_OR_GREATER + [StringSyntax(StringSyntaxAttribute.Json)] +#endif + string attributesJson, + CommandFlags flags = CommandFlags.None); + + /// + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + Task?> VectorSetSimilaritySearchAsync( + RedisKey key, + VectorSetSimilaritySearchRequest query, + CommandFlags flags = CommandFlags.None); +} diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs new file mode 100644 index 000000000..0bc7b4867 --- /dev/null +++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs @@ -0,0 +1,848 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net; +using System.Threading.Tasks; + +// ReSharper disable once CheckNamespace +namespace StackExchange.Redis +{ + /// + /// Describes functionality that is common to both standalone redis servers and redis clusters. + /// + public partial interface IDatabaseAsync : IRedisAsync + { + /// + /// Indicates whether the instance can communicate with the server (resolved using the supplied key and optional flags). + /// + /// The key to check for. + /// The flags to use for this operation. + bool IsConnected(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task KeyMigrateAsync(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None); + + /// " + Task DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task GeoAddAsync(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + Task GeoAddAsync(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None); + + /// + Task GeoAddAsync(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None); + + /// + Task GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + Task GeoDistanceAsync(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None); + + /// + Task GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); + + /// + Task GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + Task GeoPositionAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); + + /// + Task GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + Task GeoRadiusAsync(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None); + + /// + Task GeoRadiusAsync(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None); + + /// + Task GeoSearchAsync(RedisKey key, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None); + + /// + Task GeoSearchAsync(RedisKey key, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None); + + /// + Task GeoSearchAndStoreAsync(RedisKey sourceKey, RedisKey destinationKey, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None); + + /// + Task GeoSearchAndStoreAsync(RedisKey sourceKey, RedisKey destinationKey, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None); + + /// + Task HashDecrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None); + + /// + Task HashDecrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None); + + /// + Task HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + Task HashDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + Task?> HashFieldGetLeaseAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None); + + /// + Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetExpireDateTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + Task HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + Task HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + Task?> HashGetLeaseAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + Task HashGetAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + Task HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task HashIncrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None); + + /// + Task HashIncrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None); + + /// + Task HashKeysAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task HashRandomFieldAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task HashRandomFieldsAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + Task HashRandomFieldsWithValuesAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + IAsyncEnumerable HashScanAsync(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None); + + /// + IAsyncEnumerable HashScanNoValuesAsync(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None); + + /// + Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + Task HashSetAsync(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task HashStringLengthAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + Task HashValuesAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task HyperLogLogAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + Task HyperLogLogAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); + + /// + Task HyperLogLogLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task HyperLogLogLengthAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + Task HyperLogLogMergeAsync(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); + + /// + Task HyperLogLogMergeAsync(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None); + + /// + Task IdentifyEndpointAsync(RedisKey key = default, CommandFlags flags = CommandFlags.None); + + /// + Task KeyCopyAsync(RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase = -1, bool replace = false, CommandFlags flags = CommandFlags.None); + + /// + Task KeyDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task KeyDeleteAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + Task KeyDumpAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task KeyEncodingAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task KeyExistsAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task KeyExistsAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags); + + /// + Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Task KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags); + + /// + Task KeyExpireAsync(RedisKey key, DateTime? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + Task KeyExpireTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task KeyFrequencyAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task KeyIdleTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task KeyMoveAsync(RedisKey key, int database, CommandFlags flags = CommandFlags.None); + + /// + Task KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task KeyRandomAsync(CommandFlags flags = CommandFlags.None); + + /// + Task KeyRefCountAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task KeyRenameAsync(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None); + + /// + Task KeyTimeToLiveAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task KeyTouchAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task KeyTouchAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + Task KeyTypeAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task ListGetByIndexAsync(RedisKey key, long index, CommandFlags flags = CommandFlags.None); + + /// + Task ListInsertAfterAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + Task ListInsertBeforeAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + Task ListLeftPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task ListLeftPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + Task ListLeftPopAsync(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None); + + /// + Task ListPositionAsync(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None); + + /// + Task ListPositionsAsync(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None); + + /// + Task ListLeftPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task ListLeftPushAsync(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task ListLeftPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags); + + /// + Task ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task ListMoveAsync(RedisKey sourceKey, RedisKey destinationKey, ListSide sourceSide, ListSide destinationSide, CommandFlags flags = CommandFlags.None); + + /// + Task ListRangeAsync(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None); + + /// + Task ListRemoveAsync(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None); + + /// + Task ListRightPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task ListRightPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + Task ListRightPopAsync(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None); + + /// + Task ListRightPopLeftPushAsync(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None); + + /// + Task ListRightPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task ListRightPushAsync(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task ListRightPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags); + + /// + Task ListSetByIndexAsync(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + Task ListTrimAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None); + + /// + Task LockExtendAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None); + + /// + Task LockQueryAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task LockReleaseAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + Task LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None); + + /// + Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None); + + /// + Task ExecuteAsync(string command, params object[] args); + + /// + Task ExecuteAsync(string command, ICollection? args, CommandFlags flags = CommandFlags.None); + + /// + Task ScriptEvaluateAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + Task ScriptEvaluateAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + Task ScriptEvaluateAsync(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); + + /// + Task ScriptEvaluateAsync(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None); + + /// + Task ScriptEvaluateReadOnlyAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + Task ScriptEvaluateReadOnlyAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None); + + /// + Task SetAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + Task SetAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); + + /// + Task SetCombineAsync(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); + + /// + Task SetCombineAsync(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); + + /// + Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + Task SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + Task SetContainsAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); + + /// + Task SetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None); + + /// + Task SetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task SetMembersAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task SetMoveAsync(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + Task SetPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task SetPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + Task SetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task SetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + Task SetRemoveAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + Task SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None); + + /// + IAsyncEnumerable SetScanAsync(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None); + + /// + Task SortAsync(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None); + + /// + Task SortAndStoreAsync(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, CommandFlags flags); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, When when, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, CommandFlags flags); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, When when, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetCombineAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetCombineWithScoresAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetDecrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetIncrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetLengthAsync(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetLengthByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRandomMembersWithScoresAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRangeByRankAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRangeAndStoreAsync( + RedisKey sourceKey, + RedisKey destinationKey, + RedisValue start, + RedisValue stop, + SortedSetOrder sortedSetOrder = SortedSetOrder.ByRank, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long? take = null, + CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRangeByRankWithScoresAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRangeByScoreAsync( + RedisKey key, + double start = double.NegativeInfinity, + double stop = double.PositiveInfinity, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long take = -1, + CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRangeByScoreWithScoresAsync( + RedisKey key, + double start = double.NegativeInfinity, + double stop = double.PositiveInfinity, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long take = -1, + CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRangeByValueAsync( + RedisKey key, + RedisValue min, + RedisValue max, + Exclude exclude, + long skip, + long take = -1, + CommandFlags flags = CommandFlags.None); // defaults removed to avoid ambiguity with overload with order + + /// + Task SortedSetRangeByValueAsync( + RedisKey key, + RedisValue min = default, + RedisValue max = default, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long take = -1, + CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRankAsync(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRemoveAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRemoveRangeByRankAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); + + /// + IAsyncEnumerable SortedSetScanAsync( + RedisKey key, + RedisValue pattern = default, + int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, + long cursor = RedisBase.CursorUtils.Origin, + int pageOffset = 0, + CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetScoreAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetScoresAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetUpdateAsync(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetUpdateAsync(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetPopAsync(RedisKey key, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetPopAsync(RedisKey key, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + Task SortedSetPopAsync(RedisKey[] keys, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None); + + /// + Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + +#pragma warning disable RS0026 // similar overloads + /// + Task StreamAcknowledgeAndDeleteAsync(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue messageId, CommandFlags flags = CommandFlags.None); + + /// + Task StreamAcknowledgeAndDeleteAsync(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0026 + + /// + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags); + + /// + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags); + +#pragma warning disable RS0026 // similar overloads + /// + Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + + /// + Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode trimMode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0026 + + /// + Task StreamAutoClaimAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None); + + /// + Task StreamAutoClaimIdsOnlyAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None); + + /// + Task StreamClaimAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + Task StreamClaimIdsOnlyAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + Task StreamConsumerGroupSetPositionAsync(RedisKey key, RedisValue groupName, RedisValue position, CommandFlags flags = CommandFlags.None); + + /// + Task StreamConsumerInfoAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + + /// + Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue? position, CommandFlags flags); + + /// + Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue? position = null, bool createStream = true, CommandFlags flags = CommandFlags.None); + +#pragma warning disable RS0026 + /// + Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None); + + /// + Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, StreamTrimMode mode, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0026 + + /// + Task StreamDeleteConsumerAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, CommandFlags flags = CommandFlags.None); + + /// + Task StreamDeleteConsumerGroupAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + + /// + Task StreamGroupInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task StreamInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task StreamPendingAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None); + + /// + Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId, RedisValue? maxId, CommandFlags flags); + + /// + Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, long? minIdleTimeInMs = null, CommandFlags flags = CommandFlags.None); + + /// + Task StreamRangeAsync(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None); + + /// + Task StreamReadAsync(RedisKey key, RedisValue position, int? count = null, CommandFlags flags = CommandFlags.None); + + /// + Task StreamReadAsync(StreamPosition[] streamPositions, int? countPerStream = null, CommandFlags flags = CommandFlags.None); + + /// + Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position, int? count, CommandFlags flags); + + /// + Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position = null, int? count = null, bool noAck = false, CommandFlags flags = CommandFlags.None); + + /// + Task StreamReadGroupAsync(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream, CommandFlags flags); + + /// + Task StreamReadGroupAsync(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream = null, bool noAck = false, CommandFlags flags = CommandFlags.None); + + /// + Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength, CommandFlags flags); + + /// + Task StreamTrimAsync(RedisKey key, long maxLength, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + + /// + Task StreamTrimByMinIdAsync(RedisKey key, RedisValue minId, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None); + + /// + Task StringAppendAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Task StringBitCountAsync(RedisKey key, long start, long end, CommandFlags flags); + + /// + Task StringBitCountAsync(RedisKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None); + + /// + Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default, CommandFlags flags = CommandFlags.None); + + /// + Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Task StringBitPositionAsync(RedisKey key, bool bit, long start, long end, CommandFlags flags); + + /// + Task StringBitPositionAsync(RedisKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None); + + /// + Task StringDecrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None); + + /// + Task StringDecrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None); + + /// + Task StringGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task StringGetAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None); + + /// + Task?> StringGetLeaseAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task StringGetBitAsync(RedisKey key, long offset, CommandFlags flags = CommandFlags.None); + + /// + Task StringGetRangeAsync(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None); + + /// + Task StringGetSetAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + Task StringGetSetExpiryAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None); + + /// + Task StringGetSetExpiryAsync(RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None); + + /// + Task StringGetDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task StringGetWithExpiryAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task StringIncrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None); + + /// + Task StringIncrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None); + + /// + Task StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None); + + /// + Task StringLongestCommonSubsequenceAsync(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); + + /// t + Task StringLongestCommonSubsequenceLengthAsync(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None); + + /// + Task StringLongestCommonSubsequenceWithMatchesAsync(RedisKey first, RedisKey second, long minLength = 0, CommandFlags flags = CommandFlags.None); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when); + + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags); + + /// + Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task StringSetAsync(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task StringSetAndGetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags); + + /// + Task StringSetAndGetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None); + + /// + Task StringSetBitAsync(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None); + + /// + Task StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None); + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/IMultiMessage.cs b/src/StackExchange.Redis/Interfaces/IMultiMessage.cs similarity index 100% rename from StackExchange.Redis/StackExchange/Redis/IMultiMessage.cs rename to src/StackExchange.Redis/Interfaces/IMultiMessage.cs diff --git a/StackExchange.Redis/StackExchange/Redis/IReconnectRetryPolicy.cs b/src/StackExchange.Redis/Interfaces/IReconnectRetryPolicy.cs similarity index 80% rename from StackExchange.Redis/StackExchange/Redis/IReconnectRetryPolicy.cs rename to src/StackExchange.Redis/Interfaces/IReconnectRetryPolicy.cs index c368b0ea8..7bb29843a 100644 --- a/StackExchange.Redis/StackExchange/Redis/IReconnectRetryPolicy.cs +++ b/src/StackExchange.Redis/Interfaces/IReconnectRetryPolicy.cs @@ -1,17 +1,15 @@ -using System; - -namespace StackExchange.Redis +namespace StackExchange.Redis { /// - /// Describes retry policy functionality that can be provided to the multiplexer to be used for connection reconnects + /// Describes retry policy functionality that can be provided to the multiplexer to be used for connection reconnects. /// public interface IReconnectRetryPolicy { /// /// This method is called by the multiplexer to determine if a reconnect operation can be retried now. /// - /// The number of times reconnect retries have already been made by the multiplexer while it was in connecting state - /// Total time elapsed in milliseconds since the last reconnect retry was made + /// The number of times reconnect retries have already been made by the multiplexer while it was in connecting state. + /// Total time elapsed in milliseconds since the last reconnect retry was made. bool ShouldRetry(long currentRetryCount, int timeElapsedMillisecondsSinceLastRetry); } -} \ No newline at end of file +} diff --git a/src/StackExchange.Redis/Interfaces/IRedis.cs b/src/StackExchange.Redis/Interfaces/IRedis.cs new file mode 100644 index 000000000..3507aa433 --- /dev/null +++ b/src/StackExchange.Redis/Interfaces/IRedis.cs @@ -0,0 +1,18 @@ +using System; + +namespace StackExchange.Redis +{ + /// + /// Common operations available to all redis connections. + /// + public partial interface IRedis : IRedisAsync + { + /// + /// This command is often used to test if a connection is still alive, or to measure latency. + /// + /// The command flags to use when pinging. + /// The observed latency. + /// + TimeSpan Ping(CommandFlags flags = CommandFlags.None); + } +} diff --git a/src/StackExchange.Redis/Interfaces/IRedisAsync.cs b/src/StackExchange.Redis/Interfaces/IRedisAsync.cs new file mode 100644 index 000000000..4c20d5e72 --- /dev/null +++ b/src/StackExchange.Redis/Interfaces/IRedisAsync.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + /// + /// Common operations available to all redis connections. + /// + public partial interface IRedisAsync + { + /// + /// Gets the multiplexer that created this instance. + /// + IConnectionMultiplexer Multiplexer { get; } + + /// + /// This command is often used to test if a connection is still alive, or to measure latency. + /// + /// The command flags to use. + /// The observed latency. + /// + Task PingAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Wait for a given asynchronous operation to complete (or timeout), reporting which. + /// + /// The task to wait on. + bool TryWait(Task task); + + /// + /// Wait for a given asynchronous operation to complete (or timeout). + /// + /// The task to wait on. + void Wait(Task task); + + /// + /// Wait for a given asynchronous operation to complete (or timeout). + /// + /// The type of task to wait on. + /// The task to wait on. + T Wait(Task task); + + /// + /// Wait for the given asynchronous operations to complete (or timeout). + /// + /// The tasks to wait on. + void WaitAll(params Task[] tasks); + } +} diff --git a/src/StackExchange.Redis/Interfaces/IScanningCursor.cs b/src/StackExchange.Redis/Interfaces/IScanningCursor.cs new file mode 100644 index 000000000..a9c8c45cf --- /dev/null +++ b/src/StackExchange.Redis/Interfaces/IScanningCursor.cs @@ -0,0 +1,23 @@ +namespace StackExchange.Redis +{ + /// + /// Represents a resumable, cursor-based scanning operation. + /// + public interface IScanningCursor + { + /// + /// Returns the cursor that represents the *active* page of results (not the pending/next page of results as returned by SCAN/HSCAN/ZSCAN/SSCAN). + /// + long Cursor { get; } + + /// + /// The page size of the current operation. + /// + int PageSize { get; } + + /// + /// The offset into the current page. + /// + int PageOffset { get; } + } +} diff --git a/src/StackExchange.Redis/Interfaces/IServer.cs b/src/StackExchange.Redis/Interfaces/IServer.cs new file mode 100644 index 000000000..8e4178fc9 --- /dev/null +++ b/src/StackExchange.Redis/Interfaces/IServer.cs @@ -0,0 +1,818 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + /// + /// Provides configuration controls of a redis server. + /// + public partial interface IServer : IRedis + { + /// + /// Gets the cluster configuration associated with this server, if known. + /// + ClusterConfiguration? ClusterConfiguration { get; } + + /// + /// Gets the address of the connected server. + /// + EndPoint EndPoint { get; } + + /// + /// Gets the features available to the connected server. + /// + RedisFeatures Features { get; } + + /// + /// Gets whether the connection to the server is active and usable. + /// + bool IsConnected { get; } + + /// + /// The protocol being used to communicate with this server (if not connected/known, then the anticipated protocol from the configuration is returned, assuming success). + /// + RedisProtocol Protocol { get; } + + /// + /// Gets whether the connected server is a replica. + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(IsReplica) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + bool IsSlave { get; } + + /// + /// Gets whether the connected server is a replica. + /// + bool IsReplica { get; } + + /// + /// Explicitly opt in for replica writes on writable replica. + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(AllowReplicaWrites) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + bool AllowSlaveWrites { get; set; } + + /// + /// Explicitly opt in for replica writes on writable replica. + /// + bool AllowReplicaWrites { get; set; } + + /// + /// Gets the operating mode of the connected server. + /// + ServerType ServerType { get; } + + /// + /// Gets the version of the connected server. + /// + Version Version { get; } + + /// + /// The number of databases supported on this server. + /// + int DatabaseCount { get; } + + /// + /// The CLIENT KILL command closes a given client connection identified by ip:port. + /// The ip:port should match a line returned by the CLIENT LIST command. + /// Due to the single-threaded nature of Redis, it is not possible to kill a client connection while it is executing a command. + /// From the client point of view, the connection can never be closed in the middle of the execution of a command. + /// However, the client will notice the connection has been closed only when the next command is sent (and results in network error). + /// + /// The endpoint of the client to kill. + /// The command flags to use. + /// + void ClientKill(EndPoint endpoint, CommandFlags flags = CommandFlags.None); + + /// + Task ClientKillAsync(EndPoint endpoint, CommandFlags flags = CommandFlags.None); + + /// + /// The CLIENT KILL command closes multiple connections that match the specified filters. + /// + /// The ID of the client to kill. + /// The type of client. + /// The endpoint to kill. + /// Whether to skip the current connection. + /// The command flags to use. + /// the number of clients killed. + /// + long ClientKill(long? id = null, ClientType? clientType = null, EndPoint? endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None); + + /// + Task ClientKillAsync(long? id = null, ClientType? clientType = null, EndPoint? endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None); + + /// + /// The CLIENT KILL command closes multiple connections that match the specified filters. + /// + /// The filter to use in choosing which clients to kill. + /// The command flags to use. + /// the number of clients killed. + long ClientKill(ClientKillFilter filter, CommandFlags flags = CommandFlags.None); + + /// + Task ClientKillAsync(ClientKillFilter filter, CommandFlags flags = CommandFlags.None); + + /// + /// The CLIENT LIST command returns information and statistics about the client connections server in a mostly human readable format. + /// + /// The command flags to use. + /// + ClientInfo[] ClientList(CommandFlags flags = CommandFlags.None); + + /// + Task ClientListAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Obtains the current CLUSTER NODES output from a cluster server. + /// + /// The command flags to use. + /// + ClusterConfiguration? ClusterNodes(CommandFlags flags = CommandFlags.None); + + /// + Task ClusterNodesAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Obtains the current raw CLUSTER NODES output from a cluster server. + /// + /// The command flags to use. + /// + string? ClusterNodesRaw(CommandFlags flags = CommandFlags.None); + + /// + Task ClusterNodesRawAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Get all configuration parameters matching the specified pattern. + /// + /// The pattern of config values to get. + /// The command flags to use. + /// All matching configuration parameters. + /// + KeyValuePair[] ConfigGet(RedisValue pattern = default, CommandFlags flags = CommandFlags.None); + + /// + Task[]> ConfigGetAsync(RedisValue pattern = default, CommandFlags flags = CommandFlags.None); + + /// + /// Resets the statistics reported by Redis using the INFO command. + /// + /// The command flags to use. + /// + void ConfigResetStatistics(CommandFlags flags = CommandFlags.None); + + /// + Task ConfigResetStatisticsAsync(CommandFlags flags = CommandFlags.None); + + /// + /// The CONFIG REWRITE command rewrites the redis.conf file the server was started with, + /// applying the minimal changes needed to make it reflecting the configuration currently + /// used by the server, that may be different compared to the original one because of the use of the CONFIG SET command. + /// + /// The command flags to use. + /// + void ConfigRewrite(CommandFlags flags = CommandFlags.None); + + /// + Task ConfigRewriteAsync(CommandFlags flags = CommandFlags.None); + + /// + /// The CONFIG SET command is used in order to reconfigure the server at runtime without the need to restart Redis. + /// You can change both trivial parameters or switch from one to another persistence option using this command. + /// + /// The setting name. + /// The new setting value. + /// The command flags to use. + /// + void ConfigSet(RedisValue setting, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + Task ConfigSetAsync(RedisValue setting, RedisValue value, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the number of total commands available in this Redis server. + /// + /// The command flags to use. + /// + long CommandCount(CommandFlags flags = CommandFlags.None); + + /// + Task CommandCountAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Returns list of keys from a full Redis command. + /// + /// The command to get keys from. + /// The command flags to use. + /// + RedisKey[] CommandGetKeys(RedisValue[] command, CommandFlags flags = CommandFlags.None); + + /// + Task CommandGetKeysAsync(RedisValue[] command, CommandFlags flags = CommandFlags.None); + + /// + /// Returns a list of command names available on this Redis server. + /// Only one of the filter options is usable at a time. + /// + /// The module name to filter the command list by. + /// The category to filter the command list by. + /// The pattern to filter the command list by. + /// The command flags to use. + /// + string[] CommandList(RedisValue? moduleName = null, RedisValue? category = null, RedisValue? pattern = null, CommandFlags flags = CommandFlags.None); + + /// + Task CommandListAsync(RedisValue? moduleName = null, RedisValue? category = null, RedisValue? pattern = null, CommandFlags flags = CommandFlags.None); + + /// + /// Return the number of keys in the database. + /// + /// The database ID. + /// The command flags to use. + /// + long DatabaseSize(int database = -1, CommandFlags flags = CommandFlags.None); + + /// + Task DatabaseSizeAsync(int database = -1, CommandFlags flags = CommandFlags.None); + + /// + /// Return the same message passed in. + /// + /// The message to echo. + /// The command flags to use. + /// + RedisValue Echo(RedisValue message, CommandFlags flags = CommandFlags.None); + + /// + Task EchoAsync(RedisValue message, CommandFlags flags = CommandFlags.None); + + /// + /// Execute an arbitrary command against the server; this is primarily intended for + /// executing modules, but may also be used to provide access to new features that lack + /// a direct API. + /// + /// The command to run. + /// The arguments to pass for the command. + /// A dynamic representation of the command's result. + /// This API should be considered an advanced feature; inappropriate use can be harmful. + RedisResult Execute(string command, params object[] args); + + /// + Task ExecuteAsync(string command, params object[] args); + +#pragma warning disable RS0026, RS0027 // multiple overloads + /// + /// Execute an arbitrary command against the server; this is primarily intended for + /// executing modules, but may also be used to provide access to new features that lack + /// a direct API. The command is assumed to be not database-specific. If this is not the case, + /// should be used to + /// specify the database (using null to use the configured default database). + /// + /// The command to run. + /// The arguments to pass for the command. + /// The flags to use for this operation. + /// A dynamic representation of the command's result. + /// This API should be considered an advanced feature; inappropriate use can be harmful. + RedisResult Execute(string command, ICollection args, CommandFlags flags = CommandFlags.None); + + /// + Task ExecuteAsync(string command, ICollection args, CommandFlags flags = CommandFlags.None); +#pragma warning restore RS0026, RS0027 + + /// + /// Execute an arbitrary database-specific command against the server; this is primarily intended for + /// executing modules, but may also be used to provide access to new features that lack + /// a direct API. + /// + /// The database ID; if , the configured default database is used. + /// The command to run. + /// The arguments to pass for the command. + /// The flags to use for this operation. + /// A dynamic representation of the command's result. + /// This API should be considered an advanced feature; inappropriate use can be harmful. + RedisResult Execute(int? database, string command, ICollection args, CommandFlags flags = CommandFlags.None); + + /// + Task ExecuteAsync(int? database, string command, ICollection args, CommandFlags flags = CommandFlags.None); + + /// + /// Delete all the keys of all databases on the server. + /// + /// The command flags to use. + /// + void FlushAllDatabases(CommandFlags flags = CommandFlags.None); + + /// + Task FlushAllDatabasesAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Delete all the keys of the database. + /// + /// The database ID. + /// The command flags to use. + /// + void FlushDatabase(int database = -1, CommandFlags flags = CommandFlags.None); + + /// + Task FlushDatabaseAsync(int database = -1, CommandFlags flags = CommandFlags.None); + + /// + /// Get summary statistics associates with this server. + /// + ServerCounters GetCounters(); + + /// + /// The INFO command returns information and statistics about the server in a format that is simple to parse by computers and easy to read by humans. + /// + /// The info section to get, if getting a specific one. + /// The command flags to use. + /// A grouping of key/value pairs, grouped by their section header. + /// + IGrouping>[] Info(RedisValue section = default, CommandFlags flags = CommandFlags.None); + + /// + Task>[]> InfoAsync(RedisValue section = default, CommandFlags flags = CommandFlags.None); + + /// + /// The INFO command returns information and statistics about the server in a format that is simple to parse by computers and easy to read by humans. + /// + /// The info section to get, if getting a specific one. + /// The command flags to use. + /// The entire raw INFO string. + /// + string? InfoRaw(RedisValue section = default, CommandFlags flags = CommandFlags.None); + + /// + Task InfoRawAsync(RedisValue section = default, CommandFlags flags = CommandFlags.None); + + /// + IEnumerable Keys(int database, RedisValue pattern, int pageSize, CommandFlags flags); + + /// + /// Returns all keys matching . + /// The KEYS or SCAN commands will be used based on the server capabilities. + /// Note: to resume an iteration via cursor, cast the original enumerable or enumerator to . + /// + /// The database ID. + /// The pattern to use. + /// The page size to iterate by. + /// The cursor position to resume at. + /// The page offset to start at. + /// The command flags to use. + /// An enumeration of matching redis keys. + /// + /// Warning: consider KEYS as a command that should only be used in production environments with extreme care. + /// + /// See + /// , + /// . + /// + /// + IEnumerable Keys(int database = -1, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None); + + /// + IAsyncEnumerable KeysAsync(int database = -1, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None); + + /// + /// Return the time of the last DB save executed with success. + /// A client may check if a BGSAVE command succeeded reading the LASTSAVE value, then issuing a BGSAVE command + /// and checking at regular intervals every N seconds if LASTSAVE changed. + /// + /// The command flags to use. + /// The last time a save was performed. + /// + DateTime LastSave(CommandFlags flags = CommandFlags.None); + + /// + Task LastSaveAsync(CommandFlags flags = CommandFlags.None); + + /// + [Obsolete("Please use " + nameof(MakePrimaryAsync) + ", this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + void MakeMaster(ReplicationChangeOptions options, TextWriter? log = null); + + /// + /// Promote the selected node to be primary. + /// + /// The options to use for this topology change. + /// The log to write output to. + /// + Task MakePrimaryAsync(ReplicationChangeOptions options, TextWriter? log = null); + + /// + /// Returns the role info for the current server. + /// + /// + Role Role(CommandFlags flags = CommandFlags.None); + + /// + Task RoleAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Explicitly request the database to persist the current state to disk. + /// + /// The method of the save (e.g. background or foreground). + /// The command flags to use. + /// + /// See + /// , + /// , + /// , + /// . + /// + void Save(SaveType type, CommandFlags flags = CommandFlags.None); + + /// + Task SaveAsync(SaveType type, CommandFlags flags = CommandFlags.None); + + /// + /// Indicates whether the specified script is defined on the server. + /// + /// The text of the script to check for on the server. + /// The command flags to use. + /// + bool ScriptExists(string script, CommandFlags flags = CommandFlags.None); + + /// + Task ScriptExistsAsync(string script, CommandFlags flags = CommandFlags.None); + + /// + /// Indicates whether the specified script hash is defined on the server. + /// + /// The SHA1 of the script to check for on the server. + /// The command flags to use. + /// + bool ScriptExists(byte[] sha1, CommandFlags flags = CommandFlags.None); + + /// + Task ScriptExistsAsync(byte[] sha1, CommandFlags flags = CommandFlags.None); + + /// + /// Removes all cached scripts on this server. + /// + /// The command flags to use. + /// + void ScriptFlush(CommandFlags flags = CommandFlags.None); + + /// + Task ScriptFlushAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Explicitly defines a script on the server. + /// + /// The script to load. + /// The command flags to use. + /// The SHA1 of the loaded script. + /// + byte[] ScriptLoad(string script, CommandFlags flags = CommandFlags.None); + + /// + Task ScriptLoadAsync(string script, CommandFlags flags = CommandFlags.None); + + /// + /// Explicitly defines a script on the server. + /// + /// The script to load. + /// The command flags to use. + /// The loaded script, ready for rapid reuse based on the SHA1. + /// + LoadedLuaScript ScriptLoad(LuaScript script, CommandFlags flags = CommandFlags.None); + + /// + Task ScriptLoadAsync(LuaScript script, CommandFlags flags = CommandFlags.None); + + /// + /// Asks the redis server to shutdown, killing all connections. Please FULLY read the notes on the SHUTDOWN command. + /// + /// The mode of the shutdown. + /// The command flags to use. + /// + void Shutdown(ShutdownMode shutdownMode = ShutdownMode.Default, CommandFlags flags = CommandFlags.None); + + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(ReplicaOfAsync) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + void SlaveOf(EndPoint master, CommandFlags flags = CommandFlags.None); + + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(ReplicaOfAsync) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Task SlaveOfAsync(EndPoint master, CommandFlags flags = CommandFlags.None); + + /// + [Obsolete("Please use " + nameof(ReplicaOfAsync) + ", this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + void ReplicaOf(EndPoint master, CommandFlags flags = CommandFlags.None); + + /// + /// The REPLICAOF command can change the replication settings of a replica on the fly. + /// If a Redis server is already acting as replica, specifying a null primary will turn off the replication, + /// turning the Redis server into a PRIMARY. Specifying a non-null primary will make the server a replica of + /// another server listening at the specified hostname and port. + /// + /// Endpoint of the new primary to replicate from. + /// The command flags to use. + /// + Task ReplicaOfAsync(EndPoint master, CommandFlags flags = CommandFlags.None); + + /// + /// To read the slow log the SLOWLOG GET command is used, that returns every entry in the slow log. + /// It is possible to return only the N most recent entries passing an additional argument to the command (for instance SLOWLOG GET 10). + /// + /// The count of items to get. + /// The command flags to use. + /// The slow command traces as recorded by the Redis server. + /// + CommandTrace[] SlowlogGet(int count = 0, CommandFlags flags = CommandFlags.None); + + /// + Task SlowlogGetAsync(int count = 0, CommandFlags flags = CommandFlags.None); + + /// + /// You can reset the slow log using the SLOWLOG RESET command. Once deleted the information is lost forever. + /// + /// The command flags to use. + /// + void SlowlogReset(CommandFlags flags = CommandFlags.None); + + /// + Task SlowlogResetAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Lists the currently active channels. + /// An active channel is a Pub/Sub channel with one ore more subscribers (not including clients subscribed to patterns). + /// + /// The channel name pattern to get channels for. + /// The command flags to use. + /// a list of active channels, optionally matching the specified pattern. + /// + RedisChannel[] SubscriptionChannels(RedisChannel pattern = default, CommandFlags flags = CommandFlags.None); + + /// + Task SubscriptionChannelsAsync(RedisChannel pattern = default, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the number of subscriptions to patterns (that are performed using the PSUBSCRIBE command). + /// Note that this is not just the count of clients subscribed to patterns but the total number of patterns all the clients are subscribed to. + /// + /// The command flags to use. + /// the number of patterns all the clients are subscribed to. + /// + long SubscriptionPatternCount(CommandFlags flags = CommandFlags.None); + + /// + Task SubscriptionPatternCountAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Returns the number of subscribers (not counting clients subscribed to patterns) for the specified channel. + /// + /// The channel to get a subscriber count for. + /// The command flags to use. + /// The number of subscribers on this server. + /// + long SubscriptionSubscriberCount(RedisChannel channel, CommandFlags flags = CommandFlags.None); + + /// + Task SubscriptionSubscriberCountAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None); + + /// + /// Swaps two Redis databases, so that immediately all the clients connected to a given database will see the data of the other database, and the other way around. + /// + /// The ID of the first database. + /// The ID of the second database. + /// The command flags to use. + /// + void SwapDatabases(int first, int second, CommandFlags flags = CommandFlags.None); + + /// + Task SwapDatabasesAsync(int first, int second, CommandFlags flags = CommandFlags.None); + + /// + /// The TIME command returns the current server time in UTC format. + /// Use the method to get local time. + /// + /// The command flags to use. + /// The server's current time. + /// + DateTime Time(CommandFlags flags = CommandFlags.None); + + /// + Task TimeAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Gets a text-based latency diagnostic. + /// + /// The full text result of latency doctor. + /// + /// See + /// , + /// . + /// + string LatencyDoctor(CommandFlags flags = CommandFlags.None); + + /// + Task LatencyDoctorAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Resets the given events (or all if none are specified), discarding the currently logged latency spike events, and resetting the maximum event time register. + /// + /// The number of events that were reset. + /// + /// See + /// , + /// . + /// + long LatencyReset(string[]? eventNames = null, CommandFlags flags = CommandFlags.None); + + /// + Task LatencyResetAsync(string[]? eventNames = null, CommandFlags flags = CommandFlags.None); + + /// + /// Fetch raw latency data from the event time series, as timestamp-latency pairs. + /// + /// An array of latency history entries. + /// + /// See + /// , + /// . + /// + LatencyHistoryEntry[] LatencyHistory(string eventName, CommandFlags flags = CommandFlags.None); + + /// + Task LatencyHistoryAsync(string eventName, CommandFlags flags = CommandFlags.None); + + /// + /// Fetch raw latency data from the event time series, as timestamp-latency pairs. + /// + /// An array of the latest latency history entries. + /// + /// See + /// , + /// . + /// + LatencyLatestEntry[] LatencyLatest(CommandFlags flags = CommandFlags.None); + + /// + Task LatencyLatestAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Reports about different memory-related issues that the Redis server experiences, and advises about possible remedies. + /// + /// The full text result of memory doctor. + /// + string MemoryDoctor(CommandFlags flags = CommandFlags.None); + + /// + Task MemoryDoctorAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Attempts to purge dirty pages so these can be reclaimed by the allocator. + /// + /// + void MemoryPurge(CommandFlags flags = CommandFlags.None); + + /// + Task MemoryPurgeAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Returns an array reply about the memory usage of the server. + /// + /// An array reply of memory stat metrics and values. + /// + RedisResult MemoryStats(CommandFlags flags = CommandFlags.None); + + /// + Task MemoryStatsAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Provides an internal statistics report from the memory allocator. + /// + /// The full text result of memory allocation stats. + /// + string? MemoryAllocatorStats(CommandFlags flags = CommandFlags.None); + + /// + Task MemoryAllocatorStatsAsync(CommandFlags flags = CommandFlags.None); + + /// + /// Returns the IP and port number of the primary with that name. + /// If a failover is in progress or terminated successfully for this primary it returns the address and port of the promoted replica. + /// + /// The sentinel service name. + /// The command flags to use. + /// The primary IP and port. + /// + EndPoint? SentinelGetMasterAddressByName(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + Task SentinelGetMasterAddressByNameAsync(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the IP and port numbers of all known Sentinels for the given service name. + /// + /// The sentinel service name. + /// The command flags to use. + /// A list of the sentinel IPs and ports. + /// + EndPoint[] SentinelGetSentinelAddresses(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + Task SentinelGetSentinelAddressesAsync(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + /// Returns the IP and port numbers of all known Sentinel replicas for the given service name. + /// + /// The sentinel service name. + /// The command flags to use. + /// A list of the replica IPs and ports. + /// + EndPoint[] SentinelGetReplicaAddresses(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + Task SentinelGetReplicaAddressesAsync(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + /// Show the state and info of the specified primary. + /// + /// The sentinel service name. + /// The command flags to use. + /// The primaries state as KeyValuePairs. + /// + KeyValuePair[] SentinelMaster(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + Task[]> SentinelMasterAsync(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + /// Show a list of monitored primaries and their state. + /// + /// The command flags to use. + /// An array of primaries state KeyValuePair arrays. + /// + KeyValuePair[][] SentinelMasters(CommandFlags flags = CommandFlags.None); + + /// + Task[][]> SentinelMastersAsync(CommandFlags flags = CommandFlags.None); + + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(SentinelReplicas) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + KeyValuePair[][] SentinelSlaves(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(SentinelReplicasAsync) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + Task[][]> SentinelSlavesAsync(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + /// Show a list of replicas for this primary, and their state. + /// + /// The sentinel service name. + /// The command flags to use. + /// An array of replica state KeyValuePair arrays. + /// + KeyValuePair[][] SentinelReplicas(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + Task[][]> SentinelReplicasAsync(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + /// Force a failover as if the primary was not reachable, and without asking for agreement to other Sentinels + /// (however a new version of the configuration will be published so that the other Sentinels will update their configurations). + /// + /// The sentinel service name. + /// The command flags to use. + /// + void SentinelFailover(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + Task SentinelFailoverAsync(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + /// Show a list of sentinels for a primary, and their state. + /// + /// The sentinel service name. + /// The command flags to use. + /// + KeyValuePair[][] SentinelSentinels(string serviceName, CommandFlags flags = CommandFlags.None); + + /// + Task[][]> SentinelSentinelsAsync(string serviceName, CommandFlags flags = CommandFlags.None); + } + + internal static class IServerExtensions + { + /// + /// For testing only: Break the connection without mercy or thought. + /// + /// The server to simulate failure on. + /// The type of failure(s) to simulate. + internal static void SimulateConnectionFailure(this IServer server, SimulatedFailureType failureType) => (server as RedisServer)?.SimulateConnectionFailure(failureType); + } +} diff --git a/src/StackExchange.Redis/Interfaces/ISubscriber.cs b/src/StackExchange.Redis/Interfaces/ISubscriber.cs new file mode 100644 index 000000000..a9c0bf298 --- /dev/null +++ b/src/StackExchange.Redis/Interfaces/ISubscriber.cs @@ -0,0 +1,120 @@ +using System; +using System.Net; +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + /// + /// A redis connection used as the subscriber in a pub/sub scenario. + /// + public interface ISubscriber : IRedis + { + /// + /// Indicate exactly which redis server we are talking to. + /// + /// The channel to identify the server endpoint by. + /// The command flags to use. + EndPoint? IdentifyEndpoint(RedisChannel channel, CommandFlags flags = CommandFlags.None); + + /// + Task IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None); + + /// + /// Indicates whether the instance can communicate with the server. + /// If a channel is specified, the existing subscription map is queried to + /// resolve the server responsible for that subscription - otherwise the + /// server is chosen arbitrarily from the primaries. + /// + /// The channel to identify the server endpoint by. + /// if connected, otherwise. + bool IsConnected(RedisChannel channel = default); + + /// + /// Posts a message to the given channel. + /// + /// The channel to publish to. + /// The message to publish. + /// The command flags to use. + /// + /// The number of clients that received the message *on the destination server*, + /// note that this doesn't mean much in a cluster as clients can get the message through other nodes. + /// + /// + long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None); + + /// + Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None); + + /// + /// Subscribe to perform some operation when a message to the preferred/active node is broadcast, without any guarantee of ordered handling. + /// + /// The channel to subscribe to. + /// The handler to invoke when a message is received on . + /// The command flags to use. + /// + /// See + /// , + /// . + /// + void Subscribe(RedisChannel channel, Action handler, CommandFlags flags = CommandFlags.None); + + /// + Task SubscribeAsync(RedisChannel channel, Action handler, CommandFlags flags = CommandFlags.None); + + /// + /// Subscribe to perform some operation when a message to the preferred/active node is broadcast, as a queue that guarantees ordered handling. + /// + /// The redis channel to subscribe to. + /// The command flags to use. + /// A channel that represents this source. + /// + /// See + /// , + /// . + /// + ChannelMessageQueue Subscribe(RedisChannel channel, CommandFlags flags = CommandFlags.None); + + /// + Task SubscribeAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None); + + /// + /// Indicate to which redis server we are actively subscribed for a given channel. + /// + /// The channel to check which server endpoint was subscribed on. + /// The subscribed endpoint for the given , if the channel is not actively subscribed. + EndPoint? SubscribedEndpoint(RedisChannel channel); + + /// + /// Unsubscribe from a specified message channel. + /// Note: if no handler is specified, the subscription is canceled regardless of the subscribers. + /// If a handler is specified, the subscription is only canceled if this handler is the last handler remaining against the channel. + /// + /// The channel that was subscribed to. + /// The handler to no longer invoke when a message is received on . + /// The command flags to use. + /// + /// See + /// , + /// . + /// + void Unsubscribe(RedisChannel channel, Action? handler = null, CommandFlags flags = CommandFlags.None); + + /// + Task UnsubscribeAsync(RedisChannel channel, Action? handler = null, CommandFlags flags = CommandFlags.None); + + /// + /// Unsubscribe all subscriptions on this instance. + /// + /// The command flags to use. + /// + /// See + /// , + /// . + /// . + /// + void UnsubscribeAll(CommandFlags flags = CommandFlags.None); + + /// + Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None); + } +} diff --git a/src/StackExchange.Redis/Interfaces/ITransaction.cs b/src/StackExchange.Redis/Interfaces/ITransaction.cs new file mode 100644 index 000000000..21c66968a --- /dev/null +++ b/src/StackExchange.Redis/Interfaces/ITransaction.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + /// + /// Represents a group of operations that will be sent to the server as a single unit, + /// and processed on the server as a single unit. Transactions can also include constraints + /// (implemented via WATCH), but note that constraint checking involves will (very briefly) + /// block the connection, since the transaction cannot be correctly committed (EXEC), + /// aborted (DISCARD) or not applied in the first place (UNWATCH) until the responses from + /// the constraint checks have arrived. + /// + /// + /// Note that on a cluster, it may be required that all keys involved in the transaction (including constraints) are in the same hash-slot. + /// + /// + public interface ITransaction : IBatch + { + /// + /// Adds a precondition for this transaction. + /// + /// The condition to add to the transaction. + ConditionResult AddCondition(Condition condition); + + /// + /// Execute the batch operation, sending all queued commands to the server. + /// + /// The command flags to use. + bool Execute(CommandFlags flags = CommandFlags.None); + + /// + /// Execute the batch operation, sending all queued commands to the server. + /// + /// The command flags to use. + Task ExecuteAsync(CommandFlags flags = CommandFlags.None); + } +} diff --git a/src/StackExchange.Redis/InternalErrorEventArgs.cs b/src/StackExchange.Redis/InternalErrorEventArgs.cs new file mode 100644 index 000000000..f664f4d62 --- /dev/null +++ b/src/StackExchange.Redis/InternalErrorEventArgs.cs @@ -0,0 +1,68 @@ +using System; +using System.Net; +using System.Text; + +namespace StackExchange.Redis +{ + /// + /// Describes internal errors (mainly intended for debugging). + /// + public class InternalErrorEventArgs : EventArgs, ICompletable + { + private readonly EventHandler? handler; + private readonly object sender; + internal InternalErrorEventArgs(EventHandler? handler, object sender, EndPoint? endpoint, ConnectionType connectionType, Exception exception, string? origin) + { + this.handler = handler; + this.sender = sender; + EndPoint = endpoint; + ConnectionType = connectionType; + Exception = exception; + Origin = origin; + } + + /// + /// This constructor is only for testing purposes. + /// + /// The source of the event. + /// The endpoint (if any) involved in the event. + /// Redis connection type. + /// The exception that occurred. + /// Origin. + public InternalErrorEventArgs(object sender, EndPoint endpoint, ConnectionType connectionType, Exception exception, string origin) + : this(null, sender, endpoint, connectionType, exception, origin) + { + } + + /// + /// Gets the connection-type of the failing connection. + /// + public ConnectionType ConnectionType { get; } + + /// + /// Gets the failing server-endpoint (this can be null). + /// + public EndPoint? EndPoint { get; } + + /// + /// Gets the exception if available (this can be null). + /// + public Exception Exception { get; } + + /// + /// The underlying origin of the error. + /// + public string? Origin { get; } + + void ICompletable.AppendStormLog(StringBuilder sb) + { + sb.Append("event, internal-error: ").Append(Origin); + if (EndPoint != null) + { + sb.Append(", ").Append(Format.ToString(EndPoint)); + } + } + + bool ICompletable.TryComplete(bool isAsync) => ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync); + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseExtension.cs b/src/StackExchange.Redis/KeyspaceIsolation/DatabaseExtension.cs similarity index 81% rename from StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseExtension.cs rename to src/StackExchange.Redis/KeyspaceIsolation/DatabaseExtension.cs index 2df4ef95d..742bc06eb 100644 --- a/StackExchange.Redis/StackExchange/Redis/KeyspaceIsolation/DatabaseExtension.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/DatabaseExtension.cs @@ -1,68 +1,67 @@ -using System; - -namespace StackExchange.Redis.KeyspaceIsolation -{ - /// - /// Provides the extension method to . - /// - public static class DatabaseExtensions - { - /// - /// Creates a new instance that provides an isolated key space - /// of the specified underyling database instance. - /// - /// - /// The underlying database instance that the returned instance shall use. - /// - /// - /// The prefix that defines a key space isolation for the returned database instance. - /// - /// - /// A new instance that invokes the specified underlying - /// but prepends the specified - /// to all key paramters and thus forms a logical key space isolation. - /// - /// - /// - /// The following methods are not supported in a key space isolated database and - /// will throw an when invoked: - /// - /// - /// - /// - /// - /// - /// Please notice that keys passed to a script are prefixed (as normal) but care must - /// be taken when a script returns the name of a key as that will (currently) not be - /// "unprefixed". - /// - /// - public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefix) - { - if (database == null) - { - throw new ArgumentNullException(nameof(database)); - } - - if (keyPrefix.IsNull) - { - throw new ArgumentNullException(nameof(keyPrefix)); - } - - if (keyPrefix.IsEmpty) - { - return database; // fine - you can keep using the original, then - } - - if(database is DatabaseWrapper) - { - // combine the key in advance to minimize indirection - var wrapper = (DatabaseWrapper)database; - keyPrefix = wrapper.ToInner(keyPrefix); - database = wrapper.Inner; - } - - return new DatabaseWrapper(database, keyPrefix.AsPrefix()); - } - } -} +using System; + +namespace StackExchange.Redis.KeyspaceIsolation +{ + /// + /// Provides the extension method to . + /// + public static class DatabaseExtensions + { + /// + /// Creates a new instance that provides an isolated key space + /// of the specified underlying database instance. + /// + /// + /// The underlying database instance that the returned instance shall use. + /// + /// + /// The prefix that defines a key space isolation for the returned database instance. + /// + /// + /// A new instance that invokes the specified underlying + /// but prepends the specified + /// to all key parameters and thus forms a logical key space isolation. + /// + /// + /// + /// The following methods are not supported in a key space isolated database and + /// will throw an when invoked: + /// + /// + /// + /// + /// + /// + /// Please notice that keys passed to a script are prefixed (as normal) but care must + /// be taken when a script returns the name of a key as that will (currently) not be + /// "unprefixed". + /// + /// + public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefix) + { + if (database == null) + { + throw new ArgumentNullException(nameof(database)); + } + + if (keyPrefix.IsNull) + { + throw new ArgumentNullException(nameof(keyPrefix)); + } + + if (keyPrefix.IsEmpty) + { + return database; // fine - you can keep using the original, then + } + + if (database is KeyPrefixedDatabase prefixed) + { + // combine the key in advance to minimize indirection + keyPrefix = prefixed.ToInner(keyPrefix); + database = prefixed.Inner; + } + + return new KeyPrefixedDatabase(database, keyPrefix.AsPrefix()!); + } + } +} diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.VectorSets.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.VectorSets.cs new file mode 100644 index 000000000..809adad97 --- /dev/null +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.VectorSets.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +// ReSharper disable once CheckNamespace +namespace StackExchange.Redis.KeyspaceIsolation; + +internal partial class KeyPrefixed +{ + // Vector Set operations - async methods + [Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] + public Task VectorSetAddAsync( + RedisKey key, + VectorSetAddRequest request, + CommandFlags flags = CommandFlags.None) => + Inner.VectorSetAddAsync(ToInner(key), request, flags); + + public Task VectorSetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetLengthAsync(ToInner(key), flags); + + public Task VectorSetDimensionAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetDimensionAsync(ToInner(key), flags); + + public Task?> VectorSetGetApproximateVectorAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetGetApproximateVectorAsync(ToInner(key), member, flags); + + public Task VectorSetGetAttributesJsonAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetGetAttributesJsonAsync(ToInner(key), member, flags); + + public Task VectorSetInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetInfoAsync(ToInner(key), flags); + + public Task VectorSetContainsAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetContainsAsync(ToInner(key), member, flags); + + public Task?> VectorSetGetLinksAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetGetLinksAsync(ToInner(key), member, flags); + + public Task?> VectorSetGetLinksWithScoresAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetGetLinksWithScoresAsync(ToInner(key), member, flags); + + public Task VectorSetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetRandomMemberAsync(ToInner(key), flags); + + public Task VectorSetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetRandomMembersAsync(ToInner(key), count, flags); + + public Task VectorSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetRemoveAsync(ToInner(key), member, flags); + + public Task VectorSetSetAttributesJsonAsync(RedisKey key, RedisValue member, string attributesJson, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetSetAttributesJsonAsync(ToInner(key), member, attributesJson, flags); + + public Task?> VectorSetSimilaritySearchAsync( + RedisKey key, + VectorSetSimilaritySearchRequest query, + CommandFlags flags = CommandFlags.None) => + Inner.VectorSetSimilaritySearchAsync(ToInner(key), query, flags); +} diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs new file mode 100644 index 000000000..61a6f44c4 --- /dev/null +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs @@ -0,0 +1,935 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace StackExchange.Redis.KeyspaceIsolation +{ + internal partial class KeyPrefixed : IDatabaseAsync where TInner : IDatabaseAsync + { + internal KeyPrefixed(TInner inner, byte[] keyPrefix) + { + Inner = inner; + Prefix = keyPrefix; + } + + public IConnectionMultiplexer Multiplexer => Inner.Multiplexer; + + internal TInner Inner { get; } + + internal byte[] Prefix { get; } + + public Task DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.DebugObjectAsync(ToInner(key), flags); + + public Task GeoAddAsync(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.GeoAddAsync(ToInner(key), longitude, latitude, member, flags); + + public Task GeoAddAsync(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None) => + Inner.GeoAddAsync(ToInner(key), value, flags); + + public Task GeoAddAsync(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None) => + Inner.GeoAddAsync(ToInner(key), values, flags); + + public Task GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.GeoRemoveAsync(ToInner(key), member, flags); + + public Task GeoDistanceAsync(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) => + Inner.GeoDistanceAsync(ToInner(key), member1, member2, unit, flags); + + public Task GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) => + Inner.GeoHashAsync(ToInner(key), members, flags); + + public Task GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.GeoHashAsync(ToInner(key), member, flags); + + public Task GeoPositionAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) => + Inner.GeoPositionAsync(ToInner(key), members, flags); + + public Task GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.GeoPositionAsync(ToInner(key), member, flags); + + public Task GeoRadiusAsync(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) => + Inner.GeoRadiusAsync(ToInner(key), member, radius, unit, count, order, options, flags); + + public Task GeoRadiusAsync(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) => + Inner.GeoRadiusAsync(ToInner(key), longitude, latitude, radius, unit, count, order, options, flags); + + public Task GeoSearchAsync(RedisKey key, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) => + Inner.GeoSearchAsync(ToInner(key), member, shape, count, demandClosest, order, options, flags); + + public Task GeoSearchAsync(RedisKey key, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) => + Inner.GeoSearchAsync(ToInner(key), longitude, latitude, shape, count, demandClosest, order, options, flags); + + public Task GeoSearchAndStoreAsync(RedisKey sourceKey, RedisKey destinationKey, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) => + Inner.GeoSearchAndStoreAsync(ToInner(sourceKey), ToInner(destinationKey), member, shape, count, demandClosest, order, storeDistances, flags); + + public Task GeoSearchAndStoreAsync(RedisKey sourceKey, RedisKey destinationKey, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) => + Inner.GeoSearchAndStoreAsync(ToInner(sourceKey), ToInner(destinationKey), longitude, latitude, shape, count, demandClosest, order, storeDistances, flags); + + public Task HashDecrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) => + Inner.HashDecrementAsync(ToInner(key), hashField, value, flags); + + public Task HashDecrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) => + Inner.HashDecrementAsync(ToInner(key), hashField, value, flags); + + public Task HashDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + Inner.HashDeleteAsync(ToInner(key), hashFields, flags); + + public Task HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashDeleteAsync(ToInner(key), hashField, flags); + + public Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashExistsAsync(ToInner(key), hashField, flags); + + public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndDeleteAsync(ToInner(key), hashField, flags); + + public Task?> HashFieldGetLeaseAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndDeleteAsync(ToInner(key), hashField, flags); + + public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndDeleteAsync(ToInner(key), hashFields, flags); + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiryAsync(ToInner(key), hashField, expiry, persist, flags); + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiryAsync(ToInner(key), hashField, expiry, flags); + + public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndSetExpiryAsync(ToInner(key), hashField, expiry, persist, flags); + + public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndSetExpiryAsync(ToInner(key), hashField, expiry, flags); + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiryAsync(ToInner(key), hashFields, expiry, persist, flags); + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiryAsync(ToInner(key), hashFields, expiry, flags); + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiryAsync(ToInner(key), field, value, expiry, keepTtl, when, flags); + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiryAsync(ToInner(key), field, value, expiry, when, flags); + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiryAsync(ToInner(key), hashFields, expiry, keepTtl, when, flags); + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiryAsync(ToInner(key), hashFields, expiry, when, flags); + + public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldExpireAsync(ToInner(key), hashFields, expiry, when, flags); + + public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldExpireAsync(ToInner(key), hashFields, expiry, when, flags); + + public Task HashFieldGetExpireDateTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashFieldGetExpireDateTimeAsync(ToInner(key), hashFields, flags); + + public Task HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashFieldPersistAsync(ToInner(key), hashFields, flags); + + public Task HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashFieldGetTimeToLiveAsync(ToInner(key), hashFields, flags); + + public Task HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.HashGetAllAsync(ToInner(key), flags); + + public Task HashGetAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + Inner.HashGetAsync(ToInner(key), hashFields, flags); + + public Task HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashGetAsync(ToInner(key), hashField, flags); + + public Task?> HashGetLeaseAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashGetLeaseAsync(ToInner(key), hashField, flags); + + public Task HashIncrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) => + Inner.HashIncrementAsync(ToInner(key), hashField, value, flags); + + public Task HashIncrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) => + Inner.HashIncrementAsync(ToInner(key), hashField, value, flags); + + public Task HashKeysAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.HashKeysAsync(ToInner(key), flags); + + public Task HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.HashLengthAsync(ToInner(key), flags); + + public Task HashRandomFieldAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.HashRandomFieldAsync(ToInner(key), flags); + + public Task HashRandomFieldsAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.HashRandomFieldsAsync(ToInner(key), count, flags); + + public Task HashRandomFieldsWithValuesAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.HashRandomFieldsWithValuesAsync(ToInner(key), count, flags); + + public IAsyncEnumerable HashScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) => + Inner.HashScanAsync(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); + + public IAsyncEnumerable HashScanNoValuesAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) => + Inner.HashScanNoValuesAsync(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); + + public Task HashSetAsync(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashSetAsync(ToInner(key), hashField, value, when, flags); + + public Task HashStringLengthAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashStringLengthAsync(ToInner(key), hashField, flags); + + public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None) => + Inner.HashSetAsync(ToInner(key), hashFields, flags); + + public Task HashValuesAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.HashValuesAsync(ToInner(key), flags); + + public Task HyperLogLogAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => + Inner.HyperLogLogAddAsync(ToInner(key), values, flags); + + public Task HyperLogLogAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.HyperLogLogAddAsync(ToInner(key), value, flags); + + public Task HyperLogLogLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.HyperLogLogLengthAsync(ToInner(key), flags); + + public Task HyperLogLogLengthAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.HyperLogLogLengthAsync(ToInner(keys), flags); + + public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) => + Inner.HyperLogLogMergeAsync(ToInner(destination), ToInner(sourceKeys), flags); + + public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) => + Inner.HyperLogLogMergeAsync(ToInner(destination), ToInner(first), ToInner(second), flags); + + public Task IdentifyEndpointAsync(RedisKey key = default(RedisKey), CommandFlags flags = CommandFlags.None) => + Inner.IdentifyEndpointAsync(ToInner(key), flags); + + public bool IsConnected(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.IsConnected(ToInner(key), flags); + + public Task KeyCopyAsync(RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase = -1, bool replace = false, CommandFlags flags = CommandFlags.None) => + Inner.KeyCopyAsync(ToInner(sourceKey), ToInner(destinationKey), destinationDatabase, replace, flags); + + public Task KeyDeleteAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.KeyDeleteAsync(ToInner(keys), flags); + + public Task KeyDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyDeleteAsync(ToInner(key), flags); + + public Task KeyDumpAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyDumpAsync(ToInner(key), flags); + + public Task KeyEncodingAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyEncodingAsync(ToInner(key), flags); + + public Task KeyExistsAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyExistsAsync(ToInner(key), flags); + + public Task KeyExistsAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.KeyExistsAsync(ToInner(keys), flags); + + public Task KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags) => + Inner.KeyExpireAsync(ToInner(key), expiry, flags); + + public Task KeyExpireAsync(RedisKey key, DateTime? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.KeyExpireAsync(ToInner(key), expiry, when, flags); + + public Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags) => + Inner.KeyExpireAsync(ToInner(key), expiry, flags); + + public Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.KeyExpireAsync(ToInner(key), expiry, when, flags); + + public Task KeyExpireTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyExpireTimeAsync(ToInner(key), flags); + + public Task KeyFrequencyAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyFrequencyAsync(ToInner(key), flags); + + public Task KeyIdleTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyIdleTimeAsync(ToInner(key), flags); + + public Task KeyMigrateAsync(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) => + Inner.KeyMigrateAsync(ToInner(key), toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); + + public Task KeyMoveAsync(RedisKey key, int database, CommandFlags flags = CommandFlags.None) => + Inner.KeyMoveAsync(ToInner(key), database, flags); + + public Task KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyPersistAsync(ToInner(key), flags); + + public Task KeyRandomAsync(CommandFlags flags = CommandFlags.None) => + throw new NotSupportedException("RANDOMKEY is not supported when a key-prefix is specified"); + + public Task KeyRefCountAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyRefCountAsync(ToInner(key), flags); + + public Task KeyRenameAsync(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.KeyRenameAsync(ToInner(key), ToInner(newKey), when, flags); + + public Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None) => + Inner.KeyRestoreAsync(ToInner(key), value, expiry, flags); + + public Task KeyTimeToLiveAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyTimeToLiveAsync(ToInner(key), flags); + + public Task KeyTypeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyTypeAsync(ToInner(key), flags); + + public Task ListGetByIndexAsync(RedisKey key, long index, CommandFlags flags = CommandFlags.None) => + Inner.ListGetByIndexAsync(ToInner(key), index, flags); + + public Task ListInsertAfterAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.ListInsertAfterAsync(ToInner(key), pivot, value, flags); + + public Task ListInsertBeforeAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.ListInsertBeforeAsync(ToInner(key), pivot, value, flags); + + public Task ListLeftPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.ListLeftPopAsync(ToInner(key), flags); + + public Task ListLeftPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.ListLeftPopAsync(ToInner(key), count, flags); + + public Task ListLeftPopAsync(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) => + Inner.ListLeftPopAsync(ToInner(keys), count, flags); + + public Task ListPositionAsync(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) => + Inner.ListPositionAsync(ToInner(key), element, rank, maxLength, flags); + + public Task ListPositionsAsync(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) => + Inner.ListPositionsAsync(ToInner(key), element, count, rank, maxLength, flags); + + public Task ListLeftPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => + Inner.ListLeftPushAsync(ToInner(key), values, flags); + + public Task ListLeftPushAsync(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.ListLeftPushAsync(ToInner(key), values, when, flags); + + public Task ListLeftPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.ListLeftPushAsync(ToInner(key), value, when, flags); + + public Task ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.ListLengthAsync(ToInner(key), flags); + + public Task ListMoveAsync(RedisKey sourceKey, RedisKey destinationKey, ListSide sourceSide, ListSide destinationSide, CommandFlags flags = CommandFlags.None) => + Inner.ListMoveAsync(ToInner(sourceKey), ToInner(destinationKey), sourceSide, destinationSide); + + public Task ListRangeAsync(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) => + Inner.ListRangeAsync(ToInner(key), start, stop, flags); + + public Task ListRemoveAsync(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) => + Inner.ListRemoveAsync(ToInner(key), value, count, flags); + + public Task ListRightPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPopAsync(ToInner(key), flags); + + public Task ListRightPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPopAsync(ToInner(key), count, flags); + + public Task ListRightPopAsync(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPopAsync(ToInner(keys), count, flags); + + public Task ListRightPopLeftPushAsync(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPopLeftPushAsync(ToInner(source), ToInner(destination), flags); + + public Task ListRightPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPushAsync(ToInner(key), values, flags); + + public Task ListRightPushAsync(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPushAsync(ToInner(key), values, when, flags); + + public Task ListRightPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPushAsync(ToInner(key), value, when, flags); + + public Task ListSetByIndexAsync(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.ListSetByIndexAsync(ToInner(key), index, value, flags); + + public Task ListTrimAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) => + Inner.ListTrimAsync(ToInner(key), start, stop, flags); + + public Task LockExtendAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) => + Inner.LockExtendAsync(ToInner(key), value, expiry, flags); + + public Task LockQueryAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.LockQueryAsync(ToInner(key), flags); + + public Task LockReleaseAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.LockReleaseAsync(ToInner(key), value, flags); + + public Task LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) => + Inner.LockTakeAsync(ToInner(key), value, expiry, flags); + + public Task StringLongestCommonSubsequenceAsync(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) => + Inner.StringLongestCommonSubsequenceAsync(ToInner(first), ToInner(second), flags); + + public Task StringLongestCommonSubsequenceLengthAsync(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) => + Inner.StringLongestCommonSubsequenceLengthAsync(ToInner(first), ToInner(second), flags); + + public Task StringLongestCommonSubsequenceWithMatchesAsync(RedisKey first, RedisKey second, long minLength = 0, CommandFlags flags = CommandFlags.None) => + Inner.StringLongestCommonSubsequenceWithMatchesAsync(ToInner(first), ToInner(second), minLength, flags); + + public Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) => + Inner.PublishAsync(ToInner(channel), message, flags); + + public Task ExecuteAsync(string command, params object[] args) => + Inner.ExecuteAsync(command, ToInner(args), CommandFlags.None); + + public Task ExecuteAsync(string command, ICollection? args, CommandFlags flags = CommandFlags.None) => + Inner.ExecuteAsync(command, ToInner(args), flags); + + public Task ScriptEvaluateAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) => + // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? + Inner.ScriptEvaluateAsync(hash, ToInner(keys), values, flags); + + public Task ScriptEvaluateAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) => + // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? + Inner.ScriptEvaluateAsync(script: script, keys: ToInner(keys), values: values, flags: flags); + + public Task ScriptEvaluateAsync(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) => + // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? + script.EvaluateAsync(Inner, parameters, Prefix, flags); + + public Task ScriptEvaluateAsync(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) => + // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? + script.EvaluateAsync(Inner, parameters, Prefix, flags); + + public Task ScriptEvaluateReadOnlyAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) => + // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? + Inner.ScriptEvaluateAsync(hash, ToInner(keys), values, flags); + + public Task ScriptEvaluateReadOnlyAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) => + // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? + Inner.ScriptEvaluateAsync(script: script, keys: ToInner(keys), values: values, flags: flags); + + public Task SetAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => + Inner.SetAddAsync(ToInner(key), values, flags); + + public Task SetAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.SetAddAsync(ToInner(key), value, flags); + + public Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.SetCombineAndStoreAsync(operation, ToInner(destination), ToInner(keys), flags); + + public Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) => + Inner.SetCombineAndStoreAsync(operation, ToInner(destination), ToInner(first), ToInner(second), flags); + + public Task SetCombineAsync(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.SetCombineAsync(operation, ToInner(keys), flags); + + public Task SetCombineAsync(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) => + Inner.SetCombineAsync(operation, ToInner(first), ToInner(second), flags); + + public Task SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.SetContainsAsync(ToInner(key), value, flags); + + public Task SetContainsAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => + Inner.SetContainsAsync(ToInner(key), values, flags); + + public Task SetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) => + Inner.SetIntersectionLengthAsync(ToInner(keys), limit, flags); + + public Task SetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.SetLengthAsync(ToInner(key), flags); + + public Task SetMembersAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.SetMembersAsync(ToInner(key), flags); + + public Task SetMoveAsync(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.SetMoveAsync(ToInner(source), ToInner(destination), value, flags); + + public Task SetPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.SetPopAsync(ToInner(key), flags); + + public Task SetPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.SetPopAsync(ToInner(key), count, flags); + + public Task SetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.SetRandomMemberAsync(ToInner(key), flags); + + public Task SetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.SetRandomMembersAsync(ToInner(key), count, flags); + + public Task SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => + Inner.SetRemoveAsync(ToInner(key), values, flags); + + public IAsyncEnumerable SetScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) => + Inner.SetScanAsync(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); + + public Task SetRemoveAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.SetRemoveAsync(ToInner(key), value, flags); + + public Task SortAndStoreAsync(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None) => + Inner.SortAndStoreAsync(ToInner(destination), ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags); + + public Task SortAsync(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None) => + Inner.SortAsync(ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags); + + public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, CommandFlags flags) => + Inner.SortedSetAddAsync(ToInner(key), values, flags); + + public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetAddAsync(ToInner(key), values, when, flags); + + public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, SortedSetWhen updateWhen = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetAddAsync(ToInner(key), values, updateWhen, flags); + + public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, CommandFlags flags) => + Inner.SortedSetAddAsync(ToInner(key), member, score, flags); + + public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetAddAsync(ToInner(key), member, score, when, flags); + + public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, SortedSetWhen updateWhen = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetAddAsync(ToInner(key), member, score, updateWhen, flags); + public Task SortedSetCombineAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetCombineAsync(operation, ToInner(keys), weights, aggregate, flags); + + public Task SortedSetCombineWithScoresAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetCombineWithScoresAsync(operation, ToInner(keys), weights, aggregate, flags); + + public Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetCombineAndStoreAsync(operation, ToInner(destination), ToInner(keys), weights, aggregate, flags); + + public Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetCombineAndStoreAsync(operation, ToInner(destination), ToInner(first), ToInner(second), aggregate, flags); + + public Task SortedSetDecrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetDecrementAsync(ToInner(key), member, value, flags); + + public Task SortedSetIncrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetIncrementAsync(ToInner(key), member, value, flags); + + public Task SortedSetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetIntersectionLengthAsync(ToInner(keys), limit, flags); + + public Task SortedSetLengthAsync(RedisKey key, double min = -1.0 / 0.0, double max = 1.0 / 0.0, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetLengthAsync(ToInner(key), min, max, exclude, flags); + + public Task SortedSetLengthByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetLengthByValueAsync(ToInner(key), min, max, exclude, flags); + + public Task SortedSetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRandomMemberAsync(ToInner(key), flags); + + public Task SortedSetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRandomMembersAsync(ToInner(key), count, flags); + + public Task SortedSetRandomMembersWithScoresAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRandomMembersWithScoresAsync(ToInner(key), count, flags); + + public Task SortedSetRangeAndStoreAsync( + RedisKey sourceKey, + RedisKey destinationKey, + RedisValue start, + RedisValue stop, + SortedSetOrder sortedSetOrder = SortedSetOrder.ByRank, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long? take = null, + CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRangeAndStoreAsync(ToInner(sourceKey), ToInner(destinationKey), start, stop, sortedSetOrder, exclude, order, skip, take, flags); + + public Task SortedSetRangeByRankAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRangeByRankAsync(ToInner(key), start, stop, order, flags); + + public Task SortedSetRangeByRankWithScoresAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRangeByRankWithScoresAsync(ToInner(key), start, stop, order, flags); + + public Task SortedSetRangeByScoreAsync(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRangeByScoreAsync(ToInner(key), start, stop, exclude, order, skip, take, flags); + + public Task SortedSetRangeByScoreWithScoresAsync(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRangeByScoreWithScoresAsync(ToInner(key), start, stop, exclude, order, skip, take, flags); + + public Task SortedSetRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude, long skip, long take, CommandFlags flags) => + Inner.SortedSetRangeByValueAsync(ToInner(key), min, max, exclude, Order.Ascending, skip, take, flags); + + public Task SortedSetRangeByValueAsync(RedisKey key, RedisValue min = default, RedisValue max = default, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRangeByValueAsync(ToInner(key), min, max, exclude, order, skip, take, flags); + + public Task SortedSetRankAsync(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRankAsync(ToInner(key), member, order, flags); + + public Task SortedSetRemoveAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRemoveAsync(ToInner(key), members, flags); + + public Task SortedSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRemoveAsync(ToInner(key), member, flags); + + public Task SortedSetRemoveRangeByRankAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRemoveRangeByRankAsync(ToInner(key), start, stop, flags); + + public Task SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRemoveRangeByScoreAsync(ToInner(key), start, stop, exclude, flags); + + public Task SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRemoveRangeByValueAsync(ToInner(key), min, max, exclude, flags); + + public Task SortedSetScoreAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetScoreAsync(ToInner(key), member, flags); + + public Task SortedSetScoresAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetScoresAsync(ToInner(key), members, flags); + + public IAsyncEnumerable SortedSetScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) => + Inner.SortedSetScanAsync(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); + + public Task SortedSetUpdateAsync(RedisKey key, SortedSetEntry[] values, SortedSetWhen updateWhen = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetUpdateAsync(ToInner(key), values, updateWhen, flags); + + public Task SortedSetUpdateAsync(RedisKey key, RedisValue member, double score, SortedSetWhen updateWhen = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetUpdateAsync(ToInner(key), member, score, updateWhen, flags); + + public Task SortedSetPopAsync(RedisKey key, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetPopAsync(ToInner(key), order, flags); + + public Task SortedSetPopAsync(RedisKey key, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetPopAsync(ToInner(key), count, order, flags); + + public Task SortedSetPopAsync(RedisKey[] keys, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetPopAsync(ToInner(keys), count, order, flags); + + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None) => + Inner.StreamAcknowledgeAsync(ToInner(key), groupName, messageId, flags); + + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) => + Inner.StreamAcknowledgeAsync(ToInner(key), groupName, messageIds, flags); + + public Task StreamAcknowledgeAndDeleteAsync(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue messageId, CommandFlags flags = CommandFlags.None) => + Inner.StreamAcknowledgeAndDeleteAsync(ToInner(key), groupName, mode, messageId, flags); + + public Task StreamAcknowledgeAndDeleteAsync(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) => + Inner.StreamAcknowledgeAndDeleteAsync(ToInner(key), groupName, mode, messageIds, flags); + + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) => + Inner.StreamAddAsync(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, flags); + + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) => + Inner.StreamAddAsync(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, flags); + + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamAddAsync(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, limit, mode, flags); + + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamAddAsync(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, limit, mode, flags); + + public Task StreamAutoClaimAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) => + Inner.StreamAutoClaimAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, flags); + + public Task StreamAutoClaimIdsOnlyAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) => + Inner.StreamAutoClaimIdsOnlyAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, flags); + + public Task StreamClaimAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) => + Inner.StreamClaimAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + + public Task StreamClaimIdsOnlyAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) => + Inner.StreamClaimIdsOnlyAsync(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + + public Task StreamConsumerGroupSetPositionAsync(RedisKey key, RedisValue groupName, RedisValue position, CommandFlags flags = CommandFlags.None) => + Inner.StreamConsumerGroupSetPositionAsync(ToInner(key), groupName, position, flags); + + public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue? position, CommandFlags flags) => + Inner.StreamCreateConsumerGroupAsync(ToInner(key), groupName, position, flags); + + public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue? position = null, bool createStream = true, CommandFlags flags = CommandFlags.None) => + Inner.StreamCreateConsumerGroupAsync(ToInner(key), groupName, position, createStream, flags); + + public Task StreamInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StreamInfoAsync(ToInner(key), flags); + + public Task StreamGroupInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StreamGroupInfoAsync(ToInner(key), flags); + + public Task StreamConsumerInfoAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) => + Inner.StreamConsumerInfoAsync(ToInner(key), groupName, flags); + + public Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StreamLengthAsync(ToInner(key), flags); + + public Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) => + Inner.StreamDeleteAsync(ToInner(key), messageIds, flags); + + public Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, StreamTrimMode mode, CommandFlags flags = CommandFlags.None) => + Inner.StreamDeleteAsync(ToInner(key), messageIds, mode, flags); + + public Task StreamDeleteConsumerAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, CommandFlags flags = CommandFlags.None) => + Inner.StreamDeleteConsumerAsync(ToInner(key), groupName, consumerName, flags); + + public Task StreamDeleteConsumerGroupAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) => + Inner.StreamDeleteConsumerGroupAsync(ToInner(key), groupName, flags); + + public Task StreamPendingAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) => + Inner.StreamPendingAsync(ToInner(key), groupName, flags); + + public Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId, RedisValue? maxId, CommandFlags flags) => + Inner.StreamPendingMessagesAsync(ToInner(key), groupName, count, consumerName, minId, maxId, flags); + + public Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, long? minIdleTimeInMs = null, CommandFlags flags = CommandFlags.None) => + Inner.StreamPendingMessagesAsync(ToInner(key), groupName, count, consumerName, minId, maxId, minIdleTimeInMs, flags); + + public Task StreamRangeAsync(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.StreamRangeAsync(ToInner(key), minId, maxId, count, messageOrder, flags); + + public Task StreamReadAsync(RedisKey key, RedisValue position, int? count = null, CommandFlags flags = CommandFlags.None) => + Inner.StreamReadAsync(ToInner(key), position, count, flags); + + public Task StreamReadAsync(StreamPosition[] streamPositions, int? countPerStream = null, CommandFlags flags = CommandFlags.None) => + Inner.StreamReadAsync(streamPositions, countPerStream, flags); + + public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position, int? count, CommandFlags flags) => + Inner.StreamReadGroupAsync(ToInner(key), groupName, consumerName, position, count, flags); + + public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position = null, int? count = null, bool noAck = false, CommandFlags flags = CommandFlags.None) => + Inner.StreamReadGroupAsync(ToInner(key), groupName, consumerName, position, count, noAck, flags); + + public Task StreamReadGroupAsync(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream, CommandFlags flags) => + Inner.StreamReadGroupAsync(streamPositions, groupName, consumerName, countPerStream, flags); + + public Task StreamReadGroupAsync(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream = null, bool noAck = false, CommandFlags flags = CommandFlags.None) => + Inner.StreamReadGroupAsync(streamPositions, groupName, consumerName, countPerStream, noAck, flags); + + public Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength, CommandFlags flags) => + Inner.StreamTrimAsync(ToInner(key), maxLength, useApproximateMaxLength, flags); + + public Task StreamTrimAsync(RedisKey key, long maxLength, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamTrimAsync(ToInner(key), maxLength, useApproximateMaxLength, limit, mode, flags); + + public Task StreamTrimByMinIdAsync(RedisKey key, RedisValue minId, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamTrimByMinIdAsync(ToInner(key), minId, useApproximateMaxLength, limit, mode, flags); + + public Task StringAppendAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.StringAppendAsync(ToInner(key), value, flags); + + public Task StringBitCountAsync(RedisKey key, long start, long end, CommandFlags flags) => + Inner.StringBitCountAsync(ToInner(key), start, end, flags); + + public Task StringBitCountAsync(RedisKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) => + Inner.StringBitCountAsync(ToInner(key), start, end, indexType, flags); + + public Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.StringBitOperationAsync(operation, ToInner(destination), ToInner(keys), flags); + + public Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default, CommandFlags flags = CommandFlags.None) => + Inner.StringBitOperationAsync(operation, ToInner(destination), ToInner(first), ToInnerOrDefault(second), flags); + + public Task StringBitPositionAsync(RedisKey key, bool bit, long start, long end, CommandFlags flags) => + Inner.StringBitPositionAsync(ToInner(key), bit, start, end, flags); + + public Task StringBitPositionAsync(RedisKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) => + Inner.StringBitPositionAsync(ToInner(key), bit, start, end, indexType, flags); + + public Task StringDecrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) => + Inner.StringDecrementAsync(ToInner(key), value, flags); + + public Task StringDecrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) => + Inner.StringDecrementAsync(ToInner(key), value, flags); + + public Task StringGetAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.StringGetAsync(ToInner(keys), flags); + + public Task StringGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StringGetAsync(ToInner(key), flags); + + public Task StringGetSetExpiryAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) => + Inner.StringGetSetExpiryAsync(ToInner(key), expiry, flags); + + public Task StringGetSetExpiryAsync(RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.StringGetSetExpiryAsync(ToInner(key), expiry, flags); + + public Task?> StringGetLeaseAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StringGetLeaseAsync(ToInner(key), flags); + + public Task StringGetBitAsync(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) => + Inner.StringGetBitAsync(ToInner(key), offset, flags); + + public Task StringGetRangeAsync(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) => + Inner.StringGetRangeAsync(ToInner(key), start, end, flags); + + public Task StringGetSetAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.StringGetSetAsync(ToInner(key), value, flags); + + public Task StringGetDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StringGetDeleteAsync(ToInner(key), flags); + + public Task StringGetWithExpiryAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StringGetWithExpiryAsync(ToInner(key), flags); + + public Task StringIncrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) => + Inner.StringIncrementAsync(ToInner(key), value, flags); + + public Task StringIncrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) => + Inner.StringIncrementAsync(ToInner(key), value, flags); + + public Task StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StringLengthAsync(ToInner(key), flags); + + public Task StringSetAsync(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.StringSetAsync(ToInner(values), when, flags); + + public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when) => + Inner.StringSetAsync(ToInner(key), value, expiry, when); + public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags) => + Inner.StringSetAsync(ToInner(key), value, expiry, when, flags); + public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.StringSetAsync(ToInner(key), value, expiry, keepTtl, when, flags); + + public Task StringSetAndGetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags) => + Inner.StringSetAndGetAsync(ToInner(key), value, expiry, when, flags); + + public Task StringSetAndGetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.StringSetAndGetAsync(ToInner(key), value, expiry, keepTtl, when, flags); + + public Task StringSetBitAsync(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None) => + Inner.StringSetBitAsync(ToInner(key), offset, bit, flags); + + public Task StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.StringSetRangeAsync(ToInner(key), offset, value, flags); + + public Task PingAsync(CommandFlags flags = CommandFlags.None) => + Inner.PingAsync(flags); + + public Task KeyTouchAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.KeyTouchAsync(ToInner(keys), flags); + + public Task KeyTouchAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyTouchAsync(ToInner(key), flags); + + public bool TryWait(Task task) => + Inner.TryWait(task); + + public TResult Wait(Task task) => + Inner.Wait(task); + + public void Wait(Task task) => + Inner.Wait(task); + + public void WaitAll(params Task[] tasks) => + Inner.WaitAll(tasks); + + protected internal RedisKey ToInner(RedisKey outer) => + RedisKey.WithPrefix(Prefix, outer); + + protected RedisKey ToInnerOrDefault(RedisKey outer) => + (outer == default(RedisKey)) ? outer : ToInner(outer); + + [return: NotNullIfNotNull("args")] + protected ICollection? ToInner(ICollection? args) + { + if (args?.Any(x => x is RedisKey || x is RedisChannel) == true) + { + var withPrefix = new object[args.Count]; + int i = 0; + foreach (var oldArg in args) + { + object newArg; + if (oldArg is RedisKey key) + { + newArg = ToInner(key); + } + else if (oldArg is RedisChannel channel) + { + newArg = ToInner(channel); + } + else + { + newArg = oldArg; + } + withPrefix[i++] = newArg; + } + args = withPrefix; + } + return args; + } + + [return: NotNullIfNotNull("outer")] + protected RedisKey[]? ToInner(RedisKey[]? outer) + { + if (outer == null || outer.Length == 0) + { + return outer; + } + else + { + RedisKey[] inner = new RedisKey[outer.Length]; + + for (int i = 0; i < outer.Length; ++i) + { + inner[i] = ToInner(outer[i]); + } + + return inner; + } + } + + protected KeyValuePair ToInner(KeyValuePair outer) => + new KeyValuePair(ToInner(outer.Key), outer.Value); + + [return: NotNullIfNotNull("outer")] + protected KeyValuePair[]? ToInner(KeyValuePair[]? outer) + { + if (outer == null || outer.Length == 0) + { + return outer; + } + else + { + KeyValuePair[] inner = new KeyValuePair[outer.Length]; + + for (int i = 0; i < outer.Length; ++i) + { + inner[i] = ToInner(outer[i]); + } + + return inner; + } + } + + protected RedisValue ToInner(RedisValue outer) => + RedisKey.ConcatenateBytes(Prefix, null, (byte[]?)outer); + + protected RedisValue SortByToInner(RedisValue outer) => + (outer == "nosort") ? outer : ToInner(outer); + + protected RedisValue SortGetToInner(RedisValue outer) => + (outer == "#") ? outer : ToInner(outer); + + [return: NotNullIfNotNull("outer")] + protected RedisValue[]? SortGetToInner(RedisValue[]? outer) + { + if (outer == null || outer.Length == 0) + { + return outer; + } + else + { + RedisValue[] inner = new RedisValue[outer.Length]; + + for (int i = 0; i < outer.Length; ++i) + { + inner[i] = SortGetToInner(outer[i]); + } + + return inner; + } + } + + protected RedisChannel ToInner(RedisChannel outer) + { + var combined = RedisKey.ConcatenateBytes(Prefix, null, (byte[]?)outer); + return new RedisChannel(combined, outer.IsPattern ? RedisChannel.PatternMode.Pattern : RedisChannel.PatternMode.Literal); + } + + private Func? mapFunction; + protected Func GetMapFunction() => + // create as a delegate when first required, then re-use + mapFunction ??= new Func(ToInner); + } +} diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedBatch.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedBatch.cs new file mode 100644 index 000000000..6f5679a66 --- /dev/null +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedBatch.cs @@ -0,0 +1,9 @@ +namespace StackExchange.Redis.KeyspaceIsolation +{ + internal sealed class KeyPrefixedBatch : KeyPrefixed, IBatch + { + public KeyPrefixedBatch(IBatch inner, byte[] prefix) : base(inner, prefix) { } + + public void Execute() => Inner.Execute(); + } +} diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.VectorSets.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.VectorSets.cs new file mode 100644 index 000000000..62f4e9202 --- /dev/null +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.VectorSets.cs @@ -0,0 +1,56 @@ +using System; + +// ReSharper disable once CheckNamespace +namespace StackExchange.Redis.KeyspaceIsolation; + +internal sealed partial class KeyPrefixedDatabase +{ + // Vector Set operations + public bool VectorSetAdd( + RedisKey key, + VectorSetAddRequest request, + CommandFlags flags = CommandFlags.None) => + Inner.VectorSetAdd(ToInner(key), request, flags); + + public long VectorSetLength(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetLength(ToInner(key), flags); + + public int VectorSetDimension(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetDimension(ToInner(key), flags); + + public Lease? VectorSetGetApproximateVector(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetGetApproximateVector(ToInner(key), member, flags); + + public string? VectorSetGetAttributesJson(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetGetAttributesJson(ToInner(key), member, flags); + + public VectorSetInfo? VectorSetInfo(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetInfo(ToInner(key), flags); + + public bool VectorSetContains(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetContains(ToInner(key), member, flags); + + public Lease? VectorSetGetLinks(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetGetLinks(ToInner(key), member, flags); + + public Lease? VectorSetGetLinksWithScores(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetGetLinksWithScores(ToInner(key), member, flags); + + public RedisValue VectorSetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetRandomMember(ToInner(key), flags); + + public RedisValue[] VectorSetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetRandomMembers(ToInner(key), count, flags); + + public bool VectorSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetRemove(ToInner(key), member, flags); + + public bool VectorSetSetAttributesJson(RedisKey key, RedisValue member, string attributesJson, CommandFlags flags = CommandFlags.None) => + Inner.VectorSetSetAttributesJson(ToInner(key), member, attributesJson, flags); + + public Lease? VectorSetSimilaritySearch( + RedisKey key, + VectorSetSimilaritySearchRequest query, + CommandFlags flags = CommandFlags.None) => + Inner.VectorSetSimilaritySearch(ToInner(key), query, flags); +} diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs new file mode 100644 index 000000000..2a139694e --- /dev/null +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs @@ -0,0 +1,808 @@ +using System; +using System.Collections.Generic; +using System.Net; + +namespace StackExchange.Redis.KeyspaceIsolation +{ + internal sealed partial class KeyPrefixedDatabase : KeyPrefixed, IDatabase + { + public KeyPrefixedDatabase(IDatabase inner, byte[] prefix) : base(inner, prefix) + { + } + + public IBatch CreateBatch(object? asyncState = null) => + new KeyPrefixedBatch(Inner.CreateBatch(asyncState), Prefix); + + public ITransaction CreateTransaction(object? asyncState = null) => + new KeyPrefixedTransaction(Inner.CreateTransaction(asyncState), Prefix); + + public int Database => Inner.Database; + + public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.DebugObject(ToInner(key), flags); + + public bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.GeoAdd(ToInner(key), longitude, latitude, member, flags); + + public long GeoAdd(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None) => + Inner.GeoAdd(ToInner(key), values, flags); + + public bool GeoAdd(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None) => + Inner.GeoAdd(ToInner(key), value, flags); + + public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.GeoRemove(ToInner(key), member, flags); + + public double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) => + Inner.GeoDistance(ToInner(key), member1, member2, unit, flags); + + public string?[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) => + Inner.GeoHash(ToInner(key), members, flags); + + public string? GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.GeoHash(ToInner(key), member, flags); + + public GeoPosition?[] GeoPosition(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) => + Inner.GeoPosition(ToInner(key), members, flags); + + public GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.GeoPosition(ToInner(key), member, flags); + + public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) => + Inner.GeoRadius(ToInner(key), member, radius, unit, count, order, options, flags); + + public GeoRadiusResult[] GeoRadius(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) => + Inner.GeoRadius(ToInner(key), longitude, latitude, radius, unit, count, order, options, flags); + + public GeoRadiusResult[] GeoSearch(RedisKey key, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) => + Inner.GeoSearch(ToInner(key), member, shape, count, demandClosest, order, options, flags); + + public GeoRadiusResult[] GeoSearch(RedisKey key, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) => + Inner.GeoSearch(ToInner(key), longitude, latitude, shape, count, demandClosest, order, options, flags); + + public long GeoSearchAndStore(RedisKey sourceKey, RedisKey destinationKey, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) => + Inner.GeoSearchAndStore(ToInner(sourceKey), ToInner(destinationKey), member, shape, count, demandClosest, order, storeDistances, flags); + + public long GeoSearchAndStore(RedisKey sourceKey, RedisKey destinationKey, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) => + Inner.GeoSearchAndStore(ToInner(sourceKey), ToInner(destinationKey), longitude, latitude, shape, count, demandClosest, order, storeDistances, flags); + + public double HashDecrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) => + Inner.HashDecrement(ToInner(key), hashField, value, flags); + + public long HashDecrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) => + Inner.HashDecrement(ToInner(key), hashField, value, flags); + + public long HashDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + Inner.HashDelete(ToInner(key), hashFields, flags); + + public bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashDelete(ToInner(key), hashField, flags); + + public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashExists(ToInner(key), hashField, flags); + + public RedisValue HashFieldGetAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndDelete(ToInner(key), hashField, flags); + + public Lease? HashFieldGetLeaseAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndDelete(ToInner(key), hashField, flags); + + public RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndDelete(ToInner(key), hashFields, flags); + + public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiry(ToInner(key), hashField, expiry, persist, flags); + + public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiry(ToInner(key), hashField, expiry, flags); + + public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndSetExpiry(ToInner(key), hashField, expiry, persist, flags); + + public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetLeaseAndSetExpiry(ToInner(key), hashField, expiry, flags); + + public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiry(ToInner(key), hashFields, expiry, persist, flags); + + public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldGetAndSetExpiry(ToInner(key), hashFields, expiry, flags); + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiry(ToInner(key), field, value, expiry, keepTtl, when, flags); + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiry(ToInner(key), field, value, expiry, when, flags); + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiry(ToInner(key), hashFields, expiry, keepTtl, when, flags); + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldSetAndSetExpiry(ToInner(key), hashFields, expiry, when, flags); + + public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldExpire(ToInner(key), hashFields, expiry, when, flags); + + public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashFieldExpire(ToInner(key), hashFields, expiry, when, flags); + + public long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashFieldGetExpireDateTime(ToInner(key), hashFields, flags); + + public PersistResult[] HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashFieldPersist(ToInner(key), hashFields, flags); + + public long[] HashFieldGetTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashFieldGetTimeToLive(ToInner(key), hashFields, flags); + + public HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.HashGetAll(ToInner(key), flags); + + public RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + Inner.HashGet(ToInner(key), hashFields, flags); + + public RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashGet(ToInner(key), hashField, flags); + + public Lease? HashGetLease(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashGetLease(ToInner(key), hashField, flags); + + public double HashIncrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) => + Inner.HashIncrement(ToInner(key), hashField, value, flags); + + public long HashIncrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) => + Inner.HashIncrement(ToInner(key), hashField, value, flags); + + public RedisValue[] HashKeys(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.HashKeys(ToInner(key), flags); + + public long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.HashLength(ToInner(key), flags); + + public RedisValue HashRandomField(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.HashRandomField(ToInner(key), flags); + + public RedisValue[] HashRandomFields(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.HashRandomFields(ToInner(key), count, flags); + + public HashEntry[] HashRandomFieldsWithValues(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.HashRandomFieldsWithValues(ToInner(key), count, flags); + + public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.HashSet(ToInner(key), hashField, value, when, flags); + + public long HashStringLength(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => + Inner.HashStringLength(ToInner(key), hashField, flags); + + public void HashSet(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None) => + Inner.HashSet(ToInner(key), hashFields, flags); + + public RedisValue[] HashValues(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.HashValues(ToInner(key), flags); + + public bool HyperLogLogAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => + Inner.HyperLogLogAdd(ToInner(key), values, flags); + + public bool HyperLogLogAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.HyperLogLogAdd(ToInner(key), value, flags); + + public long HyperLogLogLength(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.HyperLogLogLength(ToInner(key), flags); + + public long HyperLogLogLength(RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.HyperLogLogLength(ToInner(keys), flags); + + public void HyperLogLogMerge(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) => + Inner.HyperLogLogMerge(ToInner(destination), ToInner(sourceKeys), flags); + + public void HyperLogLogMerge(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) => + Inner.HyperLogLogMerge(ToInner(destination), ToInner(first), ToInner(second), flags); + + public EndPoint? IdentifyEndpoint(RedisKey key = default, CommandFlags flags = CommandFlags.None) => + Inner.IdentifyEndpoint(ToInner(key), flags); + + public bool KeyCopy(RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase = -1, bool replace = false, CommandFlags flags = CommandFlags.None) => + Inner.KeyCopy(ToInner(sourceKey), ToInner(destinationKey), destinationDatabase, replace, flags); + + public long KeyDelete(RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.KeyDelete(ToInner(keys), flags); + + public bool KeyDelete(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyDelete(ToInner(key), flags); + + public byte[]? KeyDump(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyDump(ToInner(key), flags); + + public string? KeyEncoding(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyEncoding(ToInner(key), flags); + + public bool KeyExists(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyExists(ToInner(key), flags); + public long KeyExists(RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.KeyExists(ToInner(keys), flags); + + public bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags) => + Inner.KeyExpire(ToInner(key), expiry, flags); + + public bool KeyExpire(RedisKey key, DateTime? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.KeyExpire(ToInner(key), expiry, when, flags); + + public bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags) => + Inner.KeyExpire(ToInner(key), expiry, flags); + + public bool KeyExpire(RedisKey key, TimeSpan? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.KeyExpire(ToInner(key), expiry, when, flags); + + public DateTime? KeyExpireTime(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyExpireTime(ToInner(key), flags); + + public long? KeyFrequency(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyFrequency(ToInner(key), flags); + + public TimeSpan? KeyIdleTime(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyIdleTime(ToInner(key), flags); + + public void KeyMigrate(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) => + Inner.KeyMigrate(ToInner(key), toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); + + public bool KeyMove(RedisKey key, int database, CommandFlags flags = CommandFlags.None) => + Inner.KeyMove(ToInner(key), database, flags); + + public bool KeyPersist(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyPersist(ToInner(key), flags); + + public RedisKey KeyRandom(CommandFlags flags = CommandFlags.None) => + throw new NotSupportedException("RANDOMKEY is not supported when a key-prefix is specified"); + + public long? KeyRefCount(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyRefCount(ToInner(key), flags); + + public bool KeyRename(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.KeyRename(ToInner(key), ToInner(newKey), when, flags); + + public void KeyRestore(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None) => + Inner.KeyRestore(ToInner(key), value, expiry, flags); + + public TimeSpan? KeyTimeToLive(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyTimeToLive(ToInner(key), flags); + + public RedisType KeyType(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyType(ToInner(key), flags); + + public RedisValue ListGetByIndex(RedisKey key, long index, CommandFlags flags = CommandFlags.None) => + Inner.ListGetByIndex(ToInner(key), index, flags); + + public long ListInsertAfter(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.ListInsertAfter(ToInner(key), pivot, value, flags); + + public long ListInsertBefore(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.ListInsertBefore(ToInner(key), pivot, value, flags); + + public RedisValue ListLeftPop(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.ListLeftPop(ToInner(key), flags); + + public RedisValue[] ListLeftPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.ListLeftPop(ToInner(key), count, flags); + + public ListPopResult ListLeftPop(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) => + Inner.ListLeftPop(ToInner(keys), count, flags); + + public long ListPosition(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) => + Inner.ListPosition(ToInner(key), element, rank, maxLength, flags); + + public long[] ListPositions(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) => + Inner.ListPositions(ToInner(key), element, count, rank, maxLength, flags); + + public long ListLeftPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => + Inner.ListLeftPush(ToInner(key), values, flags); + + public long ListLeftPush(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.ListLeftPush(ToInner(key), values, when, flags); + + public long ListLeftPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.ListLeftPush(ToInner(key), value, when, flags); + + public long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.ListLength(ToInner(key), flags); + + public RedisValue ListMove(RedisKey sourceKey, RedisKey destinationKey, ListSide sourceSide, ListSide destinationSide, CommandFlags flags = CommandFlags.None) => + Inner.ListMove(ToInner(sourceKey), ToInner(destinationKey), sourceSide, destinationSide); + + public RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) => + Inner.ListRange(ToInner(key), start, stop, flags); + + public long ListRemove(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) => + Inner.ListRemove(ToInner(key), value, count, flags); + + public RedisValue ListRightPop(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPop(ToInner(key), flags); + + public RedisValue[] ListRightPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPop(ToInner(key), count, flags); + + public ListPopResult ListRightPop(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPop(ToInner(keys), count, flags); + + public RedisValue ListRightPopLeftPush(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPopLeftPush(ToInner(source), ToInner(destination), flags); + + public long ListRightPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPush(ToInner(key), values, flags); + + public long ListRightPush(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPush(ToInner(key), values, when, flags); + + public long ListRightPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.ListRightPush(ToInner(key), value, when, flags); + + public void ListSetByIndex(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.ListSetByIndex(ToInner(key), index, value, flags); + + public void ListTrim(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) => + Inner.ListTrim(ToInner(key), start, stop, flags); + + public bool LockExtend(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) => + Inner.LockExtend(ToInner(key), value, expiry, flags); + + public RedisValue LockQuery(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.LockQuery(ToInner(key), flags); + + public bool LockRelease(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.LockRelease(ToInner(key), value, flags); + + public bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) => + Inner.LockTake(ToInner(key), value, expiry, flags); + + public string? StringLongestCommonSubsequence(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) => + Inner.StringLongestCommonSubsequence(ToInner(first), ToInner(second), flags); + + public long StringLongestCommonSubsequenceLength(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) => + Inner.StringLongestCommonSubsequenceLength(ToInner(first), ToInner(second), flags); + + public LCSMatchResult StringLongestCommonSubsequenceWithMatches(RedisKey first, RedisKey second, long minLength = 0, CommandFlags flags = CommandFlags.None) => + Inner.StringLongestCommonSubsequenceWithMatches(ToInner(first), ToInner(second), minLength, flags); + + public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) => + Inner.Publish(ToInner(channel), message, flags); + + public RedisResult Execute(string command, params object[] args) + => Inner.Execute(command, ToInner(args), CommandFlags.None); + + public RedisResult Execute(string command, ICollection args, CommandFlags flags = CommandFlags.None) + => Inner.Execute(command, ToInner(args), flags); + + public RedisResult ScriptEvaluate(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) => + // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? + Inner.ScriptEvaluate(hash, ToInner(keys), values, flags); + + public RedisResult ScriptEvaluate(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) => + // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? + Inner.ScriptEvaluate(script: script, keys: ToInner(keys), values: values, flags: flags); + + public RedisResult ScriptEvaluate(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) => + // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? + script.Evaluate(Inner, parameters, Prefix, flags); + + public RedisResult ScriptEvaluate(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) => + // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? + script.Evaluate(Inner, parameters, Prefix, flags); + + public RedisResult ScriptEvaluateReadOnly(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) => + // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? + Inner.ScriptEvaluateReadOnly(hash, ToInner(keys), values, flags); + + public RedisResult ScriptEvaluateReadOnly(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) => + // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? + Inner.ScriptEvaluateReadOnly(script, ToInner(keys), values, flags); + + public long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => + Inner.SetAdd(ToInner(key), values, flags); + + public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.SetAdd(ToInner(key), value, flags); + + public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.SetCombineAndStore(operation, ToInner(destination), ToInner(keys), flags); + + public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) => + Inner.SetCombineAndStore(operation, ToInner(destination), ToInner(first), ToInner(second), flags); + + public RedisValue[] SetCombine(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.SetCombine(operation, ToInner(keys), flags); + + public RedisValue[] SetCombine(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) => + Inner.SetCombine(operation, ToInner(first), ToInner(second), flags); + + public bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.SetContains(ToInner(key), value, flags); + + public bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => + Inner.SetContains(ToInner(key), values, flags); + + public long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) => + Inner.SetIntersectionLength(ToInner(keys), limit, flags); + + public long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.SetLength(ToInner(key), flags); + + public RedisValue[] SetMembers(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.SetMembers(ToInner(key), flags); + + public bool SetMove(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.SetMove(ToInner(source), ToInner(destination), value, flags); + + public RedisValue SetPop(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.SetPop(ToInner(key), flags); + + public RedisValue[] SetPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.SetPop(ToInner(key), count, flags); + + public RedisValue SetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.SetRandomMember(ToInner(key), flags); + + public RedisValue[] SetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.SetRandomMembers(ToInner(key), count, flags); + + public long SetRemove(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) => + Inner.SetRemove(ToInner(key), values, flags); + + public bool SetRemove(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.SetRemove(ToInner(key), value, flags); + + public long SortAndStore(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None) => + Inner.SortAndStore(ToInner(destination), ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags); + + public RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None) => + Inner.Sort(ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags); + + public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, CommandFlags flags) => + Inner.SortedSetAdd(ToInner(key), values, flags); + + public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetAdd(ToInner(key), values, when, flags); + + public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetAdd(ToInner(key), values, when, flags); + + public bool SortedSetAdd(RedisKey key, RedisValue member, double score, CommandFlags flags) => + Inner.SortedSetAdd(ToInner(key), member, score, flags); + + public bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetAdd(ToInner(key), member, score, when, flags); + + public bool SortedSetAdd(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetAdd(ToInner(key), member, score, when, flags); + + public RedisValue[] SortedSetCombine(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetCombine(operation, ToInner(keys), weights, aggregate, flags); + + public SortedSetEntry[] SortedSetCombineWithScores(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetCombineWithScores(operation, ToInner(keys), weights, aggregate, flags); + + public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetCombineAndStore(operation, ToInner(destination), ToInner(keys), weights, aggregate, flags); + + public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetCombineAndStore(operation, ToInner(destination), ToInner(first), ToInner(second), aggregate, flags); + + public double SortedSetDecrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetDecrement(ToInner(key), member, value, flags); + + public double SortedSetIncrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetIncrement(ToInner(key), member, value, flags); + + public long SortedSetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetIntersectionLength(ToInner(keys), limit, flags); + + public long SortedSetLength(RedisKey key, double min = -1.0 / 0.0, double max = 1.0 / 0.0, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetLength(ToInner(key), min, max, exclude, flags); + + public long SortedSetLengthByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetLengthByValue(ToInner(key), min, max, exclude, flags); + + public RedisValue SortedSetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRandomMember(ToInner(key), flags); + + public RedisValue[] SortedSetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRandomMembers(ToInner(key), count, flags); + + public SortedSetEntry[] SortedSetRandomMembersWithScores(RedisKey key, long count, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRandomMembersWithScores(ToInner(key), count, flags); + + public RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRangeByRank(ToInner(key), start, stop, order, flags); + + public long SortedSetRangeAndStore( + RedisKey destinationKey, + RedisKey sourceKey, + RedisValue start, + RedisValue stop, + SortedSetOrder sortedSetOrder = SortedSetOrder.ByRank, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long? take = null, + CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRangeAndStore(ToInner(sourceKey), ToInner(destinationKey), start, stop, sortedSetOrder, exclude, order, skip, take, flags); + + public SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRangeByRankWithScores(ToInner(key), start, stop, order, flags); + + public RedisValue[] SortedSetRangeByScore(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRangeByScore(ToInner(key), start, stop, exclude, order, skip, take, flags); + + public SortedSetEntry[] SortedSetRangeByScoreWithScores(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRangeByScoreWithScores(ToInner(key), start, stop, exclude, order, skip, take, flags); + + public RedisValue[] SortedSetRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude, long skip, long take, CommandFlags flags) => + Inner.SortedSetRangeByValue(ToInner(key), min, max, exclude, Order.Ascending, skip, take, flags); + + public RedisValue[] SortedSetRangeByValue(RedisKey key, RedisValue min = default, RedisValue max = default, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRangeByValue(ToInner(key), min, max, exclude, order, skip, take, flags); + + public long? SortedSetRank(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRank(ToInner(key), member, order, flags); + + public long SortedSetRemove(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRemove(ToInner(key), members, flags); + + public bool SortedSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRemove(ToInner(key), member, flags); + + public long SortedSetRemoveRangeByRank(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRemoveRangeByRank(ToInner(key), start, stop, flags); + + public long SortedSetRemoveRangeByScore(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRemoveRangeByScore(ToInner(key), start, stop, exclude, flags); + + public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetRemoveRangeByValue(ToInner(key), min, max, exclude, flags); + + public double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetScore(ToInner(key), member, flags); + + public double?[] SortedSetScores(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetScores(ToInner(key), members, flags); + + public long SortedSetUpdate(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetUpdate(ToInner(key), values, when, flags); + + public bool SortedSetUpdate(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetUpdate(ToInner(key), member, score, when, flags); + + public SortedSetEntry? SortedSetPop(RedisKey key, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetPop(ToInner(key), order, flags); + + public SortedSetEntry[] SortedSetPop(RedisKey key, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetPop(ToInner(key), count, order, flags); + + public SortedSetPopResult SortedSetPop(RedisKey[] keys, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.SortedSetPop(ToInner(keys), count, order, flags); + + public long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None) => + Inner.StreamAcknowledge(ToInner(key), groupName, messageId, flags); + + public long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) => + Inner.StreamAcknowledge(ToInner(key), groupName, messageIds, flags); + + public StreamTrimResult StreamAcknowledgeAndDelete(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue messageId, CommandFlags flags = CommandFlags.None) => + Inner.StreamAcknowledgeAndDelete(ToInner(key), groupName, mode, messageId, flags); + + public StreamTrimResult[] StreamAcknowledgeAndDelete(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) => + Inner.StreamAcknowledgeAndDelete(ToInner(key), groupName, mode, messageIds, flags); + + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) => + Inner.StreamAdd(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, flags); + + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) => + Inner.StreamAdd(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, flags); + + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamAdd(ToInner(key), streamField, streamValue, messageId, maxLength, useApproximateMaxLength, limit, mode, flags); + + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamAdd(ToInner(key), streamPairs, messageId, maxLength, useApproximateMaxLength, limit, mode, flags); + + public StreamAutoClaimResult StreamAutoClaim(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) => + Inner.StreamAutoClaim(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, flags); + + public StreamAutoClaimIdsOnlyResult StreamAutoClaimIdsOnly(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) => + Inner.StreamAutoClaimIdsOnly(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, flags); + + public StreamEntry[] StreamClaim(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) => + Inner.StreamClaim(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + + public RedisValue[] StreamClaimIdsOnly(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) => + Inner.StreamClaimIdsOnly(ToInner(key), consumerGroup, claimingConsumer, minIdleTimeInMs, messageIds, flags); + + public bool StreamConsumerGroupSetPosition(RedisKey key, RedisValue groupName, RedisValue position, CommandFlags flags = CommandFlags.None) => + Inner.StreamConsumerGroupSetPosition(ToInner(key), groupName, position, flags); + + public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue? position, CommandFlags flags) => + Inner.StreamCreateConsumerGroup(ToInner(key), groupName, position, flags); + + public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue? position = null, bool createStream = true, CommandFlags flags = CommandFlags.None) => + Inner.StreamCreateConsumerGroup(ToInner(key), groupName, position, createStream, flags); + + public StreamInfo StreamInfo(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StreamInfo(ToInner(key), flags); + + public StreamGroupInfo[] StreamGroupInfo(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StreamGroupInfo(ToInner(key), flags); + + public StreamConsumerInfo[] StreamConsumerInfo(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) => + Inner.StreamConsumerInfo(ToInner(key), groupName, flags); + + public long StreamLength(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StreamLength(ToInner(key), flags); + + public long StreamDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) => + Inner.StreamDelete(ToInner(key), messageIds, flags); + + public StreamTrimResult[] StreamDelete(RedisKey key, RedisValue[] messageIds, StreamTrimMode mode, CommandFlags flags = CommandFlags.None) => + Inner.StreamDelete(ToInner(key), messageIds, mode, flags); + + public long StreamDeleteConsumer(RedisKey key, RedisValue groupName, RedisValue consumerName, CommandFlags flags = CommandFlags.None) => + Inner.StreamDeleteConsumer(ToInner(key), groupName, consumerName, flags); + + public bool StreamDeleteConsumerGroup(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) => + Inner.StreamDeleteConsumerGroup(ToInner(key), groupName, flags); + + public StreamPendingInfo StreamPending(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) => + Inner.StreamPending(ToInner(key), groupName, flags); + + public StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId, RedisValue? maxId, CommandFlags flags) => + Inner.StreamPendingMessages(ToInner(key), groupName, count, consumerName, minId, maxId, flags); + + public StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, long? minIdleTimeInMs = null, CommandFlags flags = CommandFlags.None) => + Inner.StreamPendingMessages(ToInner(key), groupName, count, consumerName, minId, maxId, minIdleTimeInMs, flags); + + public StreamEntry[] StreamRange(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None) => + Inner.StreamRange(ToInner(key), minId, maxId, count, messageOrder, flags); + + public StreamEntry[] StreamRead(RedisKey key, RedisValue position, int? count = null, CommandFlags flags = CommandFlags.None) => + Inner.StreamRead(ToInner(key), position, count, flags); + + public RedisStream[] StreamRead(StreamPosition[] streamPositions, int? countPerStream = null, CommandFlags flags = CommandFlags.None) => + Inner.StreamRead(streamPositions, countPerStream, flags); + + public StreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position, int? count, CommandFlags flags) => + Inner.StreamReadGroup(ToInner(key), groupName, consumerName, position, count, flags); + + public StreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position = null, int? count = null, bool noAck = false, CommandFlags flags = CommandFlags.None) => + Inner.StreamReadGroup(ToInner(key), groupName, consumerName, position, count, noAck, flags); + + public RedisStream[] StreamReadGroup(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream, CommandFlags flags) => + Inner.StreamReadGroup(streamPositions, groupName, consumerName, countPerStream, flags); + + public RedisStream[] StreamReadGroup(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream = null, bool noAck = false, CommandFlags flags = CommandFlags.None) => + Inner.StreamReadGroup(streamPositions, groupName, consumerName, countPerStream, noAck, flags); + + public long StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength, CommandFlags flags) => + Inner.StreamTrim(ToInner(key), maxLength, useApproximateMaxLength, flags); + + public long StreamTrim(RedisKey key, long maxLength, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamTrim(ToInner(key), maxLength, useApproximateMaxLength, limit, mode, flags); + + public long StreamTrimByMinId(RedisKey key, RedisValue minId, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) => + Inner.StreamTrimByMinId(ToInner(key), minId, useApproximateMaxLength, limit, mode, flags); + + public long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.StringAppend(ToInner(key), value, flags); + + public long StringBitCount(RedisKey key, long start, long end, CommandFlags flags) => + Inner.StringBitCount(ToInner(key), start, end, flags); + + public long StringBitCount(RedisKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) => + Inner.StringBitCount(ToInner(key), start, end, indexType, flags); + + public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.StringBitOperation(operation, ToInner(destination), ToInner(keys), flags); + + public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default, CommandFlags flags = CommandFlags.None) => + Inner.StringBitOperation(operation, ToInner(destination), ToInner(first), ToInnerOrDefault(second), flags); + + public long StringBitPosition(RedisKey key, bool bit, long start, long end, CommandFlags flags) => + Inner.StringBitPosition(ToInner(key), bit, start, end, flags); + + public long StringBitPosition(RedisKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) => + Inner.StringBitPosition(ToInner(key), bit, start, end, indexType, flags); + + public double StringDecrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) => + Inner.StringDecrement(ToInner(key), value, flags); + + public long StringDecrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) => + Inner.StringDecrement(ToInner(key), value, flags); + + public RedisValue[] StringGet(RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.StringGet(ToInner(keys), flags); + + public RedisValue StringGet(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StringGet(ToInner(key), flags); + + public RedisValue StringGetSetExpiry(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) => + Inner.StringGetSetExpiry(ToInner(key), expiry, flags); + + public RedisValue StringGetSetExpiry(RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None) => + Inner.StringGetSetExpiry(ToInner(key), expiry, flags); + + public Lease? StringGetLease(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StringGetLease(ToInner(key), flags); + + public bool StringGetBit(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) => + Inner.StringGetBit(ToInner(key), offset, flags); + + public RedisValue StringGetRange(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) => + Inner.StringGetRange(ToInner(key), start, end, flags); + + public RedisValue StringGetSet(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.StringGetSet(ToInner(key), value, flags); + + public RedisValue StringGetDelete(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StringGetDelete(ToInner(key), flags); + + public RedisValueWithExpiry StringGetWithExpiry(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StringGetWithExpiry(ToInner(key), flags); + + public double StringIncrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) => + Inner.StringIncrement(ToInner(key), value, flags); + + public long StringIncrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) => + Inner.StringIncrement(ToInner(key), value, flags); + + public long StringLength(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.StringLength(ToInner(key), flags); + + public bool StringSet(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.StringSet(ToInner(values), when, flags); + + public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, When when) => + Inner.StringSet(ToInner(key), value, expiry, when); + public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags) => + Inner.StringSet(ToInner(key), value, expiry, when, flags); + public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.StringSet(ToInner(key), value, expiry, keepTtl, when, flags); + + public RedisValue StringSetAndGet(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags) => + Inner.StringSetAndGet(ToInner(key), value, expiry, when, flags); + + public RedisValue StringSetAndGet(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) => + Inner.StringSetAndGet(ToInner(key), value, expiry, keepTtl, when, flags); + + public bool StringSetBit(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None) => + Inner.StringSetBit(ToInner(key), offset, bit, flags); + + public RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) => + Inner.StringSetRange(ToInner(key), offset, value, flags); + + public TimeSpan Ping(CommandFlags flags = CommandFlags.None) => + Inner.Ping(flags); + + IEnumerable IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) + => Inner.HashScan(ToInner(key), pattern, pageSize, flags); + + IEnumerable IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => Inner.HashScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); + + IEnumerable IDatabase.HashScanNoValues(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => Inner.HashScanNoValues(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); + + IEnumerable IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) + => Inner.SetScan(ToInner(key), pattern, pageSize, flags); + + IEnumerable IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => Inner.SetScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); + + IEnumerable IDatabase.SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) + => Inner.SortedSetScan(ToInner(key), pattern, pageSize, flags); + + IEnumerable IDatabase.SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => Inner.SortedSetScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); + + public bool KeyTouch(RedisKey key, CommandFlags flags = CommandFlags.None) => + Inner.KeyTouch(ToInner(key), flags); + + public long KeyTouch(RedisKey[] keys, CommandFlags flags = CommandFlags.None) => + Inner.KeyTouch(ToInner(keys), flags); + } +} diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedTransaction.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedTransaction.cs new file mode 100644 index 000000000..89703ba6a --- /dev/null +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedTransaction.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; + +namespace StackExchange.Redis.KeyspaceIsolation +{ + internal sealed class KeyPrefixedTransaction : KeyPrefixed, ITransaction + { + public KeyPrefixedTransaction(ITransaction inner, byte[] prefix) : base(inner, prefix) { } + + public ConditionResult AddCondition(Condition condition) => Inner.AddCondition(condition.MapKeys(GetMapFunction())); + + public bool Execute(CommandFlags flags = CommandFlags.None) => Inner.Execute(flags); + + public Task ExecuteAsync(CommandFlags flags = CommandFlags.None) => Inner.ExecuteAsync(flags); + + public void Execute() => Inner.Execute(); + } +} diff --git a/src/StackExchange.Redis/Lease.cs b/src/StackExchange.Redis/Lease.cs new file mode 100644 index 000000000..91495dd08 --- /dev/null +++ b/src/StackExchange.Redis/Lease.cs @@ -0,0 +1,81 @@ +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace StackExchange.Redis +{ + /// + /// A sized region of contiguous memory backed by a memory pool; disposing the lease returns the memory to the pool. + /// + /// The type of data being leased. + public sealed class Lease : IMemoryOwner + { + /// + /// A lease of length zero. + /// + public static Lease Empty { get; } = new Lease(System.Array.Empty(), 0); + + private T[]? _arr; + + /// + /// The length of the lease. + /// + public int Length { get; } + + /// + /// Create a new lease. + /// + /// The size required. + /// Whether to erase the memory. + public static Lease Create(int length, bool clear = true) + { + if (length == 0) return Empty; + var arr = ArrayPool.Shared.Rent(length); + if (clear) System.Array.Clear(arr, 0, length); + return new Lease(arr, length); + } + + private Lease(T[] arr, int length) + { + _arr = arr; + Length = length; + } + + /// + /// Release all resources owned by the lease. + /// + public void Dispose() + { + if (Length != 0) + { + var arr = Interlocked.Exchange(ref _arr, null); + if (arr != null) ArrayPool.Shared.Return(arr); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T[] ThrowDisposed() => throw new ObjectDisposedException(nameof(Lease)); + + private T[] Array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _arr ?? ThrowDisposed(); + } + + /// + /// The data as a . + /// + public Memory Memory => new Memory(Array, 0, Length); + + /// + /// The data as a . + /// + public Span Span => new Span(Array, 0, Length); + + /// + /// The data as an . + /// + public ArraySegment ArraySegment => new ArraySegment(Array, 0, Length); + } +} diff --git a/src/StackExchange.Redis/LinearRetry.cs b/src/StackExchange.Redis/LinearRetry.cs new file mode 100644 index 000000000..ddd269eb3 --- /dev/null +++ b/src/StackExchange.Redis/LinearRetry.cs @@ -0,0 +1,25 @@ +namespace StackExchange.Redis +{ + /// + /// Represents a retry policy that performs retries at a fixed interval. The retries are performed up to a maximum allowed time. + /// + public class LinearRetry : IReconnectRetryPolicy + { + private readonly int maxRetryElapsedTimeAllowedMilliseconds; + + /// + /// Initializes a new instance using the specified maximum retry elapsed time allowed. + /// + /// maximum elapsed time in milliseconds to be allowed for it to perform retries. + public LinearRetry(int maxRetryElapsedTimeAllowedMilliseconds) => + this.maxRetryElapsedTimeAllowedMilliseconds = maxRetryElapsedTimeAllowedMilliseconds; + + /// + /// This method is called by the ConnectionMultiplexer to determine if a reconnect operation can be retried now. + /// + /// The number of times reconnect retries have already been made by the ConnectionMultiplexer while it was in the connecting state. + /// Total elapsed time in milliseconds since the last reconnect retry was made. + public bool ShouldRetry(long currentRetryCount, int timeElapsedMillisecondsSinceLastRetry) => + timeElapsedMillisecondsSinceLastRetry >= maxRetryElapsedTimeAllowedMilliseconds; + } +} diff --git a/src/StackExchange.Redis/LoggerExtensions.cs b/src/StackExchange.Redis/LoggerExtensions.cs new file mode 100644 index 000000000..be51733ce --- /dev/null +++ b/src/StackExchange.Redis/LoggerExtensions.cs @@ -0,0 +1,712 @@ +using System; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace StackExchange.Redis; + +internal static partial class LoggerExtensions +{ + // Helper structs for complex ToString() calls + internal readonly struct EndPointLogValue(EndPoint? endpoint) + { + public override string ToString() => Format.ToString(endpoint); + } + + internal readonly struct ServerEndPointLogValue(ServerEndPoint server) + { + public override string ToString() => Format.ToString(server); + } + + internal readonly struct ConfigurationOptionsLogValue(ConfigurationOptions options) + { + public override string ToString() => options.ToString(includePassword: false); + } + + // manual extensions + internal static void LogWithThreadPoolStats(this ILogger? log, string message) + { + if (log is null || !log.IsEnabled(LogLevel.Information)) + { + return; + } + + _ = PerfCounterHelper.GetThreadPoolStats(out string iocp, out string worker, out string? workItems); + +#if NET6_0_OR_GREATER + // use DISH when possible + // similar to: var composed = $"{message}, IOCP: {iocp}, WORKER: {worker}, ..."; on net6+ + var dish = new System.Runtime.CompilerServices.DefaultInterpolatedStringHandler(26, 4); + dish.AppendFormatted(message); + dish.AppendLiteral(", IOCP: "); + dish.AppendFormatted(iocp); + dish.AppendLiteral(", WORKER: "); + dish.AppendFormatted(worker); + if (workItems is not null) + { + dish.AppendLiteral(", POOL: "); + dish.AppendFormatted(workItems); + } + var composed = dish.ToStringAndClear(); +#else + var sb = new StringBuilder(); + sb.Append(message).Append(", IOCP: ").Append(iocp).Append(", WORKER: ").Append(worker); + if (workItems is not null) + { + sb.Append(", POOL: ").Append(workItems); + } + var composed = sb.ToString(); +#endif + log.LogInformationThreadPoolStats(composed); + } + + // Generated LoggerMessage methods + [LoggerMessage( + Level = LogLevel.Error, + Message = "Connection failed: {EndPoint} ({ConnectionType}, {FailureType}): {ErrorMessage}")] + internal static partial void LogErrorConnectionFailed(this ILogger logger, Exception? exception, EndPointLogValue endPoint, ConnectionType connectionType, ConnectionFailureType failureType, string errorMessage); + + [LoggerMessage( + Level = LogLevel.Error, + EventId = 1, + Message = "> {Message}")] + internal static partial void LogErrorInnerException(this ILogger logger, Exception exception, string message); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 2, + Message = "Checking {EndPoint} is available...")] + internal static partial void LogInformationCheckingServerAvailable(this ILogger logger, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Error, + EventId = 3, + Message = "Operation failed on {EndPoint}, aborting: {ErrorMessage}")] + internal static partial void LogErrorOperationFailedOnServer(this ILogger logger, Exception exception, EndPointLogValue endPoint, string errorMessage); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 4, + Message = "Attempting to set tie-breaker on {EndPoint}...")] + internal static partial void LogInformationAttemptingToSetTieBreaker(this ILogger logger, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 5, + Message = "Making {EndPoint} a primary...")] + internal static partial void LogInformationMakingServerPrimary(this ILogger logger, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 6, + Message = "Resending tie-breaker to {EndPoint}...")] + internal static partial void LogInformationResendingTieBreaker(this ILogger logger, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 7, + Message = "Broadcasting via {EndPoint}...")] + internal static partial void LogInformationBroadcastingViaNode(this ILogger logger, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 8, + Message = "Replicating to {EndPoint}...")] + internal static partial void LogInformationReplicatingToNode(this ILogger logger, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 9, + Message = "Reconfiguring all endpoints...")] + internal static partial void LogInformationReconfiguringAllEndpoints(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 10, + Message = "Verifying the configuration was incomplete; please verify")] + internal static partial void LogInformationVerifyingConfigurationIncomplete(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 11, + Message = "Connecting (async) on {Framework} (StackExchange.Redis: v{Version})")] + internal static partial void LogInformationConnectingAsync(this ILogger logger, string framework, string version); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 12, + Message = "Connecting (sync) on {Framework} (StackExchange.Redis: v{Version})")] + internal static partial void LogInformationConnectingSync(this ILogger logger, string framework, string version); + + [LoggerMessage( + Level = LogLevel.Error, + EventId = 13, + Message = "{ErrorMessage}")] + internal static partial void LogErrorSyncConnectTimeout(this ILogger logger, Exception exception, string errorMessage); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 14, + Message = "{Message}")] + internal static partial void LogInformationAfterConnect(this ILogger logger, string message); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 15, + Message = "Total connect time: {ElapsedMs:n0} ms")] + internal static partial void LogInformationTotalConnectTime(this ILogger logger, long elapsedMs); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 16, + Message = "No tasks to await")] + internal static partial void LogInformationNoTasksToAwait(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 17, + Message = "All tasks are already complete")] + internal static partial void LogInformationAllTasksComplete(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 18, + Message = "{Message}", + SkipEnabledCheck = true)] + internal static partial void LogInformationThreadPoolStats(this ILogger logger, string message); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 19, + Message = "Reconfiguration was already in progress due to: {ActiveCause}, attempted to run for: {NewCause}")] + internal static partial void LogInformationReconfigurationInProgress(this ILogger logger, string? activeCause, string newCause); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 20, + Message = "{Configuration}")] + internal static partial void LogInformationConfiguration(this ILogger logger, ConfigurationOptionsLogValue configuration); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 21, + Message = "{Count} unique nodes specified ({TieBreakerStatus} tiebreaker)")] + internal static partial void LogInformationUniqueNodesSpecified(this ILogger logger, int count, string tieBreakerStatus); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 22, + Message = "Allowing {Count} endpoint(s) {TimeSpan} to respond...")] + internal static partial void LogInformationAllowingEndpointsToRespond(this ILogger logger, int count, TimeSpan timeSpan); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 23, + Message = " Server[{Index}] ({Server}) Status: {Status} (inst: {MessagesSinceLastHeartbeat}, qs: {MessagesSentAwaitingResponse}, in: {BytesAvailableOnSocket}, qu: {MessagesSinceLastHeartbeat2}, aw: {IsWriterActive}, in-pipe: {BytesInReadPipe}, out-pipe: {BytesInWritePipe}, bw: {BacklogStatus}, rs: {ReadStatus}. ws: {WriteStatus})")] + internal static partial void LogInformationServerStatus(this ILogger logger, int index, ServerEndPointLogValue server, TaskStatus status, long messagesSinceLastHeartbeat, long messagesSentAwaitingResponse, long bytesAvailableOnSocket, long messagesSinceLastHeartbeat2, bool isWriterActive, long bytesInReadPipe, long bytesInWritePipe, PhysicalBridge.BacklogStatus backlogStatus, PhysicalConnection.ReadStatus readStatus, PhysicalConnection.WriteStatus writeStatus); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 24, + Message = "Endpoint summary:")] + internal static partial void LogInformationEndpointSummary(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 25, + Message = " {EndPoint}: Endpoint is (Interactive: {InteractiveState}, Subscription: {SubscriptionState})")] + internal static partial void LogInformationEndpointState(this ILogger logger, EndPointLogValue endPoint, PhysicalBridge.State interactiveState, PhysicalBridge.State subscriptionState); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 26, + Message = "Task summary:")] + internal static partial void LogInformationTaskSummary(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Error, + EventId = 27, + Message = " {Server}: Faulted: {ErrorMessage}")] + internal static partial void LogErrorServerFaulted(this ILogger logger, Exception exception, ServerEndPointLogValue server, string errorMessage); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 28, + Message = " {Server}: Connect task canceled")] + internal static partial void LogInformationConnectTaskCanceled(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 29, + Message = " {Server}: Returned with success as {ServerType} {Role} (Source: {Source})")] + internal static partial void LogInformationServerReturnedSuccess(this ILogger logger, ServerEndPointLogValue server, ServerType serverType, string role, string source); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 30, + Message = " {Server}: Returned, but incorrectly")] + internal static partial void LogInformationServerReturnedIncorrectly(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 31, + Message = " {Server}: Did not respond (Task.Status: {TaskStatus})")] + internal static partial void LogInformationServerDidNotRespond(this ILogger logger, ServerEndPointLogValue server, TaskStatus taskStatus); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 32, + Message = "{EndPoint}: Clearing as RedundantPrimary")] + internal static partial void LogInformationClearingAsRedundantPrimary(this ILogger logger, ServerEndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 33, + Message = "{EndPoint}: Setting as RedundantPrimary")] + internal static partial void LogInformationSettingAsRedundantPrimary(this ILogger logger, ServerEndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 34, + Message = "Cluster: {CoveredSlots} of {TotalSlots} slots covered")] + internal static partial void LogInformationClusterSlotsCovered(this ILogger logger, long coveredSlots, int totalSlots); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 35, + Message = "No subscription changes necessary")] + internal static partial void LogInformationNoSubscriptionChanges(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 36, + Message = "Subscriptions attempting reconnect: {SubscriptionChanges}")] + internal static partial void LogInformationSubscriptionsAttemptingReconnect(this ILogger logger, long subscriptionChanges); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 37, + Message = "{StormLog}")] + internal static partial void LogInformationStormLog(this ILogger logger, string stormLog); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 38, + Message = "Resetting failing connections to retry...")] + internal static partial void LogInformationResettingFailingConnections(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 39, + Message = " Retrying - attempts left: {AttemptsLeft}...")] + internal static partial void LogInformationRetryingAttempts(this ILogger logger, int attemptsLeft); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 40, + Message = "Starting heartbeat...")] + internal static partial void LogInformationStartingHeartbeat(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 41, + Message = "Broadcasting reconfigure...")] + internal static partial void LogInformationBroadcastingReconfigure(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Error, + EventId = 42, + Message = "Encountered error while updating cluster config: {ErrorMessage}")] + internal static partial void LogErrorEncounteredErrorWhileUpdatingClusterConfig(this ILogger logger, Exception exception, string errorMessage); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 43, + Message = "Election summary:")] + internal static partial void LogInformationElectionSummary(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 44, + Message = " Election: {Server} had no tiebreaker set")] + internal static partial void LogInformationElectionNoTiebreaker(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 45, + Message = " Election: {Server} nominates: {ServerResult}")] + internal static partial void LogInformationElectionNominates(this ILogger logger, ServerEndPointLogValue server, string serverResult); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 46, + Message = " Election: No primaries detected")] + internal static partial void LogInformationElectionNoPrimariesDetected(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 47, + Message = " Election: Single primary detected: {EndPoint}")] + internal static partial void LogInformationElectionSinglePrimaryDetected(this ILogger logger, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 48, + Message = " Election: Multiple primaries detected...")] + internal static partial void LogInformationElectionMultiplePrimariesDetected(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 49, + Message = " Election: No nominations by tie-breaker")] + internal static partial void LogInformationElectionNoNominationsByTieBreaker(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 50, + Message = " Election: Tie-breaker unanimous: {Unanimous}")] + internal static partial void LogInformationElectionTieBreakerUnanimous(this ILogger logger, string unanimous); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 51, + Message = " Election: Elected: {EndPoint}")] + internal static partial void LogInformationElectionElected(this ILogger logger, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 52, + Message = " Election is contested:")] + internal static partial void LogInformationElectionContested(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 53, + Message = " Election: {Key} has {Value} votes")] + internal static partial void LogInformationElectionVotes(this ILogger logger, string key, int value); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 54, + Message = " Election: Choosing primary arbitrarily: {EndPoint}")] + internal static partial void LogInformationElectionChoosingPrimaryArbitrarily(this ILogger logger, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 55, + Message = "...but we couldn't find that")] + internal static partial void LogInformationCouldNotFindThatEndpoint(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 56, + Message = "...but we did find instead: {DeDottedEndpoint}")] + internal static partial void LogInformationFoundAlternativeEndpoint(this ILogger logger, string deDottedEndpoint); + + // ServerEndPoint logging methods + [LoggerMessage( + Level = LogLevel.Information, + EventId = 57, + Message = "{Server}: OnConnectedAsync already connected start")] + internal static partial void LogInformationOnConnectedAsyncAlreadyConnectedStart(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 58, + Message = "{Server}: OnConnectedAsync already connected end")] + internal static partial void LogInformationOnConnectedAsyncAlreadyConnectedEnd(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 59, + Message = "{Server}: OnConnectedAsync init (State={ConnectionState})")] + internal static partial void LogInformationOnConnectedAsyncInit(this ILogger logger, ServerEndPointLogValue server, PhysicalBridge.State? connectionState); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 60, + Message = "{Server}: OnConnectedAsync completed ({Result})")] + internal static partial void LogInformationOnConnectedAsyncCompleted(this ILogger logger, ServerEndPointLogValue server, string result); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 61, + Message = "{Server}: Auto-configuring...")] + internal static partial void LogInformationAutoConfiguring(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 62, + Message = "{EndPoint}: Requesting tie-break (Key=\"{TieBreakerKey}\")...")] + internal static partial void LogInformationRequestingTieBreak(this ILogger logger, EndPointLogValue endPoint, RedisKey tieBreakerKey); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 63, + Message = "{Server}: Server handshake")] + internal static partial void LogInformationServerHandshake(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 64, + Message = "{Server}: Authenticating via HELLO")] + internal static partial void LogInformationAuthenticatingViaHello(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 65, + Message = "{Server}: Authenticating (user/password)")] + internal static partial void LogInformationAuthenticatingUserPassword(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 66, + Message = "{Server}: Authenticating (password)")] + internal static partial void LogInformationAuthenticatingPassword(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 67, + Message = "{Server}: Setting client name: {ClientName}")] + internal static partial void LogInformationSettingClientName(this ILogger logger, ServerEndPointLogValue server, string clientName); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 68, + Message = "{Server}: Setting client lib/ver")] + internal static partial void LogInformationSettingClientLibVer(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 69, + Message = "{Server}: Sending critical tracer (handshake): {CommandAndKey}")] + internal static partial void LogInformationSendingCriticalTracer(this ILogger logger, ServerEndPointLogValue server, string commandAndKey); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 70, + Message = "{Server}: Flushing outbound buffer")] + internal static partial void LogInformationFlushingOutboundBuffer(this ILogger logger, ServerEndPointLogValue server); + + // ResultProcessor logging methods + [LoggerMessage( + Level = LogLevel.Information, + EventId = 71, + Message = "Response from {BridgeName} / {CommandAndKey}: {Result}")] + internal static partial void LogInformationResponse(this ILogger logger, string? bridgeName, string commandAndKey, RawResult result); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 72, + Message = "{Server}: Auto-configured role: replica")] + internal static partial void LogInformationAutoConfiguredRoleReplica(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 73, + Message = "{Server}: Auto-configured (CLIENT) connection-id: {ConnectionId}")] + internal static partial void LogInformationAutoConfiguredClientConnectionId(this ILogger logger, ServerEndPointLogValue server, long connectionId); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 74, + Message = "{Server}: Auto-configured (INFO) role: {Role}")] + internal static partial void LogInformationAutoConfiguredInfoRole(this ILogger logger, ServerEndPointLogValue server, string role); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 75, + Message = "{Server}: Auto-configured (INFO) version: {Version}")] + internal static partial void LogInformationAutoConfiguredInfoVersion(this ILogger logger, ServerEndPointLogValue server, Version version); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 76, + Message = "{Server}: Auto-configured (INFO) server-type: {ServerType}")] + internal static partial void LogInformationAutoConfiguredInfoServerType(this ILogger logger, ServerEndPointLogValue server, ServerType serverType); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 77, + Message = "{Server}: Auto-configured (SENTINEL) server-type: sentinel")] + internal static partial void LogInformationAutoConfiguredSentinelServerType(this ILogger logger, ServerEndPointLogValue server); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 78, + Message = "{Server}: Auto-configured (CONFIG) timeout: {TimeoutSeconds}s")] + internal static partial void LogInformationAutoConfiguredConfigTimeout(this ILogger logger, ServerEndPointLogValue server, int timeoutSeconds); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 79, + Message = "{Server}: Auto-configured (CONFIG) databases: {DatabaseCount}")] + internal static partial void LogInformationAutoConfiguredConfigDatabases(this ILogger logger, ServerEndPointLogValue server, int databaseCount); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 81, + Message = "{Server}: Auto-configured (CONFIG) read-only replica: {ReadOnlyReplica}")] + internal static partial void LogInformationAutoConfiguredConfigReadOnlyReplica(this ILogger logger, ServerEndPointLogValue server, bool readOnlyReplica); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 82, + Message = "{Server}: Auto-configured (HELLO) server-version: {Version}")] + internal static partial void LogInformationAutoConfiguredHelloServerVersion(this ILogger logger, ServerEndPointLogValue server, Version version); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 83, + Message = "{Server}: Auto-configured (HELLO) protocol: {Protocol}")] + internal static partial void LogInformationAutoConfiguredHelloProtocol(this ILogger logger, ServerEndPointLogValue server, RedisProtocol protocol); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 84, + Message = "{Server}: Auto-configured (HELLO) connection-id: {ConnectionId}")] + internal static partial void LogInformationAutoConfiguredHelloConnectionId(this ILogger logger, ServerEndPointLogValue server, long connectionId); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 85, + Message = "{Server}: Auto-configured (HELLO) server-type: {ServerType}")] + internal static partial void LogInformationAutoConfiguredHelloServerType(this ILogger logger, ServerEndPointLogValue server, ServerType serverType); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 86, + Message = "{Server}: Auto-configured (HELLO) role: {Role}")] + internal static partial void LogInformationAutoConfiguredHelloRole(this ILogger logger, ServerEndPointLogValue server, string role); + + // PhysicalBridge logging methods + [LoggerMessage( + Level = LogLevel.Information, + EventId = 87, + Message = "{EndPoint}: OnEstablishingAsync complete")] + internal static partial void LogInformationOnEstablishingComplete(this ILogger logger, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 88, + Message = "{ErrorMessage}")] + internal static partial void LogInformationConnectionFailureRequested(this ILogger logger, Exception exception, string errorMessage); + + [LoggerMessage( + Level = LogLevel.Error, + EventId = 89, + Message = "{ErrorMessage}")] + internal static partial void LogErrorConnectionIssue(this ILogger logger, Exception exception, string errorMessage); + + [LoggerMessage( + Level = LogLevel.Warning, + EventId = 90, + Message = "Dead socket detected, no reads in {LastReadSecondsAgo} seconds with {TimeoutCount} timeouts, issuing disconnect")] + internal static partial void LogWarningDeadSocketDetected(this ILogger logger, long lastReadSecondsAgo, long timeoutCount); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 91, + Message = "Resurrecting {Bridge} (retry: {RetryCount})")] + internal static partial void LogInformationResurrecting(this ILogger logger, PhysicalBridge bridge, long retryCount); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 92, + Message = "{BridgeName}: Connecting...")] + internal static partial void LogInformationConnecting(this ILogger logger, string bridgeName); + + [LoggerMessage( + Level = LogLevel.Error, + EventId = 93, + Message = "{BridgeName}: Connect failed: {ErrorMessage}")] + internal static partial void LogErrorConnectFailed(this ILogger logger, Exception exception, string bridgeName, string errorMessage); + + // PhysicalConnection logging methods + [LoggerMessage( + Level = LogLevel.Error, + EventId = 94, + Message = "No endpoint")] + internal static partial void LogErrorNoEndpoint(this ILogger logger, Exception exception); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 95, + Message = "{EndPoint}: BeginConnectAsync")] + internal static partial void LogInformationBeginConnectAsync(this ILogger logger, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 96, + Message = "{EndPoint}: Starting read")] + internal static partial void LogInformationStartingRead(this ILogger logger, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Error, + EventId = 97, + Message = "{EndPoint}: (socket shutdown)")] + internal static partial void LogErrorSocketShutdown(this ILogger logger, Exception exception, EndPointLogValue endPoint); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 98, + Message = "Configuring TLS")] + internal static partial void LogInformationConfiguringTLS(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 99, + Message = "TLS connection established successfully using protocol: {SslProtocol}")] + internal static partial void LogInformationTLSConnectionEstablished(this ILogger logger, System.Security.Authentication.SslProtocols sslProtocol); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 100, + Message = "{BridgeName}: Connected")] + internal static partial void LogInformationConnected(this ILogger logger, string bridgeName); + + // ConnectionMultiplexer GetStatus logging methods + [LoggerMessage( + Level = LogLevel.Information, + EventId = 101, + Message = "Endpoint Summary:")] + internal static partial void LogInformationEndpointSummaryHeader(this ILogger logger); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 102, + Message = "Server summary: {ServerSummary}, counters: {ServerCounters}, profile: {ServerProfile}")] + internal static partial void LogInformationServerSummary(this ILogger logger, string serverSummary, ServerCounters serverCounters, string serverProfile); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 105, + Message = "Sync timeouts: {SyncTimeouts}; async timeouts: {AsyncTimeouts}; fire and forget: {FireAndForgets}; last heartbeat: {LastHeartbeatSecondsAgo}s ago")] + internal static partial void LogInformationTimeoutsSummary(this ILogger logger, long syncTimeouts, long asyncTimeouts, long fireAndForgets, long lastHeartbeatSecondsAgo); + + // EndPointCollection logging methods + [LoggerMessage( + Level = LogLevel.Information, + EventId = 106, + Message = "Using DNS to resolve '{DnsHost}'...")] + internal static partial void LogInformationUsingDnsToResolve(this ILogger logger, string dnsHost); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 107, + Message = "'{DnsHost}' => {IpAddress}")] + internal static partial void LogInformationDnsResolutionResult(this ILogger logger, string dnsHost, IPAddress ipAddress); + + [LoggerMessage( + Level = LogLevel.Error, + EventId = 108, + Message = "{ErrorMessage}")] + internal static partial void LogErrorDnsResolution(this ILogger logger, Exception exception, string errorMessage); + + [LoggerMessage( + Level = LogLevel.Information, + EventId = 109, + Message = "Service name not defined.")] + internal static partial void LogInformationServiceNameNotDefined(this ILogger logger); +} diff --git a/src/StackExchange.Redis/LoggingPipe.cs b/src/StackExchange.Redis/LoggingPipe.cs new file mode 100644 index 000000000..3c89110ae --- /dev/null +++ b/src/StackExchange.Redis/LoggingPipe.cs @@ -0,0 +1,86 @@ +namespace StackExchange.Redis +{ +#if LOGOUTPUT + sealed class LoggingPipe : IDuplexPipe + { + private IDuplexPipe _inner; + + public LoggingPipe(IDuplexPipe inner, string inPath, string outPath, SocketManager mgr) + { + _inner = inner; + if (string.IsNullOrWhiteSpace(inPath)) + { + Input = inner.Input; + } + else + { + var pipe = new Pipe(mgr.ReceivePipeOptions); + Input = pipe.Reader; + CloneAsync(inPath, inner.Input, pipe.Writer).RedisFireAndForget(); + } + + if (string.IsNullOrWhiteSpace(outPath)) + { + Output = inner.Output; + } + else + { + var pipe = new Pipe(mgr.SendPipeOptions); + Output = pipe.Writer; + CloneAsync(outPath, pipe.Reader, inner.Output).RedisFireAndForget(); + } + + } + + private async Task CloneAsync(string path, PipeReader from, PipeWriter to) + { + try { + to.OnReaderCompleted((ex, o) => { + // if (ex != null) Console.Error.WriteLine(ex); + ((PipeReader)o).Complete(ex); + }, from); + from.OnWriterCompleted((ex, o) => + { + // if (ex != null) Console.Error.WriteLine(ex); + ((PipeWriter)o).Complete(ex); + }, to); + try { File.Delete(path); } catch{} + + while(true) + { + var result = await from.ReadAsync().ForAwait(); + var buffer = result.Buffer; + if (result.IsCompleted && buffer.IsEmpty) break; + + using (var file = new FileStream(path, FileMode.Append, FileAccess.Write)) + { + foreach (var segment in buffer) + { + // append it to the file + bool leased = false; + if (!MemoryMarshal.TryGetArray(segment, out var arr)) + { + var tmp = ArrayPool.Shared.Rent(segment.Length); + segment.CopyTo(tmp); + arr = new ArraySegment(tmp, 0, segment.Length); + leased = true; + } + await file.WriteAsync(arr.Array, arr.Offset, arr.Count).ForAwait(); + await file.FlushAsync().ForAwait(); + if (leased) ArrayPool.Shared.Return(arr.Array); + + // and flush it upstream + await to.WriteAsync(segment).ForAwait(); + } + } + from.AdvanceTo(buffer.End); + } + } + catch { } + } + public PipeReader Input { get; } + + public PipeWriter Output { get; } + } +#endif +} diff --git a/src/StackExchange.Redis/LuaScript.cs b/src/StackExchange.Redis/LuaScript.cs new file mode 100644 index 000000000..7a99f635a --- /dev/null +++ b/src/StackExchange.Redis/LuaScript.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.ComponentModel; +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + /// + /// Represents a Lua script that can be executed on Redis. + /// + /// Unlike normal Redis Lua scripts, LuaScript can have named parameters (prefixed by a @). + /// Public fields and properties of the passed in object are treated as parameters. + /// + /// + /// Parameters of type RedisKey are sent to Redis as KEY (https://redis.io/commands/eval) in addition to arguments, + /// so as to play nicely with Redis Cluster. + /// + /// All members of this class are thread safe. + /// + public sealed class LuaScript + { + /// + /// Since the mapping of "script text" -> LuaScript doesn't depend on any particular details of + /// the redis connection itself, this cache is global. + /// + private static readonly ConcurrentDictionary Cache = new(); + + /// + /// The original Lua script that was used to create this. + /// + public string OriginalScript { get; } + + /// + /// The Lua script that will actually be sent to Redis for execution. + /// All @-prefixed parameter names have been replaced at this point. + /// + public string ExecutableScript { get; } + + /// + /// Arguments are in the order they have to passed to the script in. + /// + internal string[] Arguments { get; } + + private bool HasArguments => Arguments?.Length > 0; + + private readonly Hashtable? ParameterMappers; + + internal LuaScript(string originalScript, string executableScript, string[] arguments) + { + OriginalScript = originalScript; + ExecutableScript = executableScript; + Arguments = arguments; + + if (HasArguments) + { + ParameterMappers = new Hashtable(); + } + } + + /// + /// Finalizer - used to prompt cleanups of the script cache when a LuaScript reference goes out of scope. + /// + ~LuaScript() + { + try + { + Cache.TryRemove(OriginalScript, out _); + } + catch { } + } + + /// + /// Invalidates the internal cache of LuaScript objects. + /// Existing LuaScripts will continue to work, but future calls to LuaScript.Prepare + /// return a new LuaScript instance. + /// + public static void PurgeCache() => Cache.Clear(); + + /// + /// Returns the number of cached LuaScripts. + /// + public static int GetCachedScriptCount() => Cache.Count; + + /// + /// Prepares a Lua script with named parameters to be run against any Redis instance. + /// + /// The script to prepare. + public static LuaScript Prepare(string script) + { + if (!Cache.TryGetValue(script, out WeakReference? weakRef) || weakRef.Target is not LuaScript ret) + { + ret = ScriptParameterMapper.PrepareScript(script); + Cache[script] = new WeakReference(ret); + } + + return ret; + } + + internal void ExtractParameters(object? ps, RedisKey? keyPrefix, out RedisKey[]? keys, out RedisValue[]? args) + { + if (HasArguments) + { + if (ps == null) throw new ArgumentNullException(nameof(ps), "Script requires parameters"); + + var psType = ps.GetType(); + var mapper = (Func?)ParameterMappers![psType]; + if (mapper == null) + { + lock (ParameterMappers) + { + mapper = (Func?)ParameterMappers[psType]; + if (mapper == null) + { + if (!ScriptParameterMapper.IsValidParameterHash(psType, this, out string? missingMember, out string? badMemberType)) + { + if (missingMember != null) + { + throw new ArgumentException("Expected [" + missingMember + "] to be a field or gettable property on [" + psType.FullName + "]", nameof(ps)); + } + + throw new ArgumentException("Expected [" + badMemberType + "] on [" + psType.FullName + "] to be convertible to a RedisValue", nameof(ps)); + } + + ParameterMappers[psType] = mapper = ScriptParameterMapper.GetParameterExtractor(psType, this); + } + } + } + + var mapped = mapper(ps, keyPrefix); + keys = mapped.Keys; + args = mapped.Arguments; + } + else + { + keys = null; + args = null; + } + } + + /// + /// Evaluates this LuaScript against the given database, extracting parameters from the passed in object if any. + /// + /// The redis database to evaluate against. + /// The parameter object to use. + /// The key prefix to use, if any. + /// The command flags to use. + public RedisResult Evaluate(IDatabase db, object? ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) + { + ExtractParameters(ps, withKeyPrefix, out RedisKey[]? keys, out RedisValue[]? args); + return db.ScriptEvaluate(script: ExecutableScript, keys: keys, values: args, flags: flags); + } + + /// + /// Evaluates this LuaScript against the given database, extracting parameters from the passed in object if any. + /// + /// The redis database to evaluate against. + /// The parameter object to use. + /// The key prefix to use, if any. + /// The command flags to use. + public Task EvaluateAsync(IDatabaseAsync db, object? ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) + { + ExtractParameters(ps, withKeyPrefix, out RedisKey[]? keys, out RedisValue[]? args); + return db.ScriptEvaluateAsync(script: ExecutableScript, keys: keys, values: args, flags: flags); + } + + /// + /// + /// Loads this LuaScript into the given IServer so it can be run with it's SHA1 hash, instead of + /// using the implicit SHA1 hash that's calculated after the script is sent to the server for the first time. + /// + /// Note: the FireAndForget command flag cannot be set. + /// + /// The server to load the script on. + /// The command flags to use. + public LoadedLuaScript Load(IServer server, CommandFlags flags = CommandFlags.None) + { + if ((flags & CommandFlags.FireAndForget) != 0) + { + throw new ArgumentOutOfRangeException(nameof(flags), "Loading a script cannot be FireAndForget"); + } + + var hash = server.ScriptLoad(ExecutableScript, flags); + return new LoadedLuaScript(this, hash!); // not nullable because fire and forget is disabled + } + + /// + /// + /// Loads this LuaScript into the given IServer so it can be run with it's SHA1 hash, instead of + /// using the implicit SHA1 hash that's calculated after the script is sent to the server for the first time. + /// + /// Note: the FireAndForget command flag cannot be set. + /// + /// The server to load the script on. + /// The command flags to use. + public async Task LoadAsync(IServer server, CommandFlags flags = CommandFlags.None) + { + if ((flags & CommandFlags.FireAndForget) != 0) + { + throw new ArgumentOutOfRangeException(nameof(flags), "Loading a script cannot be FireAndForget"); + } + + var hash = await server.ScriptLoadAsync(ExecutableScript, flags).ForAwait()!; + return new LoadedLuaScript(this, hash!); // not nullable because fire and forget is disabled + } + } + + /// + /// Represents a Lua script that can be executed on Redis. + /// + /// Unlike LuaScript, LoadedLuaScript sends the hash of it's ExecutableScript to Redis rather than pass + /// the whole script on each call. This requires that the script be loaded into Redis before it is used. + /// + /// + /// To create a LoadedLuaScript first create a LuaScript via LuaScript.Prepare(string), then + /// call Load(IServer, CommandFlags) on the returned LuaScript. + /// + /// + /// Unlike normal Redis Lua scripts, LoadedLuaScript can have named parameters (prefixed by a @). + /// Public fields and properties of the passed in object are treated as parameters. + /// + /// + /// Parameters of type RedisKey are sent to Redis as KEY (https://redis.io/commands/eval) in addition to arguments, + /// so as to play nicely with Redis Cluster. + /// + /// All members of this class are thread safe. + /// + public sealed class LoadedLuaScript + { + /// + /// The original script that was used to create this LoadedLuaScript. + /// + public string OriginalScript => Original.OriginalScript; + + /// + /// The script that will actually be sent to Redis for execution. + /// + public string ExecutableScript => Original.ExecutableScript; + + /// + /// The SHA1 hash of ExecutableScript. + /// This is sent to Redis instead of ExecutableScript during Evaluate and EvaluateAsync calls. + /// + /// Be aware that using hash directly is not resilient to Redis server restarts. + [EditorBrowsable(EditorBrowsableState.Never)] + public byte[] Hash { get; } + + // internal for testing purposes only + internal LuaScript Original; + + internal LoadedLuaScript(LuaScript original, byte[] hash) + { + Original = original; + Hash = hash; + } + + /// + /// Evaluates this LoadedLuaScript against the given database, extracting parameters for the passed in object if any. + /// + /// This method sends the SHA1 hash of the ExecutableScript instead of the script itself. + /// If the script has not been loaded into the passed Redis instance, it will fail. + /// + /// + /// The redis database to evaluate against. + /// The parameter object to use. + /// The key prefix to use, if any. + /// The command flags to use. + public RedisResult Evaluate(IDatabase db, object? ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) + { + Original.ExtractParameters(ps, withKeyPrefix, out RedisKey[]? keys, out RedisValue[]? args); + + return db.ScriptEvaluate(script: ExecutableScript, keys: keys, values: args, flags: flags); + } + + /// + /// Evaluates this LoadedLuaScript against the given database, extracting parameters for the passed in object if any. + /// + /// This method sends the SHA1 hash of the ExecutableScript instead of the script itself. + /// If the script has not been loaded into the passed Redis instance, it will fail. + /// + /// + /// The redis database to evaluate against. + /// The parameter object to use. + /// The key prefix to use, if any. + /// The command flags to use. + public Task EvaluateAsync(IDatabaseAsync db, object? ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) + { + Original.ExtractParameters(ps, withKeyPrefix, out RedisKey[]? keys, out RedisValue[]? args); + + return db.ScriptEvaluateAsync(script: ExecutableScript, keys: keys, values: args, flags: flags); + } + } +} diff --git a/src/StackExchange.Redis/Maintenance/AzureMaintenanceEvent.cs b/src/StackExchange.Redis/Maintenance/AzureMaintenanceEvent.cs new file mode 100644 index 000000000..4e32afa5a --- /dev/null +++ b/src/StackExchange.Redis/Maintenance/AzureMaintenanceEvent.cs @@ -0,0 +1,197 @@ +using System; +using System.Globalization; +using System.Net; +using System.Threading.Tasks; +#if NETCOREAPP +using System.Buffers.Text; +#endif + +namespace StackExchange.Redis.Maintenance +{ + /// + /// Azure node maintenance event. For more information, please see: . + /// + public sealed class AzureMaintenanceEvent : ServerMaintenanceEvent + { + private const string PubSubChannelName = "AzureRedisEvents"; + + internal AzureMaintenanceEvent(string? azureEvent) + { + if (azureEvent == null) + { + return; + } + + // The message consists of key-value pairs delimited by pipes. For example, a message might look like: + // NotificationType|NodeMaintenanceStarting|StartTimeUtc|2021-09-23T12:34:19|IsReplica|False|IpAddress|13.67.42.199|SSLPort|15001|NonSSLPort|13001 + var message = azureEvent.AsSpan(); + try + { + while (message.Length > 0) + { + if (message[0] == '|') + { + message = message.Slice(1); + continue; + } + + // Grab the next pair + var nextDelimiter = message.IndexOf('|'); + if (nextDelimiter < 0) + { + // The rest of the message is not a key-value pair and is therefore malformed. Stop processing it. + break; + } + + if (nextDelimiter == message.Length - 1) + { + // The message is missing the value for this key-value pair. It is malformed so we stop processing it. + break; + } + + var key = message.Slice(0, nextDelimiter); + message = message.Slice(key.Length + 1); + + var valueEnd = message.IndexOf('|'); + var value = valueEnd > -1 ? message.Slice(0, valueEnd) : message; + message = message.Slice(value.Length); + + if (key.Length > 0 && value.Length > 0) + { +#if NETCOREAPP + switch (key) + { + case var _ when key.SequenceEqual(nameof(NotificationType).AsSpan()): + NotificationTypeString = value.ToString(); + NotificationType = ParseNotificationType(NotificationTypeString); + break; + case var _ when key.SequenceEqual("StartTimeInUTC".AsSpan()) && DateTime.TryParseExact(value, "s", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTime startTime): + StartTimeUtc = DateTime.SpecifyKind(startTime, DateTimeKind.Utc); + break; + case var _ when key.SequenceEqual(nameof(IsReplica).AsSpan()) && bool.TryParse(value, out var isReplica): + IsReplica = isReplica; + break; + case var _ when key.SequenceEqual(nameof(IPAddress).AsSpan()) && IPAddress.TryParse(value, out var ipAddress): + IPAddress = ipAddress; + break; + case var _ when key.SequenceEqual("SSLPort".AsSpan()) && Format.TryParseInt32(value, out var port): + SslPort = port; + break; + case var _ when key.SequenceEqual("NonSSLPort".AsSpan()) && Format.TryParseInt32(value, out var nonsslport): + NonSslPort = nonsslport; + break; + } +#else + switch (key) + { + case var _ when key.SequenceEqual(nameof(NotificationType).AsSpan()): + NotificationTypeString = value.ToString(); + NotificationType = ParseNotificationType(NotificationTypeString); + break; + case var _ when key.SequenceEqual("StartTimeInUTC".AsSpan()) && DateTime.TryParseExact(value.ToString(), "s", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTime startTime): + StartTimeUtc = DateTime.SpecifyKind(startTime, DateTimeKind.Utc); + break; + case var _ when key.SequenceEqual(nameof(IsReplica).AsSpan()) && bool.TryParse(value.ToString(), out var isReplica): + IsReplica = isReplica; + break; + case var _ when key.SequenceEqual(nameof(IPAddress).AsSpan()) && IPAddress.TryParse(value.ToString(), out var ipAddress): + IPAddress = ipAddress; + break; + case var _ when key.SequenceEqual("SSLPort".AsSpan()) && Format.TryParseInt32(value.ToString(), out var port): + SslPort = port; + break; + case var _ when key.SequenceEqual("NonSSLPort".AsSpan()) && Format.TryParseInt32(value.ToString(), out var nonsslport): + NonSslPort = nonsslport; + break; + } +#endif + } + } + } + catch + { + // TODO: Append to rolling debug log when it's present + } + } + + internal static async Task AddListenerAsync(ConnectionMultiplexer multiplexer, Action? log = null) + { + if (!multiplexer.CommandMap.IsAvailable(RedisCommand.SUBSCRIBE)) + { + return; + } + + try + { + var sub = multiplexer.GetSubscriber(); + if (sub == null) + { + log?.Invoke("Failed to GetSubscriber for AzureRedisEvents"); + return; + } + + await sub.SubscribeAsync(RedisChannel.Literal(PubSubChannelName), async (_, message) => + { + var newMessage = new AzureMaintenanceEvent(message!); + newMessage.NotifyMultiplexer(multiplexer); + + switch (newMessage.NotificationType) + { + case AzureNotificationType.NodeMaintenanceEnded: + case AzureNotificationType.NodeMaintenanceFailoverComplete: + case AzureNotificationType.NodeMaintenanceScaleComplete: + await multiplexer.ReconfigureAsync($"Azure Event: {newMessage.NotificationType}").ForAwait(); + break; + } + }).ForAwait(); + } + catch (Exception e) + { + log?.Invoke($"Encountered exception: {e}"); + } + } + + /// + /// Indicates the type of event (raw string form). + /// + public string NotificationTypeString { get; } = "Unknown"; + + /// + /// The parsed version of for easier consumption. + /// + public AzureNotificationType NotificationType { get; } + + /// + /// Indicates if the event is for a replica node. + /// + public bool IsReplica { get; } + + /// + /// IPAddress of the node event is intended for. + /// + public IPAddress? IPAddress { get; } + + /// + /// SSL Port. + /// + public int SslPort { get; } + + /// + /// Non-SSL port. + /// + public int NonSslPort { get; } + + private static AzureNotificationType ParseNotificationType(string typeString) => typeString switch + { + "NodeMaintenanceScheduled" => AzureNotificationType.NodeMaintenanceScheduled, + "NodeMaintenanceStarting" => AzureNotificationType.NodeMaintenanceStarting, + "NodeMaintenanceStart" => AzureNotificationType.NodeMaintenanceStart, + "NodeMaintenanceEnded" => AzureNotificationType.NodeMaintenanceEnded, + // This is temporary until server changes go into effect - to be removed in later versions + "NodeMaintenanceFailover" => AzureNotificationType.NodeMaintenanceFailoverComplete, + "NodeMaintenanceFailoverComplete" => AzureNotificationType.NodeMaintenanceFailoverComplete, + "NodeMaintenanceScaleComplete" => AzureNotificationType.NodeMaintenanceScaleComplete, + _ => AzureNotificationType.Unknown, + }; + } +} diff --git a/src/StackExchange.Redis/Maintenance/AzureNotificationType.cs b/src/StackExchange.Redis/Maintenance/AzureNotificationType.cs new file mode 100644 index 000000000..13237f914 --- /dev/null +++ b/src/StackExchange.Redis/Maintenance/AzureNotificationType.cs @@ -0,0 +1,43 @@ +namespace StackExchange.Redis.Maintenance +{ + /// + /// The types of notifications that Azure is sending for events happening. + /// + public enum AzureNotificationType + { + /// + /// Unrecognized event type, likely needs a library update to recognize new events. + /// + Unknown, + + /// + /// Indicates that a maintenance event is scheduled. May be several minutes from now. + /// + NodeMaintenanceScheduled, + + /// + /// This event gets fired ~20s before maintenance begins. + /// + NodeMaintenanceStarting, + + /// + /// This event gets fired when maintenance is imminent (<5s). + /// + NodeMaintenanceStart, + + /// + /// Indicates that the node maintenance operation is over. + /// + NodeMaintenanceEnded, + + /// + /// Indicates that a replica has been promoted to primary. + /// + NodeMaintenanceFailoverComplete, + + /// + /// Indicates that a scale event (adding or removing nodes) has completed for a cluster. + /// + NodeMaintenanceScaleComplete, + } +} diff --git a/src/StackExchange.Redis/Maintenance/ServerMaintenanceEvent.cs b/src/StackExchange.Redis/Maintenance/ServerMaintenanceEvent.cs new file mode 100644 index 000000000..cb0d43c6c --- /dev/null +++ b/src/StackExchange.Redis/Maintenance/ServerMaintenanceEvent.cs @@ -0,0 +1,41 @@ +using System; + +namespace StackExchange.Redis.Maintenance +{ + /// + /// Base class for all server maintenance events. + /// + public class ServerMaintenanceEvent + { + internal ServerMaintenanceEvent() + { + ReceivedTimeUtc = DateTime.UtcNow; + } + + /// + /// Raw message received from the server. + /// + public string? RawMessage { get; protected set; } + + /// + /// The time the event was received. If we know when the event is expected to start will be populated. + /// + public DateTime ReceivedTimeUtc { get; } + + /// + /// Indicates the expected start time of the event. + /// + public DateTime? StartTimeUtc { get; protected set; } + + /// + /// Returns a string representing the maintenance event with all of its properties. + /// + public override string? ToString() => RawMessage; + + /// + /// Notifies a ConnectionMultiplexer of this event, for anyone observing its handler. + /// + protected void NotifyMultiplexer(ConnectionMultiplexer multiplexer) + => multiplexer.OnServerMaintenanceEvent(this); + } +} diff --git a/src/StackExchange.Redis/Message.cs b/src/StackExchange.Redis/Message.cs new file mode 100644 index 000000000..0c9eb4c92 --- /dev/null +++ b/src/StackExchange.Redis/Message.cs @@ -0,0 +1,1850 @@ +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using Microsoft.Extensions.Logging; +using StackExchange.Redis.Profiling; + +namespace StackExchange.Redis +{ + internal sealed class LoggingMessage : Message + { + public readonly ILogger log; + private readonly Message tail; + + public static Message Create(ILogger? log, Message tail) + { + return log == null ? tail : new LoggingMessage(log, tail); + } + + private LoggingMessage(ILogger log, Message tail) : base(tail.Db, tail.Flags, tail.Command) + { + this.log = log; + this.tail = tail; + Flags = tail.Flags; + } + + public override string CommandAndKey => tail.CommandAndKey; + + public override void AppendStormLog(StringBuilder sb) => tail.AppendStormLog(sb); + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) => tail.GetHashSlot(serverSelectionStrategy); + + protected override void WriteImpl(PhysicalConnection physical) + { + try + { + var bridge = physical.BridgeCouldBeNull; + log?.LogTrace($"{bridge?.Name}: Writing: {tail.CommandAndKey}"); + } + catch { } + tail.WriteTo(physical); + } + public override int ArgCount => tail.ArgCount; + + public ILogger Log => log; + } + + internal abstract class Message : ICompletable + { + public readonly int Db; + + private uint _highIntegrityToken; + + internal const CommandFlags InternalCallFlag = (CommandFlags)128; + + protected RedisCommand command; + + private const CommandFlags AskingFlag = (CommandFlags)32, + ScriptUnavailableFlag = (CommandFlags)256, + DemandSubscriptionConnection = (CommandFlags)2048; + + private const CommandFlags MaskPrimaryServerPreference = CommandFlags.DemandMaster + | CommandFlags.DemandReplica + | CommandFlags.PreferMaster + | CommandFlags.PreferReplica; + + private const CommandFlags UserSelectableFlags = CommandFlags.None + | CommandFlags.DemandMaster + | CommandFlags.DemandReplica + | CommandFlags.PreferMaster + | CommandFlags.PreferReplica +#pragma warning disable CS0618 // Type or member is obsolete + | CommandFlags.HighPriority +#pragma warning restore CS0618 + | CommandFlags.FireAndForget + | CommandFlags.NoRedirect + | CommandFlags.NoScriptCache; + private IResultBox? resultBox; + + private ResultProcessor? resultProcessor; + + // All for profiling purposes + private ProfiledCommand? performance; + internal DateTime CreatedDateTime; + internal long CreatedTimestamp; + + protected Message(int db, CommandFlags flags, RedisCommand command) + { + bool dbNeeded = RequiresDatabase(command); + if (command == RedisCommand.UNKNOWN) + { + // all bets are off here + } + else if (db < 0) + { + if (dbNeeded) + { + throw ExceptionFactory.DatabaseRequired(false, command); + } + } + else + { + if (!dbNeeded) + { + throw ExceptionFactory.DatabaseNotRequired(false, command); + } + } + + bool primaryOnly = command.IsPrimaryOnly(); + Db = db; + this.command = command; + Flags = flags & UserSelectableFlags; + if (primaryOnly) SetPrimaryOnly(); + + CreatedDateTime = DateTime.UtcNow; + CreatedTimestamp = Stopwatch.GetTimestamp(); + Status = CommandStatus.WaitingToBeSent; + } + + internal void SetPrimaryOnly() + { + switch (GetPrimaryReplicaFlags(Flags)) + { + case CommandFlags.DemandReplica: + throw ExceptionFactory.PrimaryOnly(false, command, null, null); + case CommandFlags.DemandMaster: + // already fine as-is + break; + case CommandFlags.PreferMaster: + case CommandFlags.PreferReplica: + default: // we will run this on the primary, then + Flags = SetPrimaryReplicaFlags(Flags, CommandFlags.DemandMaster); + break; + } + } + + internal void SetProfileStorage(ProfiledCommand storage) + { + performance = storage; + performance.SetMessage(this); + } + + internal void PrepareToResend(ServerEndPoint resendTo, bool isMoved) + { + if (performance == null) return; + + var oldPerformance = performance; + + oldPerformance.SetCompleted(); + performance = null; + + CreatedDateTime = DateTime.UtcNow; + CreatedTimestamp = Stopwatch.GetTimestamp(); + performance = ProfiledCommand.NewAttachedToSameContext(oldPerformance, resendTo, isMoved); + performance.SetMessage(this); + Status = CommandStatus.WaitingToBeSent; + } + + public CommandFlags Flags { get; internal set; } + internal CommandStatus Status { get; private set; } + public RedisCommand Command => command; + public virtual string CommandAndKey => Command.ToString(); + + /// + /// Things with the potential to cause harm, or to reveal configuration information. + /// + public bool IsAdmin + { + get + { + switch (Command) + { + case RedisCommand.BGREWRITEAOF: + case RedisCommand.BGSAVE: + case RedisCommand.CLIENT: + case RedisCommand.CLUSTER: + case RedisCommand.CONFIG: + case RedisCommand.DEBUG: + case RedisCommand.FLUSHALL: + case RedisCommand.FLUSHDB: + case RedisCommand.INFO: + case RedisCommand.KEYS: + case RedisCommand.MONITOR: + case RedisCommand.REPLICAOF: + case RedisCommand.SAVE: + case RedisCommand.SHUTDOWN: + case RedisCommand.SLAVEOF: + case RedisCommand.SLOWLOG: + case RedisCommand.SWAPDB: + case RedisCommand.SYNC: + return true; + default: + return false; + } + } + } + + public bool IsAsking => (Flags & AskingFlag) != 0; + + public bool IsHighIntegrity => _highIntegrityToken != 0; + + public uint HighIntegrityToken => _highIntegrityToken; + + internal void WithHighIntegrity(uint value) + => _highIntegrityToken = value; + + internal bool IsScriptUnavailable => (Flags & ScriptUnavailableFlag) != 0; + + internal void SetScriptUnavailable() => Flags |= ScriptUnavailableFlag; + + public bool IsFireAndForget => (Flags & CommandFlags.FireAndForget) != 0; + public bool IsInternalCall => (Flags & InternalCallFlag) != 0; + + public IResultBox? ResultBox => resultBox; + + public abstract int ArgCount { get; } // note: over-estimate if necessary + + public static Message Create(int db, CommandFlags flags, RedisCommand command) + { + if (command == RedisCommand.SELECT) + return new SelectMessage(db, flags); + return new CommandMessage(db, flags, command); + } + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key) => + new CommandKeyMessage(db, flags, command, key); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1) => + new CommandKeyKeyMessage(db, flags, command, key0, key1); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, in RedisValue value) => + new CommandKeyKeyValueMessage(db, flags, command, key0, key1, value); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, in RedisKey key2) => + new CommandKeyKeyKeyMessage(db, flags, command, key0, key1, key2); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value) => + new CommandValueMessage(db, flags, command, value); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value) => + new CommandKeyValueMessage(db, flags, command, key, value); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel) => + new CommandChannelMessage(db, flags, command, channel); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel, in RedisValue value) => + new CommandChannelValueMessage(db, flags, command, channel, value); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value, in RedisChannel channel) => + new CommandValueChannelMessage(db, flags, command, value, channel); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1) => + new CommandKeyValueValueMessage(db, flags, command, key, value0, value1); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2) => + new CommandKeyValueValueValueMessage(db, flags, command, key, value0, value1, value2); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, GeoEntry[] values) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(values); +#else + if (values == null) throw new ArgumentNullException(nameof(values)); +#endif + if (values.Length == 0) + { + throw new ArgumentOutOfRangeException(nameof(values)); + } + if (values.Length == 1) + { + var value = values[0]; + return Create(db, flags, command, key, value.Longitude, value.Latitude, value.Member); + } + var arr = new RedisValue[3 * values.Length]; + int index = 0; + foreach (var value in values) + { + arr[index++] = value.Longitude; + arr[index++] = value.Latitude; + arr[index++] = value.Member; + } + return new CommandKeyValuesMessage(db, flags, command, key, arr); + } + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3) => + new CommandKeyValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) => + new CommandKeyValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5) => + new CommandKeyValueValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4, value5); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5, in RedisValue value6) => + new CommandKeyValueValueValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4, value5, value6); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1) => + new CommandValueValueMessage(db, flags, command, value0, value1); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value, in RedisKey key) => + new CommandValueKeyMessage(db, flags, command, value, key); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2) => + new CommandValueValueValueMessage(db, flags, command, value0, value1, value2); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) => + new CommandValueValueValueValueValueMessage(db, flags, command, value0, value1, value2, value3, value4); + + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue[] values) => + new CommandKeyValueValueValuesMessage(db, flags, command, key, value0, value1, values); + + public static Message Create( + int db, + CommandFlags flags, + RedisCommand command, + in RedisKey key0, + in RedisKey key1, + in RedisValue value0, + in RedisValue value1) => + new CommandKeyKeyValueValueMessage(db, flags, command, key0, key1, value0, value1); + + public static Message Create( + int db, + CommandFlags flags, + RedisCommand command, + in RedisKey key0, + in RedisKey key1, + in RedisValue value0, + in RedisValue value1, + in RedisValue value2) => + new CommandKeyKeyValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2); + + public static Message Create( + int db, + CommandFlags flags, + RedisCommand command, + in RedisKey key0, + in RedisKey key1, + in RedisValue value0, + in RedisValue value1, + in RedisValue value2, + in RedisValue value3) => + new CommandKeyKeyValueValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, value3); + + public static Message Create( + int db, + CommandFlags flags, + RedisCommand command, + in RedisKey key0, + in RedisKey key1, + in RedisValue value0, + in RedisValue value1, + in RedisValue value2, + in RedisValue value3, + in RedisValue value4) => + new CommandKeyKeyValueValueValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, value3, value4); + + public static Message Create( + int db, + CommandFlags flags, + RedisCommand command, + in RedisKey key0, + in RedisKey key1, + in RedisValue value0, + in RedisValue value1, + in RedisValue value2, + in RedisValue value3, + in RedisValue value4, + in RedisValue value5) => + new CommandKeyKeyValueValueValueValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, value3, value4, value5); + + public static Message Create( + int db, + CommandFlags flags, + RedisCommand command, + in RedisKey key0, + in RedisKey key1, + in RedisValue value0, + in RedisValue value1, + in RedisValue value2, + in RedisValue value3, + in RedisValue value4, + in RedisValue value5, + in RedisValue value6) => + new CommandKeyKeyValueValueValueValueValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, value3, value4, value5, value6); + + public static Message CreateInSlot(int db, int slot, CommandFlags flags, RedisCommand command, RedisValue[] values) => + new CommandSlotValuesMessage(db, slot, flags, command, values); + + /// Gets whether this is primary-only. + /// + /// Note that the constructor runs the switch statement above, so + /// this will already be true for primary-only commands, even if the + /// user specified etc. + /// + public bool IsPrimaryOnly() => GetPrimaryReplicaFlags(Flags) == CommandFlags.DemandMaster; + + public virtual void AppendStormLog(StringBuilder sb) + { + if (Db >= 0) sb.Append(Db).Append(':'); + sb.Append(CommandAndKey); + } + + public virtual int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) => ServerSelectionStrategy.NoSlot; + + /// + /// This does a few important things: + /// 1: it suppresses error events for commands that the user isn't interested in + /// (i.e. "why does my standalone server keep saying ERR unknown command 'cluster' ?") + /// 2: it allows the initial PING and GET (during connect) to get queued rather + /// than be rejected as no-server-available (note that this doesn't apply to + /// handshake messages, as they bypass the queue completely). + /// 3: it disables non-pref logging, as it is usually server-targeted. + /// + public void SetInternalCall() => Flags |= InternalCallFlag; + + /// + /// Gets a string representation of this message: "[{DB}]:{CommandAndKey} ({resultProcessor})". + /// + public override string ToString() => + $"[{Db}]:{CommandAndKey} ({resultProcessor?.GetType().Name ?? "(n/a)"})"; + + /// + /// Gets a string representation of this message without the key: "[{DB}]:{Command} ({resultProcessor})". + /// + public string ToStringCommandOnly() => + $"[{Db}]:{Command} ({resultProcessor?.GetType().Name ?? "(n/a)"})"; + + public void SetResponseReceived() => performance?.SetResponseReceived(); + + bool ICompletable.TryComplete(bool isAsync) + { + Complete(); + return true; + } + + public void Complete() + { + // Ensure we can never call Complete on the same resultBox from two threads by grabbing it now + var currBox = Interlocked.Exchange(ref resultBox, null); + + // set the completion/performance data + performance?.SetCompleted(); + + currBox?.ActivateContinuations(); + } + + internal bool ResultBoxIsAsync => Volatile.Read(ref resultBox)?.IsAsync == true; + + internal static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisKey[] keys) => keys.Length switch + { + 0 => new CommandKeyMessage(db, flags, command, key), + 1 => new CommandKeyKeyMessage(db, flags, command, key, keys[0]), + 2 => new CommandKeyKeyKeyMessage(db, flags, command, key, keys[0], keys[1]), + _ => new CommandKeyKeysMessage(db, flags, command, key, keys), + }; + + internal static Message Create(int db, CommandFlags flags, RedisCommand command, IList keys) => keys.Count switch + { + 0 => new CommandMessage(db, flags, command), + 1 => new CommandKeyMessage(db, flags, command, keys[0]), + 2 => new CommandKeyKeyMessage(db, flags, command, keys[0], keys[1]), + 3 => new CommandKeyKeyKeyMessage(db, flags, command, keys[0], keys[1], keys[2]), + _ => new CommandKeysMessage(db, flags, command, (keys as RedisKey[]) ?? keys.ToArray()), + }; + + internal static Message Create(int db, CommandFlags flags, RedisCommand command, IList values) => values.Count switch + { + 0 => new CommandMessage(db, flags, command), + 1 => new CommandValueMessage(db, flags, command, values[0]), + 2 => new CommandValueValueMessage(db, flags, command, values[0], values[1]), + 3 => new CommandValueValueValueMessage(db, flags, command, values[0], values[1], values[2]), + // no 4; not worth adding + 5 => new CommandValueValueValueValueValueMessage(db, flags, command, values[0], values[1], values[2], values[3], values[4]), + _ => new CommandValuesMessage(db, flags, command, (values as RedisValue[]) ?? values.ToArray()), + }; + + internal static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisValue[] values) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(values); +#else + if (values == null) throw new ArgumentNullException(nameof(values)); +#endif + return values.Length switch + { + 0 => new CommandKeyMessage(db, flags, command, key), + 1 => new CommandKeyValueMessage(db, flags, command, key, values[0]), + 2 => new CommandKeyValueValueMessage(db, flags, command, key, values[0], values[1]), + 3 => new CommandKeyValueValueValueMessage(db, flags, command, key, values[0], values[1], values[2]), + 4 => new CommandKeyValueValueValueValueMessage(db, flags, command, key, values[0], values[1], values[2], values[3]), + _ => new CommandKeyValuesMessage(db, flags, command, key, values), + }; + } + + internal static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, RedisValue[] values) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(values); +#else + if (values == null) throw new ArgumentNullException(nameof(values)); +#endif + return values.Length switch + { + 0 => new CommandKeyKeyMessage(db, flags, command, key0, key1), + 1 => new CommandKeyKeyValueMessage(db, flags, command, key0, key1, values[0]), + 2 => new CommandKeyKeyValueValueMessage(db, flags, command, key0, key1, values[0], values[1]), + 3 => new CommandKeyKeyValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2]), + 4 => new CommandKeyKeyValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3]), + 5 => new CommandKeyKeyValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4]), + 6 => new CommandKeyKeyValueValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4], values[5]), + 7 => new CommandKeyKeyValueValueValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4], values[5], values[6]), + _ => new CommandKeyKeyValuesMessage(db, flags, command, key0, key1, values), + }; + } + + internal static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, RedisValue[] values, in RedisKey key1) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(values); +#else + if (values == null) throw new ArgumentNullException(nameof(values)); +#endif + return new CommandKeyValuesKeyMessage(db, flags, command, key0, values, key1); + } + + internal static CommandFlags GetPrimaryReplicaFlags(CommandFlags flags) + { + // for the purposes of the switch, we only care about two bits + return flags & MaskPrimaryServerPreference; + } + + internal static bool RequiresDatabase(RedisCommand command) + { + switch (command) + { + case RedisCommand.ASKING: + case RedisCommand.AUTH: + case RedisCommand.BGREWRITEAOF: + case RedisCommand.BGSAVE: + case RedisCommand.CLIENT: + case RedisCommand.CLUSTER: + case RedisCommand.COMMAND: + case RedisCommand.CONFIG: + case RedisCommand.DISCARD: + case RedisCommand.ECHO: + case RedisCommand.FLUSHALL: + case RedisCommand.HELLO: + case RedisCommand.INFO: + case RedisCommand.LASTSAVE: + case RedisCommand.LATENCY: + case RedisCommand.MEMORY: + case RedisCommand.MONITOR: + case RedisCommand.MULTI: + case RedisCommand.PING: + case RedisCommand.PUBLISH: + case RedisCommand.PUBSUB: + case RedisCommand.PUNSUBSCRIBE: + case RedisCommand.PSUBSCRIBE: + case RedisCommand.QUIT: + case RedisCommand.READONLY: + case RedisCommand.READWRITE: + case RedisCommand.REPLICAOF: + case RedisCommand.ROLE: + case RedisCommand.SAVE: + case RedisCommand.SCRIPT: + case RedisCommand.SHUTDOWN: + case RedisCommand.SLAVEOF: + case RedisCommand.SLOWLOG: + case RedisCommand.SUBSCRIBE: + case RedisCommand.SPUBLISH: + case RedisCommand.SSUBSCRIBE: + case RedisCommand.SUNSUBSCRIBE: + case RedisCommand.SWAPDB: + case RedisCommand.SYNC: + case RedisCommand.TIME: + case RedisCommand.UNSUBSCRIBE: + case RedisCommand.SENTINEL: + return false; + default: + return true; + } + } + + internal static CommandFlags SetPrimaryReplicaFlags(CommandFlags everything, CommandFlags primaryReplica) + { + // take away the two flags we don't want, and add back the ones we care about + return (everything & ~(CommandFlags.DemandMaster | CommandFlags.DemandReplica | CommandFlags.PreferMaster | CommandFlags.PreferReplica)) + | primaryReplica; + } + + internal void Cancel() => resultBox?.Cancel(); + + // true if ready to be completed (i.e. false if re-issued to another server) + internal bool ComputeResult(PhysicalConnection connection, in RawResult result) + { + var box = resultBox; + try + { + if (box != null && box.IsFaulted) return false; // already failed (timeout, etc) + if (resultProcessor == null) return true; + + // false here would be things like resends (MOVED) - the message is not yet complete + return resultProcessor.SetResult(connection, this, result); + } + catch (Exception ex) + { + ex.Data.Add("got", result.ToString()); + connection?.BridgeCouldBeNull?.Multiplexer?.OnMessageFaulted(this, ex); + box?.SetException(ex); + return box != null; // we still want to pulse/complete + } + } + + internal void Fail(ConnectionFailureType failure, Exception? innerException, string? annotation, ConnectionMultiplexer? muxer) + { + PhysicalConnection.IdentifyFailureType(innerException, ref failure); + resultProcessor?.ConnectionFail(this, failure, innerException, annotation, muxer); + } + + internal virtual void SetExceptionAndComplete(Exception exception, PhysicalBridge? bridge) + { + resultBox?.SetException(exception); + Complete(); + } + + internal bool TrySetResult(T value) + { + if (resultBox is IResultBox typed && !typed.IsFaulted) + { + typed.SetResult(value); + return true; + } + return false; + } + + internal void SetEnqueued(PhysicalConnection? connection) + { + SetWriteTime(); + performance?.SetEnqueued(connection?.BridgeCouldBeNull?.ConnectionType); + _enqueuedTo = connection; + if (connection == null) + { + _queuedStampSent = _queuedStampReceived = -1; + } + else + { + connection.GetBytes(out _queuedStampSent, out _queuedStampReceived); + } + } + + internal void TryGetHeadMessages(out Message? now, out Message? next) + { + now = next = null; + _enqueuedTo?.GetHeadMessages(out now, out next); + } + + internal bool TryGetPhysicalState( + out PhysicalConnection.WriteStatus ws, + out PhysicalConnection.ReadStatus rs, + out long sentDelta, + out long receivedDelta) + { + var connection = _enqueuedTo; + sentDelta = receivedDelta = -1; + if (connection != null) + { + ws = connection.GetWriteStatus(); + rs = connection.GetReadStatus(); + connection.GetBytes(out var sent, out var received); + if (sent >= 0 && _queuedStampSent >= 0) sentDelta = sent - _queuedStampSent; + if (received >= 0 && _queuedStampReceived >= 0) receivedDelta = received - _queuedStampReceived; + return true; + } + else + { + ws = PhysicalConnection.WriteStatus.NA; + rs = PhysicalConnection.ReadStatus.NA; + return false; + } + } + + internal bool IsBacklogged => Status == CommandStatus.WaitingInBacklog; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetBacklogged() => Status = CommandStatus.WaitingInBacklog; + + private PhysicalConnection? _enqueuedTo; + private long _queuedStampReceived, _queuedStampSent; + + internal void SetRequestSent() + { + Status = CommandStatus.Sent; + performance?.SetRequestSent(); + } + + // the time (ticks) at which this message was considered written + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetWriteTime() + { + _writeTickCount = Environment.TickCount; // note this might be reset if we resend a message, cluster-moved etc; I'm OK with that + } + private int _writeTickCount; + + public int GetWriteTime() => Volatile.Read(ref _writeTickCount); + + /// + /// Gets if this command should be sent over the subscription bridge. + /// + internal bool IsForSubscriptionBridge => (Flags & DemandSubscriptionConnection) != 0; + + public virtual string CommandString => Command.ToString(); + + /// + /// Sends this command to the subscription connection rather than the interactive. + /// + internal void SetForSubscriptionBridge() => Flags |= DemandSubscriptionConnection; + + /// + /// Checks if this message has violated the provided timeout. + /// Whether it's a sync operation in a .Wait() or in the backlog queue or written/pending asynchronously, we need to timeout everything. + /// ...or we get indefinite Task hangs for completions. + /// + internal bool HasTimedOut(int now, int timeoutMilliseconds, out int millisecondsTaken) + { + millisecondsTaken = unchecked(now - _writeTickCount); // note: we can't just check "if sent < cutoff" because of wrap-around + return millisecondsTaken >= timeoutMilliseconds; + } + + internal void SetAsking(bool value) + { + if (value) Flags |= AskingFlag; // the bits giveth + else Flags &= ~AskingFlag; // and the bits taketh away + } + + internal void SetNoRedirect() => Flags |= CommandFlags.NoRedirect; + + internal void SetPreferPrimary() => + Flags = (Flags & ~MaskPrimaryServerPreference) | CommandFlags.PreferMaster; + + internal void SetPreferReplica() => + Flags = (Flags & ~MaskPrimaryServerPreference) | CommandFlags.PreferReplica; + + /// + /// Sets the processor and box for this message to execute. + /// + /// + /// Note order here is reversed to prevent overload resolution errors. + /// + internal void SetSource(ResultProcessor? resultProcessor, IResultBox? resultBox) + { + this.resultBox = resultBox; + this.resultProcessor = resultProcessor; + } + + /// + /// Sets the box and processor for this message to execute. + /// + /// + /// Note order here is reversed to prevent overload resolution errors. + /// + /// The type of the result box result. + internal void SetSource(IResultBox resultBox, ResultProcessor? resultProcessor) + { + this.resultBox = resultBox; + this.resultProcessor = resultProcessor; + } + + protected abstract void WriteImpl(PhysicalConnection physical); + + internal void WriteTo(PhysicalConnection physical) + { + try + { + WriteImpl(physical); + } + catch (Exception ex) when (ex is not RedisCommandException) // these have specific meaning; don't wrap + { + physical?.OnInternalError(ex); + Fail(ConnectionFailureType.InternalFailure, ex, null, physical?.BridgeCouldBeNull?.Multiplexer); + } + } + + private static ReadOnlySpan ChecksumTemplate => "$4\r\nXXXX\r\n"u8; + + internal void WriteHighIntegrityChecksumRequest(PhysicalConnection physical) + { + Debug.Assert(IsHighIntegrity, "should only be used for high-integrity"); + try + { + physical.WriteHeader(RedisCommand.ECHO, 1); // use WriteHeader to allow command-rewrite + + Span chk = stackalloc byte[10]; + Debug.Assert(ChecksumTemplate.Length == chk.Length, "checksum template length error"); + ChecksumTemplate.CopyTo(chk); + BinaryPrimitives.WriteUInt32LittleEndian(chk.Slice(4, 4), _highIntegrityToken); + physical.WriteRaw(chk); + } + catch (Exception ex) + { + physical?.OnInternalError(ex); + Fail(ConnectionFailureType.InternalFailure, ex, null, physical?.BridgeCouldBeNull?.Multiplexer); + } + } + + internal static Message CreateHello(int protocolVersion, string? username, string? password, string? clientName, CommandFlags flags) + => new HelloMessage(protocolVersion, username, password, clientName, flags); + + internal sealed class HelloMessage : Message + { + private readonly string? _username, _password, _clientName; + private readonly int _protocolVersion; + + internal HelloMessage(int protocolVersion, string? username, string? password, string? clientName, CommandFlags flags) + : base(-1, flags, RedisCommand.HELLO) + { + _protocolVersion = protocolVersion; + _username = username; + _password = password; + _clientName = clientName; + } + + public override string CommandAndKey => Command + " " + _protocolVersion; + + public override int ArgCount + { + get + { + int count = 1; // HELLO protover + if (!string.IsNullOrWhiteSpace(_password)) count += 3; // [AUTH username password] + if (!string.IsNullOrWhiteSpace(_clientName)) count += 2; // [SETNAME client] + return count; + } + } + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, ArgCount); + physical.WriteBulkString(_protocolVersion); + if (!string.IsNullOrWhiteSpace(_password)) + { + physical.WriteBulkString(RedisLiterals.AUTH); + physical.WriteBulkString(string.IsNullOrWhiteSpace(_username) ? RedisLiterals.@default : _username); + physical.WriteBulkString(_password); + } + if (!string.IsNullOrWhiteSpace(_clientName)) + { + physical.WriteBulkString(RedisLiterals.SETNAME); + physical.WriteBulkString(_clientName); + } + } + } + + internal abstract class CommandChannelBase : Message + { + internal readonly RedisChannel Channel; + + protected CommandChannelBase(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel) : base(db, flags, command) + { + channel.AssertNotNull(); + Channel = channel; + } + + public override string CommandAndKey => Command + " " + Channel; + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + => Channel.IsKeyRouted ? serverSelectionStrategy.HashSlot(Channel) : ServerSelectionStrategy.NoSlot; + } + + internal abstract class CommandKeyBase : Message + { + protected readonly RedisKey Key; + + protected CommandKeyBase(int db, CommandFlags flags, RedisCommand command, in RedisKey key) : base(db, flags, command) + { + key.AssertNotNull(); + Key = key; + } + + public override string CommandAndKey => Command + " " + (string?)Key; + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) => serverSelectionStrategy.HashSlot(Key); + } + + private sealed class CommandChannelMessage : CommandChannelBase + { + public CommandChannelMessage(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel) : base(db, flags, command, channel) + { } + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 1); + physical.Write(Channel); + } + public override int ArgCount => 1; + } + + private sealed class CommandChannelValueMessage : CommandChannelBase + { + private readonly RedisValue value; + public CommandChannelValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel, in RedisValue value) : base(db, flags, command, channel) + { + value.AssertNotNull(); + this.value = value; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 2); + physical.Write(Channel); + physical.WriteBulkString(value); + } + public override int ArgCount => 2; + } + + private sealed class CommandKeyKeyKeyMessage : CommandKeyBase + { + private readonly RedisKey key1, key2; + public CommandKeyKeyKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, in RedisKey key2) : base(db, flags, command, key0) + { + key1.AssertNotNull(); + key2.AssertNotNull(); + this.key1 = key1; + this.key2 = key2; + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + { + var slot = serverSelectionStrategy.HashSlot(Key); + slot = serverSelectionStrategy.CombineSlot(slot, key1); + return serverSelectionStrategy.CombineSlot(slot, key2); + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 3); + physical.Write(Key); + physical.Write(key1); + physical.Write(key2); + } + public override int ArgCount => 3; + } + + private class CommandKeyKeyMessage : CommandKeyBase + { + protected readonly RedisKey key1; + public CommandKeyKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1) : base(db, flags, command, key0) + { + key1.AssertNotNull(); + this.key1 = key1; + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + { + var slot = serverSelectionStrategy.HashSlot(Key); + return serverSelectionStrategy.CombineSlot(slot, key1); + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 2); + physical.Write(Key); + physical.Write(key1); + } + public override int ArgCount => 2; + } + + private sealed class CommandKeyKeysMessage : CommandKeyBase + { + private readonly RedisKey[] keys; + public CommandKeyKeysMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisKey[] keys) : base(db, flags, command, key) + { + for (int i = 0; i < keys.Length; i++) + { + keys[i].AssertNotNull(); + } + this.keys = keys; + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + { + var slot = serverSelectionStrategy.HashSlot(Key); + for (int i = 0; i < keys.Length; i++) + { + slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); + } + return slot; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(command, keys.Length + 1); + physical.Write(Key); + for (int i = 0; i < keys.Length; i++) + { + physical.Write(keys[i]); + } + } + public override int ArgCount => keys.Length + 1; + } + + private sealed class CommandKeyKeyValueMessage : CommandKeyKeyMessage + { + private readonly RedisValue value; + public CommandKeyKeyValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, in RedisValue value) : base(db, flags, command, key0, key1) + { + value.AssertNotNull(); + this.value = value; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 3); + physical.Write(Key); + physical.Write(key1); + physical.WriteBulkString(value); + } + + public override int ArgCount => 3; + } + + private sealed class CommandKeyMessage : CommandKeyBase + { + public CommandKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key) : base(db, flags, command, key) + { } + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 1); + physical.Write(Key); + } + public override int ArgCount => 1; + } + + private sealed class CommandValuesMessage : Message + { + private readonly RedisValue[] values; + public CommandValuesMessage(int db, CommandFlags flags, RedisCommand command, RedisValue[] values) : base(db, flags, command) + { + for (int i = 0; i < values.Length; i++) + { + values[i].AssertNotNull(); + } + this.values = values; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(command, values.Length); + for (int i = 0; i < values.Length; i++) + { + physical.WriteBulkString(values[i]); + } + } + public override int ArgCount => values.Length; + } + + private sealed class CommandKeysMessage : Message + { + private readonly RedisKey[] keys; + public CommandKeysMessage(int db, CommandFlags flags, RedisCommand command, RedisKey[] keys) : base(db, flags, command) + { + for (int i = 0; i < keys.Length; i++) + { + keys[i].AssertNotNull(); + } + this.keys = keys; + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + { + int slot = ServerSelectionStrategy.NoSlot; + for (int i = 0; i < keys.Length; i++) + { + slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); + } + return slot; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(command, keys.Length); + for (int i = 0; i < keys.Length; i++) + { + physical.Write(keys[i]); + } + } + public override int ArgCount => keys.Length; + } + + private sealed class CommandKeyValueMessage : CommandKeyBase + { + private readonly RedisValue value; + public CommandKeyValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value) : base(db, flags, command, key) + { + value.AssertNotNull(); + this.value = value; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 2); + physical.Write(Key); + physical.WriteBulkString(value); + } + public override int ArgCount => 2; + } + + private sealed class CommandKeyValuesKeyMessage : CommandKeyBase + { + private readonly RedisKey key1; + private readonly RedisValue[] values; + public CommandKeyValuesKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, RedisValue[] values, in RedisKey key1) : base(db, flags, command, key0) + { + for (int i = 0; i < values.Length; i++) + { + values[i].AssertNotNull(); + } + this.values = values; + key1.AssertNotNull(); + this.key1 = key1; + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + { + var slot = base.GetHashSlot(serverSelectionStrategy); + return serverSelectionStrategy.CombineSlot(slot, key1); + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, values.Length + 2); + physical.Write(Key); + for (int i = 0; i < values.Length; i++) physical.WriteBulkString(values[i]); + physical.Write(key1); + } + public override int ArgCount => values.Length + 2; + } + + private sealed class CommandKeyValuesMessage : CommandKeyBase + { + private readonly RedisValue[] values; + public CommandKeyValuesMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisValue[] values) : base(db, flags, command, key) + { + for (int i = 0; i < values.Length; i++) + { + values[i].AssertNotNull(); + } + this.values = values; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, values.Length + 1); + physical.Write(Key); + for (int i = 0; i < values.Length; i++) physical.WriteBulkString(values[i]); + } + public override int ArgCount => values.Length + 1; + } + + private sealed class CommandKeyKeyValuesMessage : CommandKeyBase + { + private readonly RedisKey key1; + private readonly RedisValue[] values; + public CommandKeyKeyValuesMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisKey key1, RedisValue[] values) : base(db, flags, command, key) + { + for (int i = 0; i < values.Length; i++) + { + values[i].AssertNotNull(); + } + + key1.AssertNotNull(); + this.key1 = key1; + this.values = values; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, values.Length + 2); + physical.Write(Key); + physical.Write(key1); + for (int i = 0; i < values.Length; i++) physical.WriteBulkString(values[i]); + } + public override int ArgCount => values.Length + 1; + } + + private sealed class CommandKeyValueValueValuesMessage : CommandKeyBase + { + private readonly RedisValue value0; + private readonly RedisValue value1; + private readonly RedisValue[] values; + public CommandKeyValueValueValuesMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, RedisValue[] values) : base(db, flags, command, key) + { + for (int i = 0; i < values.Length; i++) + { + values[i].AssertNotNull(); + } + + value0.AssertNotNull(); + value1.AssertNotNull(); + this.value0 = value0; + this.value1 = value1; + this.values = values; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, values.Length + 3); + physical.Write(Key); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + for (int i = 0; i < values.Length; i++) physical.WriteBulkString(values[i]); + } + public override int ArgCount => values.Length + 3; + } + + private sealed class CommandKeyValueValueMessage : CommandKeyBase + { + private readonly RedisValue value0, value1; + public CommandKeyValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1) : base(db, flags, command, key) + { + value0.AssertNotNull(); + value1.AssertNotNull(); + this.value0 = value0; + this.value1 = value1; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 3); + physical.Write(Key); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + } + public override int ArgCount => 3; + } + + private sealed class CommandKeyValueValueValueMessage : CommandKeyBase + { + private readonly RedisValue value0, value1, value2; + public CommandKeyValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2) : base(db, flags, command, key) + { + value0.AssertNotNull(); + value1.AssertNotNull(); + value2.AssertNotNull(); + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 4); + physical.Write(Key); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + physical.WriteBulkString(value2); + } + public override int ArgCount => 4; + } + + private sealed class CommandKeyValueValueValueValueMessage : CommandKeyBase + { + private readonly RedisValue value0, value1, value2, value3; + public CommandKeyValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3) : base(db, flags, command, key) + { + value0.AssertNotNull(); + value1.AssertNotNull(); + value2.AssertNotNull(); + value3.AssertNotNull(); + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 5); + physical.Write(Key); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + physical.WriteBulkString(value2); + physical.WriteBulkString(value3); + } + public override int ArgCount => 5; + } + + private sealed class CommandKeyValueValueValueValueValueMessage : CommandKeyBase + { + private readonly RedisValue value0, value1, value2, value3, value4; + public CommandKeyValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) : base(db, flags, command, key) + { + value0.AssertNotNull(); + value1.AssertNotNull(); + value2.AssertNotNull(); + value3.AssertNotNull(); + value4.AssertNotNull(); + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + this.value4 = value4; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 6); + physical.Write(Key); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + physical.WriteBulkString(value2); + physical.WriteBulkString(value3); + physical.WriteBulkString(value4); + } + public override int ArgCount => 6; + } + + private sealed class CommandKeyValueValueValueValueValueValueMessage : CommandKeyBase + { + private readonly RedisValue value0, value1, value2, value3, value4, value5; + + public CommandKeyValueValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5) : base(db, flags, command, key) + { + value0.AssertNotNull(); + value1.AssertNotNull(); + value2.AssertNotNull(); + value3.AssertNotNull(); + value4.AssertNotNull(); + value5.AssertNotNull(); + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + this.value4 = value4; + this.value5 = value5; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, ArgCount); + physical.Write(Key); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + physical.WriteBulkString(value2); + physical.WriteBulkString(value3); + physical.WriteBulkString(value4); + physical.WriteBulkString(value5); + } + public override int ArgCount => 7; + } + + private sealed class CommandKeyValueValueValueValueValueValueValueMessage : CommandKeyBase + { + private readonly RedisValue value0, value1, value2, value3, value4, value5, value6; + + public CommandKeyValueValueValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5, in RedisValue value6) : base(db, flags, command, key) + { + value0.AssertNotNull(); + value1.AssertNotNull(); + value2.AssertNotNull(); + value3.AssertNotNull(); + value4.AssertNotNull(); + value5.AssertNotNull(); + value6.AssertNotNull(); + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + this.value4 = value4; + this.value5 = value5; + this.value6 = value6; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, ArgCount); + physical.Write(Key); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + physical.WriteBulkString(value2); + physical.WriteBulkString(value3); + physical.WriteBulkString(value4); + physical.WriteBulkString(value5); + physical.WriteBulkString(value6); + } + public override int ArgCount => 8; + } + + private sealed class CommandKeyKeyValueValueMessage : CommandKeyBase + { + private readonly RedisValue value0, value1; + private readonly RedisKey key1; + + public CommandKeyKeyValueValueMessage( + int db, + CommandFlags flags, + RedisCommand command, + in RedisKey key0, + in RedisKey key1, + in RedisValue value0, + in RedisValue value1) : base(db, flags, command, key0) + { + key1.AssertNotNull(); + value0.AssertNotNull(); + value1.AssertNotNull(); + this.key1 = key1; + this.value0 = value0; + this.value1 = value1; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, ArgCount); + physical.Write(Key); + physical.Write(key1); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + } + + public override int ArgCount => 4; + } + + private sealed class CommandKeyKeyValueValueValueMessage : CommandKeyBase + { + private readonly RedisValue value0, value1, value2; + private readonly RedisKey key1; + + public CommandKeyKeyValueValueValueMessage( + int db, + CommandFlags flags, + RedisCommand command, + in RedisKey key0, + in RedisKey key1, + in RedisValue value0, + in RedisValue value1, + in RedisValue value2) : base(db, flags, command, key0) + { + key1.AssertNotNull(); + value0.AssertNotNull(); + value1.AssertNotNull(); + value2.AssertNotNull(); + this.key1 = key1; + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, ArgCount); + physical.Write(Key); + physical.Write(key1); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + physical.WriteBulkString(value2); + } + + public override int ArgCount => 5; + } + + private sealed class CommandKeyKeyValueValueValueValueMessage : CommandKeyBase + { + private readonly RedisValue value0, value1, value2, value3; + private readonly RedisKey key1; + + public CommandKeyKeyValueValueValueValueMessage( + int db, + CommandFlags flags, + RedisCommand command, + in RedisKey key0, + in RedisKey key1, + in RedisValue value0, + in RedisValue value1, + in RedisValue value2, + in RedisValue value3) : base(db, flags, command, key0) + { + key1.AssertNotNull(); + value0.AssertNotNull(); + value1.AssertNotNull(); + value2.AssertNotNull(); + value3.AssertNotNull(); + this.key1 = key1; + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, ArgCount); + physical.Write(Key); + physical.Write(key1); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + physical.WriteBulkString(value2); + physical.WriteBulkString(value3); + } + + public override int ArgCount => 6; + } + + private sealed class CommandKeyKeyValueValueValueValueValueMessage : CommandKeyBase + { + private readonly RedisValue value0, value1, value2, value3, value4; + private readonly RedisKey key1; + + public CommandKeyKeyValueValueValueValueValueMessage( + int db, + CommandFlags flags, + RedisCommand command, + in RedisKey key0, + in RedisKey key1, + in RedisValue value0, + in RedisValue value1, + in RedisValue value2, + in RedisValue value3, + in RedisValue value4) : base(db, flags, command, key0) + { + key1.AssertNotNull(); + value0.AssertNotNull(); + value1.AssertNotNull(); + value2.AssertNotNull(); + value3.AssertNotNull(); + value4.AssertNotNull(); + this.key1 = key1; + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + this.value4 = value4; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, ArgCount); + physical.Write(Key); + physical.Write(key1); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + physical.WriteBulkString(value2); + physical.WriteBulkString(value3); + physical.WriteBulkString(value4); + } + + public override int ArgCount => 7; + } + + private sealed class CommandKeyKeyValueValueValueValueValueValueMessage : CommandKeyBase + { + private readonly RedisValue value0, value1, value2, value3, value4, value5; + private readonly RedisKey key1; + + public CommandKeyKeyValueValueValueValueValueValueMessage( + int db, + CommandFlags flags, + RedisCommand command, + in RedisKey key0, + in RedisKey key1, + in RedisValue value0, + in RedisValue value1, + in RedisValue value2, + in RedisValue value3, + in RedisValue value4, + in RedisValue value5) : base(db, flags, command, key0) + { + key1.AssertNotNull(); + value0.AssertNotNull(); + value1.AssertNotNull(); + value2.AssertNotNull(); + value3.AssertNotNull(); + value4.AssertNotNull(); + value5.AssertNotNull(); + this.key1 = key1; + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + this.value4 = value4; + this.value5 = value5; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, ArgCount); + physical.Write(Key); + physical.Write(key1); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + physical.WriteBulkString(value2); + physical.WriteBulkString(value3); + physical.WriteBulkString(value4); + physical.WriteBulkString(value5); + } + + public override int ArgCount => 8; + } + + private sealed class CommandKeyKeyValueValueValueValueValueValueValueMessage : CommandKeyBase + { + private readonly RedisValue value0, value1, value2, value3, value4, value5, value6; + private readonly RedisKey key1; + + public CommandKeyKeyValueValueValueValueValueValueValueMessage( + int db, + CommandFlags flags, + RedisCommand command, + in RedisKey key0, + in RedisKey key1, + in RedisValue value0, + in RedisValue value1, + in RedisValue value2, + in RedisValue value3, + in RedisValue value4, + in RedisValue value5, + in RedisValue value6) : base(db, flags, command, key0) + { + key1.AssertNotNull(); + value0.AssertNotNull(); + value1.AssertNotNull(); + value2.AssertNotNull(); + value3.AssertNotNull(); + value4.AssertNotNull(); + value5.AssertNotNull(); + value6.AssertNotNull(); + this.key1 = key1; + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + this.value4 = value4; + this.value5 = value5; + this.value6 = value6; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, ArgCount); + physical.Write(Key); + physical.Write(key1); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + physical.WriteBulkString(value2); + physical.WriteBulkString(value3); + physical.WriteBulkString(value4); + physical.WriteBulkString(value5); + physical.WriteBulkString(value6); + } + + public override int ArgCount => 9; + } + + private sealed class CommandMessage : Message + { + public CommandMessage(int db, CommandFlags flags, RedisCommand command) : base(db, flags, command) { } + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 0); + } + public override int ArgCount => 0; + } + + private sealed class CommandSlotValuesMessage : Message + { + private readonly int slot; + private readonly RedisValue[] values; + + public CommandSlotValuesMessage(int db, int slot, CommandFlags flags, RedisCommand command, RedisValue[] values) + : base(db, flags, command) + { + this.slot = slot; + for (int i = 0; i < values.Length; i++) + { + values[i].AssertNotNull(); + } + this.values = values; + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) => slot; + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(command, values.Length); + for (int i = 0; i < values.Length; i++) + { + physical.WriteBulkString(values[i]); + } + } + public override int ArgCount => values.Length; + } + + private sealed class CommandValueChannelMessage : CommandChannelBase + { + private readonly RedisValue value; + public CommandValueChannelMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value, in RedisChannel channel) : base(db, flags, command, channel) + { + value.AssertNotNull(); + this.value = value; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 2); + physical.WriteBulkString(value); + physical.Write(Channel); + } + public override int ArgCount => 2; + } + + private sealed class CommandValueKeyMessage : CommandKeyBase + { + private readonly RedisValue value; + + public CommandValueKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value, in RedisKey key) : base(db, flags, command, key) + { + value.AssertNotNull(); + this.value = value; + } + + public override void AppendStormLog(StringBuilder sb) + { + base.AppendStormLog(sb); + sb.Append(" (").Append((string?)value).Append(')'); + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 2); + physical.WriteBulkString(value); + physical.Write(Key); + } + public override int ArgCount => 2; + } + + private sealed class CommandValueMessage : Message + { + private readonly RedisValue value; + public CommandValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value) : base(db, flags, command) + { + value.AssertNotNull(); + this.value = value; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 1); + physical.WriteBulkString(value); + } + public override int ArgCount => 1; + } + + private sealed class CommandValueValueMessage : Message + { + private readonly RedisValue value0, value1; + public CommandValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1) : base(db, flags, command) + { + value0.AssertNotNull(); + value1.AssertNotNull(); + this.value0 = value0; + this.value1 = value1; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 2); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + } + public override int ArgCount => 2; + } + + private sealed class CommandValueValueValueMessage : Message + { + private readonly RedisValue value0, value1, value2; + public CommandValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2) : base(db, flags, command) + { + value0.AssertNotNull(); + value1.AssertNotNull(); + value2.AssertNotNull(); + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 3); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + physical.WriteBulkString(value2); + } + public override int ArgCount => 3; + } + + private sealed class CommandValueValueValueValueValueMessage : Message + { + private readonly RedisValue value0, value1, value2, value3, value4; + public CommandValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) : base(db, flags, command) + { + value0.AssertNotNull(); + value1.AssertNotNull(); + value2.AssertNotNull(); + value3.AssertNotNull(); + value4.AssertNotNull(); + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + this.value4 = value4; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 5); + physical.WriteBulkString(value0); + physical.WriteBulkString(value1); + physical.WriteBulkString(value2); + physical.WriteBulkString(value3); + physical.WriteBulkString(value4); + } + public override int ArgCount => 5; + } + + private sealed class SelectMessage : Message + { + public SelectMessage(int db, CommandFlags flags) : base(db, flags, RedisCommand.SELECT) + { + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 1); + physical.WriteBulkString(Db); + } + public override int ArgCount => 1; + } + + // this is a placeholder message for use when (for example) unable to queue the + // connection queue due to a lock timeout + internal sealed class UnknownMessage : Message + { + public static UnknownMessage Instance { get; } = new(); + private UnknownMessage() : base(0, CommandFlags.None, RedisCommand.UNKNOWN) { } + public override int ArgCount => 0; + protected override void WriteImpl(PhysicalConnection physical) => throw new InvalidOperationException("This message cannot be written"); + } + } +} diff --git a/src/StackExchange.Redis/MessageCompletable.cs b/src/StackExchange.Redis/MessageCompletable.cs new file mode 100644 index 000000000..8f4737943 --- /dev/null +++ b/src/StackExchange.Redis/MessageCompletable.cs @@ -0,0 +1,53 @@ +using System; +using System.Text; +using Pipelines.Sockets.Unofficial; + +namespace StackExchange.Redis +{ + internal sealed class MessageCompletable : ICompletable + { + private readonly RedisChannel channel; + + private readonly Action handler; + + private readonly RedisValue message; + public MessageCompletable(RedisChannel channel, RedisValue message, Action handler) + { + this.channel = channel; + this.message = message; + this.handler = handler; + } + + public override string? ToString() => (string?)channel; + + public bool TryComplete(bool isAsync) + { + if (isAsync) + { + if (handler != null) + { + ConnectionMultiplexer.TraceWithoutContext("Invoking (async)...: " + (string?)channel, "Subscription"); + if (handler.IsSingle()) + { + try { handler(channel, message); } catch { } + } + else + { + foreach (var sub in handler.AsEnumerable()) + { + try { sub.Invoke(channel, message); } catch { } + } + } + ConnectionMultiplexer.TraceWithoutContext("Invoke complete (async)", "Subscription"); + } + return true; + } + else + { + return handler == null; // anything async to do? + } + } + + void ICompletable.AppendStormLog(StringBuilder sb) => sb.Append("event, pub/sub: ").Append((string?)channel); + } +} diff --git a/src/StackExchange.Redis/NullableHacks.cs b/src/StackExchange.Redis/NullableHacks.cs new file mode 100644 index 000000000..5f8969c73 --- /dev/null +++ b/src/StackExchange.Redis/NullableHacks.cs @@ -0,0 +1,148 @@ +// https://github.com/dotnet/runtime/blob/527f9ae88a0ee216b44d556f9bdc84037fe0ebda/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs + +#pragma warning disable +#define INTERNAL_NULLABLE_ATTRIBUTES + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ +#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 + /// Specifies that null is allowed as an input even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class AllowNullAttribute : Attribute { } + + /// Specifies that null is disallowed as an input even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class DisallowNullAttribute : Attribute { } + + /// Specifies that an output may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class MaybeNullAttribute : Attribute { } + + /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class NotNullAttribute : Attribute { } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class MaybeNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that the output will be non-null if the named parameter is non-null. + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] + internal sealed class NotNullIfNotNullAttribute : Attribute + { + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + + /// Gets the associated parameter name. + public string ParameterName { get; } + } + + /// Applied to a method that will never return under any circumstance. + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + internal sealed class DoesNotReturnAttribute : Attribute { } + + /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class DoesNotReturnIfAttribute : Attribute + { + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + + /// Gets the condition parameter value. + public bool ParameterValue { get; } + } +#endif + +#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 || NETCOREAPP3_1 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 + /// Specifies that the method or property will ensure that the listed field and property members have not-null values. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullAttribute : Attribute + { + /// Initializes the attribute with a field or property member. + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullAttribute(string member) => Members = new[] { member }; + + /// Initializes the attribute with the list of field and property members. + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullAttribute(params string[] members) => Members = members; + + /// Gets field or property member names. + public string[] Members { get; } + } + + /// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = new[] { member }; + } + + /// Initializes the attribute with the specified return value condition and list of field and property members. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + + /// Gets the return value condition. + public bool ReturnValue { get; } + + /// Gets field or property member names. + public string[] Members { get; } + } +#endif +} diff --git a/src/StackExchange.Redis/Obsoletions.cs b/src/StackExchange.Redis/Obsoletions.cs new file mode 100644 index 000000000..44ede249b --- /dev/null +++ b/src/StackExchange.Redis/Obsoletions.cs @@ -0,0 +1,7 @@ +namespace StackExchange.Redis; + +internal static class Obsoletions +{ + public const string LegacyFormatterImplMessage = "This API supports obsolete formatter-based serialization. It should not be called or extended by application code."; + public const string LegacyFormatterImplDiagId = "SYSLIB0051"; +} diff --git a/src/StackExchange.Redis/PerfCounterHelper.cs b/src/StackExchange.Redis/PerfCounterHelper.cs new file mode 100644 index 000000000..8d8b6fbb0 --- /dev/null +++ b/src/StackExchange.Redis/PerfCounterHelper.cs @@ -0,0 +1,34 @@ +using System.Threading; + +namespace StackExchange.Redis +{ + internal static class PerfCounterHelper + { + internal static string GetThreadPoolAndCPUSummary() + { + GetThreadPoolStats(out string iocp, out string worker, out string? workItems); + return $"IOCP: {iocp}, WORKER: {worker}, POOL: {workItems ?? "n/a"}"; + } + + internal static int GetThreadPoolStats(out string iocp, out string worker, out string? workItems) + { + ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxIoThreads); + ThreadPool.GetAvailableThreads(out int freeWorkerThreads, out int freeIoThreads); + ThreadPool.GetMinThreads(out int minWorkerThreads, out int minIoThreads); + + int busyIoThreads = maxIoThreads - freeIoThreads; + int busyWorkerThreads = maxWorkerThreads - freeWorkerThreads; + + iocp = $"(Busy={busyIoThreads},Free={freeIoThreads},Min={minIoThreads},Max={maxIoThreads})"; + worker = $"(Busy={busyWorkerThreads},Free={freeWorkerThreads},Min={minWorkerThreads},Max={maxWorkerThreads})"; + +#if NETCOREAPP + workItems = $"(Threads={ThreadPool.ThreadCount},QueuedItems={ThreadPool.PendingWorkItemCount},CompletedItems={ThreadPool.CompletedWorkItemCount},Timers={Timer.ActiveCount})"; +#else + workItems = null; +#endif + + return busyWorkerThreads; + } + } +} diff --git a/src/StackExchange.Redis/PhysicalBridge.cs b/src/StackExchange.Redis/PhysicalBridge.cs new file mode 100644 index 000000000..1a38b7d89 --- /dev/null +++ b/src/StackExchange.Redis/PhysicalBridge.cs @@ -0,0 +1,1693 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +#if !NETCOREAPP +using Pipelines.Sockets.Unofficial.Threading; +using static Pipelines.Sockets.Unofficial.Threading.MutexSlim; +#endif + +namespace StackExchange.Redis +{ + internal sealed class PhysicalBridge : IDisposable + { + internal readonly string Name; + + private const int ProfileLogSamples = 10; + + private const double ProfileLogSeconds = (1000 /* ms */ * ProfileLogSamples) / 1000.0; + + private static readonly Message ReusableAskingCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.ASKING); + + private readonly long[] profileLog = new long[ProfileLogSamples]; + + /// + /// We have 1 queue in play on this bridge. + /// We're bypassing the queue for handshake events that go straight to the socket. + /// Everything else that's not an internal call goes into the queue if there is a queue. + /// + /// + /// In a later release we want to remove per-server events from this queue completely and shunt queued messages + /// to another capable primary connection if one is available to process them faster (order is already hosed). + /// For now, simplicity in: queue it all, replay or timeout it all. + /// + private readonly ConcurrentQueue _backlog = new(); + private bool BacklogHasItems => !_backlog.IsEmpty; + private int _backlogProcessorIsRunning = 0; + private int _backlogCurrentEnqueued = 0; + private long _backlogTotalEnqueued = 0; + + private int activeWriters = 0; + private int beating; + private int failConnectCount = 0; + private volatile bool isDisposed; + private volatile bool shouldResetConnectionRetryCount; + private long nonPreferredEndpointCount; + + // private volatile int missedHeartbeats; + private long operationCount, socketCount; + private volatile PhysicalConnection? physical; + + private long profileLastLog; + private int profileLogIndex; + + private volatile bool reportNextFailure = true, reconfigureNextFailure = false; + + private volatile int state = (int)State.Disconnected; + + internal long? ConnectionId => physical?.ConnectionId; + +#if NETCOREAPP + private readonly SemaphoreSlim _singleWriterMutex = new(1, 1); +#else + private readonly MutexSlim _singleWriterMutex; +#endif + + internal string? PhysicalName => physical?.ToString(); + + private uint _nextHighIntegrityToken; // zero means not enabled + + public DateTime? ConnectedAt { get; private set; } + + public PhysicalBridge(ServerEndPoint serverEndPoint, ConnectionType type, int timeoutMilliseconds) + { + ServerEndPoint = serverEndPoint; + ConnectionType = type; + Multiplexer = serverEndPoint.Multiplexer; + Name = Format.ToString(serverEndPoint.EndPoint) + "/" + ConnectionType.ToString(); + TimeoutMilliseconds = timeoutMilliseconds; +#if !NETCOREAPP + _singleWriterMutex = new MutexSlim(timeoutMilliseconds: timeoutMilliseconds); +#endif + if (type == ConnectionType.Interactive && Multiplexer.RawConfig.HighIntegrity) + { + // we just need this to be non-zero to enable tracking + _nextHighIntegrityToken = 1; + } + } + + private readonly int TimeoutMilliseconds; + + public enum State : byte + { + Connecting, + ConnectedEstablishing, + ConnectedEstablished, + Disconnected, + } + + public Exception? LastException { get; private set; } + + public ConnectionType ConnectionType { get; } + + public bool IsConnected => state == (int)State.ConnectedEstablished; + + public bool IsConnecting => state == (int)State.ConnectedEstablishing || state == (int)State.Connecting; + + public ConnectionMultiplexer Multiplexer { get; } + + public ServerEndPoint ServerEndPoint { get; } + + public long SubscriptionCount => physical?.SubscriptionCount ?? 0; + + internal State ConnectionState => (State)state; + internal bool IsBeating => Interlocked.CompareExchange(ref beating, 0, 0) == 1; + + internal long OperationCount => Interlocked.Read(ref operationCount); + + public RedisCommand LastCommand { get; private set; } + + /// + /// If we have (or had) a connection, report the protocol being used. + /// + /// The value remains after disconnect, so that appropriate follow-up actions (pub/sub etc) can work reliably. + public RedisProtocol? Protocol => _protocol == 0 ? default(RedisProtocol?) : _protocol; + private RedisProtocol _protocol; // note starts at zero, not RESP2 + internal void SetProtocol(RedisProtocol protocol) => _protocol = protocol; + + public void Dispose() + { + isDisposed = true; + // If there's anything in the backlog and we're being torn down - exfil it immediately (e.g. so all awaitables complete) + AbandonPendingBacklog(new ObjectDisposedException("Connection is being disposed")); + try + { + _backlogAutoReset?.Set(); + _backlogAutoReset?.Dispose(); + } + catch { } + using (var tmp = physical) + { + physical = null; + } + GC.SuppressFinalize(this); + } + + ~PhysicalBridge() + { + isDisposed = true; // make damn sure we don't true to resurrect + + // If there's anything in the backlog and we're being torn down - exfil it immediately (e.g. so all awaitables complete) + AbandonPendingBacklog(new ObjectDisposedException("Connection is being finalized")); + + // shouldn't *really* touch managed objects + // in a finalizer, but we need to kill that socket, + // and this is the first place that isn't going to + // be rooted by the socket async bits + try + { + var tmp = physical; + physical = null; + tmp?.Shutdown(); + } + catch { } + } + public void ReportNextFailure() + { + reportNextFailure = true; + } + + public override string ToString() => ConnectionType + "/" + Format.ToString(ServerEndPoint.EndPoint); + + private WriteResult QueueOrFailMessage(Message message) + { + // If it's an internal call that's not a QUIT + // or we're allowed to queue in general, then queue + if (message.IsInternalCall || Multiplexer.RawConfig.BacklogPolicy.QueueWhileDisconnected) + { + // Let's just never ever queue a QUIT message + if (message.Command != RedisCommand.QUIT) + { + message.SetEnqueued(null); + BacklogEnqueue(message); + // Note: we don't start a worker on each message here + return WriteResult.Success; // Successfully queued, so indicate success + } + } + + // Anything else goes in the bin - we're just not ready for you yet + message.Cancel(); + Multiplexer.OnMessageFaulted(message, null); + message.Complete(); + return WriteResult.NoConnectionAvailable; + } + + private WriteResult FailDueToNoConnection(Message message) + { + message.Cancel(); + Multiplexer.OnMessageFaulted(message, null); + message.Complete(); + return WriteResult.NoConnectionAvailable; + } + + [Obsolete("prefer async")] + public WriteResult TryWriteSync(Message message, bool isReplica) + { + if (isDisposed) throw new ObjectDisposedException(Name); + if (!IsConnected) return QueueOrFailMessage(message); + + var physical = this.physical; + if (physical == null) + { + // If we're not connected yet and supposed to, queue it up + if (Multiplexer.RawConfig.BacklogPolicy.QueueWhileDisconnected) + { + if (TryPushToBacklog(message, onlyIfExists: false)) + { + message.SetEnqueued(null); + return WriteResult.Success; + } + } + return FailDueToNoConnection(message); + } + var result = WriteMessageTakingWriteLockSync(physical, message); + LogNonPreferred(message.Flags, isReplica); + return result; + } + + public ValueTask TryWriteAsync(Message message, bool isReplica, bool bypassBacklog = false) + { + if (isDisposed) throw new ObjectDisposedException(Name); + if (!IsConnected && !bypassBacklog) return new ValueTask(QueueOrFailMessage(message)); + + var physical = this.physical; + if (physical == null) + { + // If we're not connected yet and supposed to, queue it up + if (!bypassBacklog && Multiplexer.RawConfig.BacklogPolicy.QueueWhileDisconnected) + { + if (TryPushToBacklog(message, onlyIfExists: false)) + { + message.SetEnqueued(null); + return new ValueTask(WriteResult.Success); + } + } + return new ValueTask(FailDueToNoConnection(message)); + } + + var result = WriteMessageTakingWriteLockAsync(physical, message, bypassBacklog: bypassBacklog); + LogNonPreferred(message.Flags, isReplica); + return result; + } + + internal void AppendProfile(StringBuilder sb) + { + var clone = new long[ProfileLogSamples + 1]; + for (int i = 0; i < ProfileLogSamples; i++) + { + clone[i] = Interlocked.Read(ref profileLog[i]); + } + clone[ProfileLogSamples] = Interlocked.Read(ref operationCount); + Array.Sort(clone); + sb.Append(' ').Append(clone[0]); + for (int i = 1; i < clone.Length; i++) + { + if (clone[i] != clone[i - 1]) + { + sb.Append('+').Append(clone[i] - clone[i - 1]); + } + } + if (clone[0] != clone[ProfileLogSamples]) + { + sb.Append('=').Append(clone[ProfileLogSamples]); + } + double rate = (clone[ProfileLogSamples] - clone[0]) / ProfileLogSeconds; + sb.Append(" (").Append(rate.ToString("N2")).Append(" ops/s; spans ").Append(ProfileLogSeconds).Append("s)"); + } + + internal void GetCounters(ConnectionCounters counters) + { + counters.OperationCount = OperationCount; + counters.SocketCount = Interlocked.Read(ref socketCount); + counters.WriterCount = Interlocked.CompareExchange(ref activeWriters, 0, 0); + counters.NonPreferredEndpointCount = Interlocked.Read(ref nonPreferredEndpointCount); + physical?.GetCounters(counters); + } + + internal readonly struct BridgeStatus + { + /// + /// Number of messages sent since the last heartbeat was processed. + /// + public int MessagesSinceLastHeartbeat { get; init; } + + /// + /// The time this connection was connected at, if it's connected currently. + /// + public DateTime? ConnectedAt { get; init; } + + /// + /// Whether the pipe writer is currently active. + /// + public bool IsWriterActive { get; init; } + + /// + /// Status of the currently processing backlog, if any. + /// + public BacklogStatus BacklogStatus { get; init; } + + /// + /// The number of messages that are in the backlog queue (waiting to be sent when the connection is healthy again). + /// + public int BacklogMessagesPending { get; init; } + + /// + /// The number of messages that are in the backlog queue (waiting to be sent when the connection is healthy again). + /// + public int BacklogMessagesPendingCounter { get; init; } + + /// + /// The number of messages ever added to the backlog queue in the life of this connection. + /// + public long TotalBacklogMessagesQueued { get; init; } + + /// + /// Status for the underlying . + /// + public PhysicalConnection.ConnectionStatus Connection { get; init; } + + /// + /// The default bridge stats, notable *not* the same as default since initializers don't run. + /// + public static BridgeStatus Zero { get; } = new() { Connection = PhysicalConnection.ConnectionStatus.Zero }; + + public override string ToString() => + $"MessagesSinceLastHeartbeat: {MessagesSinceLastHeartbeat}, ConnectedAt: {ConnectedAt?.ToString("u") ?? "n/a"}, Writer: {(IsWriterActive ? "Active" : "Inactive")}, BacklogStatus: {BacklogStatus}, BacklogMessagesPending: (Queue: {BacklogMessagesPending}, Counter: {BacklogMessagesPendingCounter}), TotalBacklogMessagesQueued: {TotalBacklogMessagesQueued}, Connection: ({Connection})"; + } + + internal BridgeStatus GetStatus() => new() + { + MessagesSinceLastHeartbeat = (int)(Interlocked.Read(ref operationCount) - Interlocked.Read(ref profileLastLog)), + ConnectedAt = ConnectedAt, +#if NETCOREAPP + IsWriterActive = _singleWriterMutex.CurrentCount == 0, +#else + IsWriterActive = !_singleWriterMutex.IsAvailable, +#endif + BacklogMessagesPending = _backlog.Count, + BacklogMessagesPendingCounter = Volatile.Read(ref _backlogCurrentEnqueued), + BacklogStatus = _backlogStatus, + TotalBacklogMessagesQueued = _backlogTotalEnqueued, + Connection = physical?.GetStatus() ?? PhysicalConnection.ConnectionStatus.Default, + }; + + internal string GetStormLog() + { + var sb = new StringBuilder("Storm log for ").Append(Format.ToString(ServerEndPoint.EndPoint)).Append(" / ").Append(ConnectionType) + .Append(" at ").Append(DateTime.UtcNow) + .AppendLine().AppendLine(); + physical?.GetStormLog(sb); + sb.Append("Circular op-count snapshot:"); + AppendProfile(sb); + sb.AppendLine(); + return sb.ToString(); + } + + internal void IncrementOpCount() + { + Interlocked.Increment(ref operationCount); + } + + /// + /// Sends a keepalive message (ECHO or PING) to keep connections alive and check validity of response. + /// + /// Whether to run even then the connection isn't idle. + internal void KeepAlive(bool forceRun = false) + { + if (!forceRun && !(physical?.IsIdle() ?? false)) return; // don't pile on if already doing something + + var commandMap = Multiplexer.CommandMap; + Message? msg = null; + var features = ServerEndPoint.GetFeatures(); + switch (ConnectionType) + { + case ConnectionType.Interactive: + msg = ServerEndPoint.GetTracerMessage(false); + msg.SetSource(ResultProcessor.Tracer, null); + break; + case ConnectionType.Subscription: + if (commandMap.IsAvailable(RedisCommand.PING) && features.PingOnSubscriber) + { + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.PING); + msg.SetForSubscriptionBridge(); + msg.SetSource(ResultProcessor.Tracer, null); + } + else if (commandMap.IsAvailable(RedisCommand.UNSUBSCRIBE)) + { + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.UNSUBSCRIBE, RedisChannel.Literal(Multiplexer.UniqueId)); + msg.SetSource(ResultProcessor.TrackSubscriptions, null); + } + break; + } + + if (msg != null) + { + msg.SetInternalCall(); + Multiplexer.Trace("Enqueue: " + msg); + Multiplexer.OnInfoMessage($"heartbeat ({physical?.LastWriteSecondsAgo}s >= {ServerEndPoint.WriteEverySeconds}s, {physical?.GetSentAwaitingResponseCount()} waiting) '{msg.CommandAndKey}' on '{PhysicalName}' (v{features.Version})"); + physical?.UpdateLastWriteTime(); // preemptively +#pragma warning disable CS0618 // Type or member is obsolete + var result = TryWriteSync(msg, ServerEndPoint.IsReplica); +#pragma warning restore CS0618 + + if (result != WriteResult.Success) + { + var ex = Multiplexer.GetException(result, msg, ServerEndPoint); + OnInternalError(ex); + } + } + } + + internal async Task OnConnectedAsync(PhysicalConnection connection, ILogger? log) + { + Trace("OnConnected"); + if (physical == connection && !isDisposed && ChangeState(State.Connecting, State.ConnectedEstablishing)) + { + ConnectedAt ??= DateTime.UtcNow; + await ServerEndPoint.OnEstablishingAsync(connection, log).ForAwait(); + log?.LogInformationOnEstablishingComplete(new(ServerEndPoint.EndPoint)); + } + else + { + try + { + connection.Dispose(); + } + catch { } + } + } + + internal void ResetNonConnected() + { + var tmp = physical; + if (tmp != null && state != (int)State.ConnectedEstablished) + { + tmp.RecordConnectionFailed(ConnectionFailureType.UnableToConnect); + } + TryConnect(null); + } + + internal void OnConnectionFailed(PhysicalConnection connection, ConnectionFailureType failureType, Exception innerException, bool wasRequested) + { + if (wasRequested) + { + Multiplexer.Logger?.LogInformationConnectionFailureRequested(innerException, innerException.Message); + } + else + { + Multiplexer.Logger?.LogErrorConnectionIssue(innerException, innerException.Message); + } + Trace($"OnConnectionFailed: {connection}"); + // If we're configured to, fail all pending backlogged messages + if (Multiplexer.RawConfig.BacklogPolicy?.AbortPendingOnConnectionFailure == true) + { + AbandonPendingBacklog(innerException); + } + + if (reportNextFailure) + { + LastException = innerException; + reportNextFailure = false; // until it is restored + var endpoint = ServerEndPoint.EndPoint; + Multiplexer.OnConnectionFailed(endpoint, ConnectionType, failureType, innerException, reconfigureNextFailure, connection?.ToString()); + } + } + + internal void OnDisconnected(ConnectionFailureType failureType, PhysicalConnection? connection, out bool isCurrent, out State oldState) + { + Trace($"OnDisconnected: {failureType}"); + + oldState = default(State); // only defined when isCurrent = true + ConnectedAt = default; + if (isCurrent = physical == connection) + { + Trace("Bridge noting disconnect from active connection" + (isDisposed ? " (disposed)" : "")); + oldState = ChangeState(State.Disconnected); + physical = null; + if (oldState == State.ConnectedEstablished && !ServerEndPoint.IsReplica) + { + // if the disconnected endpoint was a primary endpoint run info replication + // more frequently on it's replica with exponential increments + foreach (var r in ServerEndPoint.Replicas) + { + r.ForceExponentialBackoffReplicationCheck(); + } + } + ServerEndPoint.OnDisconnected(this); + + if (!isDisposed && Interlocked.Increment(ref failConnectCount) == 1) + { + TryConnect(null); // try to connect immediately + } + } + else if (physical == null) + { + Trace("Bridge noting disconnect (already terminated)"); + } + else + { + Trace("Bridge noting disconnect, but from different connection"); + } + } + + private void AbandonPendingBacklog(Exception ex) + { + while (BacklogTryDequeue(out Message? next)) + { + Multiplexer.OnMessageFaulted(next, ex); + next.SetExceptionAndComplete(ex, this); + } + } + + internal void OnFullyEstablished(PhysicalConnection connection, string source) + { + Trace("OnFullyEstablished"); + connection.SetIdle(); + if (physical == connection && !isDisposed && ChangeState(State.ConnectedEstablishing, State.ConnectedEstablished)) + { + reportNextFailure = reconfigureNextFailure = true; + LastException = null; + Interlocked.Exchange(ref failConnectCount, 0); + ServerEndPoint.OnFullyEstablished(connection, source); + + // do we have pending system things to do? + if (BacklogHasItems) + { + StartBacklogProcessor(); + } + + if (ConnectionType == ConnectionType.Interactive) ServerEndPoint.CheckInfoReplication(); + } + else + { + try { connection.Dispose(); } catch { } + } + } + + private int connectStartTicks; + private long connectTimeoutRetryCount = 0; + + private bool DueForConnectRetry() + { + int connectTimeMilliseconds = unchecked(Environment.TickCount - Volatile.Read(ref connectStartTicks)); + return Multiplexer.RawConfig.ReconnectRetryPolicy.ShouldRetry(Interlocked.Read(ref connectTimeoutRetryCount), connectTimeMilliseconds); + } + + internal void OnHeartbeat(bool ifConnectedOnly) + { + bool runThisTime = false; + try + { + if (BacklogHasItems) + { + // If we have a backlog, kickoff the processing + // This will first timeout any messages that have sat too long and either: + // A: Abort if we're still not connected yet (we should be in this path) + // or B: Process the backlog and send those messages through the pipe + StartBacklogProcessor(); + } + + runThisTime = !isDisposed && Interlocked.CompareExchange(ref beating, 1, 0) == 0; + if (!runThisTime) return; + + uint index = (uint)Interlocked.Increment(ref profileLogIndex); + long newSampleCount = Interlocked.Read(ref operationCount); + Interlocked.Exchange(ref profileLog[index % ProfileLogSamples], newSampleCount); + Interlocked.Exchange(ref profileLastLog, newSampleCount); + Trace("OnHeartbeat: " + (State)state); + switch (state) + { + case (int)State.Connecting: + if (DueForConnectRetry()) + { + Interlocked.Increment(ref connectTimeoutRetryCount); + var ex = ExceptionFactory.UnableToConnect(Multiplexer, "ConnectTimeout", Name); + LastException = ex; + Multiplexer.Logger?.LogErrorConnectionIssue(ex, ex.Message); + Trace("Aborting connect"); + // abort and reconnect + var snapshot = physical; + OnDisconnected(ConnectionFailureType.UnableToConnect, snapshot, out bool isCurrent, out State oldState); + snapshot?.Dispose(); // Cleanup the existing connection/socket if any, otherwise it will wait reading indefinitely + TryConnect(null); + } + break; + case (int)State.ConnectedEstablishing: + // (Fall through) Happens when we successfully connected via TCP, but no Redis handshake completion yet. + // This can happen brief (usual) or when the server never answers (rare). When we're in this state, + // a socket is open and reader likely listening indefinitely for incoming data on an async background task. + // We need to time that out and cleanup the PhysicalConnection if needed, otherwise that reader and socket will remain open + // for the lifetime of the application due to being orphaned, yet still referenced by the active task doing the pipe read. + case (int)State.ConnectedEstablished: + // Track that we should reset the count on the next disconnect, but not do so in a loop + shouldResetConnectionRetryCount = true; + var tmp = physical; + if (tmp != null) + { + if (state == (int)State.ConnectedEstablished) + { + Interlocked.Exchange(ref connectTimeoutRetryCount, 0); + tmp.BridgeCouldBeNull?.ServerEndPoint?.ClearUnselectable(UnselectableFlags.DidNotRespond); + } + int timedOutThisHeartbeat = tmp.OnBridgeHeartbeat(); + int writeEverySeconds = ServerEndPoint.WriteEverySeconds; + bool configCheckDue = ServerEndPoint.ConfigCheckSeconds > 0 && ServerEndPoint.LastInfoReplicationCheckSecondsAgo >= ServerEndPoint.ConfigCheckSeconds; + + if (state == (int)State.ConnectedEstablished && ConnectionType == ConnectionType.Interactive + && tmp.BridgeCouldBeNull?.Multiplexer.RawConfig.HeartbeatConsistencyChecks == true) + { + // If HeartbeatConsistencyChecks are enabled, we're sending a PING (expecting PONG) or ECHO (expecting UniqueID back) every single + // heartbeat as an opt-in measure to react to any network stream drop ASAP to terminate the connection as faulted. + // If we don't get the expected response to that command, then the connection is terminated. + // This is to prevent the case of things like 100% string command usage where a protocol error isn't otherwise encountered. + KeepAlive(forceRun: true); + + // If we're configured to check info replication, perform that too + if (configCheckDue) + { + ServerEndPoint.CheckInfoReplication(); + } + } + else if (state == (int)State.ConnectedEstablished && ConnectionType == ConnectionType.Interactive + && configCheckDue + && ServerEndPoint.CheckInfoReplication()) + { + // that serves as a keep-alive, if it is accepted + } + else if (writeEverySeconds > 0 && tmp.LastWriteSecondsAgo >= writeEverySeconds) + { + Trace("OnHeartbeat - overdue"); + if (state == (int)State.ConnectedEstablished) + { + KeepAlive(); + } + else + { + OnDisconnected(ConnectionFailureType.SocketFailure, tmp, out bool ignore, out State oldState); + tmp.Dispose(); // Cleanup the existing connection/socket if any, otherwise it will wait reading indefinitely + } + } + else if (writeEverySeconds <= 0 + && tmp.IsIdle() + && tmp.LastWriteSecondsAgo > 2 + && tmp.GetSentAwaitingResponseCount() != 0) + { + // There's a chance this is a dead socket; sending data will shake that up a bit, + // so if we have an empty unsent queue and a non-empty sent queue, test the socket. + KeepAlive(); + } + + // This is an "always" check - we always want to evaluate a dead connection from a non-responsive sever regardless of the need to heartbeat above + if (timedOutThisHeartbeat > 0 + && tmp.LastReadSecondsAgo * 1_000 > (tmp.BridgeCouldBeNull?.Multiplexer.AsyncTimeoutMilliseconds * 4)) + { + // If we've received *NOTHING* on the pipe in 4 timeouts worth of time and we're timing out commands, issue a connection failure so that we reconnect + // This is meant to address the scenario we see often in Linux configs where TCP retries will happen for 15 minutes. + // To us as a client, we'll see the socket as green/open/fine when writing but we'll bet getting nothing back. + // Since we can't depend on the pipe to fail in that case, we want to error here based on the criteria above so we reconnect broken clients much faster. + tmp.BridgeCouldBeNull?.Multiplexer.Logger?.LogWarningDeadSocketDetected(tmp.LastReadSecondsAgo, timedOutThisHeartbeat); + OnDisconnected(ConnectionFailureType.SocketFailure, tmp, out _, out State oldState); + tmp.Dispose(); // Cleanup the existing connection/socket if any, otherwise it will wait reading indefinitely + } + } + break; + case (int)State.Disconnected: + // Only if we should reset the connection count + // This should only happen after a successful reconnection, and not every time we bounce from BeginConnectAsync -> Disconnected + // in a failure loop case that happens when a node goes missing forever. + if (shouldResetConnectionRetryCount) + { + shouldResetConnectionRetryCount = false; + Interlocked.Exchange(ref connectTimeoutRetryCount, 0); + } + if (!ifConnectedOnly && DueForConnectRetry()) + { + // Increment count here, so that we don't re-enter in Connecting case up top - we don't want to re-enter and log there. + Interlocked.Increment(ref connectTimeoutRetryCount); + + Multiplexer.Logger?.LogInformationResurrecting(this, connectTimeoutRetryCount); + Multiplexer.OnResurrecting(ServerEndPoint.EndPoint, ConnectionType); + TryConnect(null); + } + break; + default: + Interlocked.Exchange(ref connectTimeoutRetryCount, 0); + break; + } + } + catch (Exception ex) + { + OnInternalError(ex); + Trace("OnHeartbeat error: " + ex.Message); + } + finally + { + if (runThisTime) Interlocked.Exchange(ref beating, 0); + } + } + + [Conditional("VERBOSE")] + internal void Trace(string message) => Multiplexer.Trace(message, ToString()); + + [Conditional("VERBOSE")] + internal void Trace(bool condition, string message) + { + if (condition) Multiplexer.Trace(message, ToString()); + } + + internal bool TryEnqueue(List messages, bool isReplica) + { + if (messages == null || messages.Count == 0) return true; + + if (isDisposed) throw new ObjectDisposedException(Name); + + if (!IsConnected) + { + return false; + } + + var physical = this.physical; + if (physical == null) return false; + foreach (var message in messages) + { + // deliberately not taking a single lock here; we don't care if + // other threads manage to interleave - in fact, it would be desirable + // (to avoid a batch monopolising the connection) +#pragma warning disable CS0618 // Type or member is obsolete + WriteMessageTakingWriteLockSync(physical, message); +#pragma warning restore CS0618 + LogNonPreferred(message.Flags, isReplica); + } + return true; + } + + private Message? _activeMessage; + + private WriteResult WriteMessageInsideLock(PhysicalConnection physical, Message message) + { + WriteResult result; + var existingMessage = Interlocked.CompareExchange(ref _activeMessage, message, null); + if (existingMessage != null) + { + Multiplexer.OnInfoMessage($"Reentrant call to WriteMessageTakingWriteLock for {message.CommandAndKey}, {existingMessage.CommandAndKey} is still active"); + return WriteResult.NoConnectionAvailable; + } + + physical.SetWriting(); + if (message is IMultiMessage multiMessage) + { + var messageIsSent = false; + SelectDatabaseInsideWriteLock(physical, message); // need to switch database *before* the transaction + foreach (var subCommand in multiMessage.GetMessages(physical)) + { + result = WriteMessageToServerInsideWriteLock(physical, subCommand); + if (result != WriteResult.Success) + { + // we screwed up; abort; note that WriteMessageToServer already + // killed the underlying connection + Trace("Unable to write to server"); + message.Fail(ConnectionFailureType.ProtocolFailure, null, "failure before write: " + result.ToString(), Multiplexer); + message.Complete(); + return result; + } + // The parent message (next) may be returned from GetMessages + // and should not be marked as sent again below. + messageIsSent = messageIsSent || subCommand == message; + } + if (!messageIsSent) + { + message.SetRequestSent(); // well, it was attempted, at least... + } + + return WriteResult.Success; + } + else + { + return WriteMessageToServerInsideWriteLock(physical, message); + } + } + + [Obsolete("prefer async")] + internal WriteResult WriteMessageTakingWriteLockSync(PhysicalConnection physical, Message message) + { + Trace("Writing: " + message); + message.SetEnqueued(physical); // this also records the read/write stats at this point + + // AVOID REORDERING MESSAGES + // Prefer to add it to the backlog if this thread can see that there might already be a message backlog. + // We do this before attempting to take the write lock, because we won't actually write, we'll just let the backlog get processed in due course + if (TryPushToBacklog(message, onlyIfExists: true)) + { + return WriteResult.Success; // queued counts as success + } + +#if NETCOREAPP + bool gotLock = false; +#else + LockToken token = default; +#endif + try + { +#if NETCOREAPP + gotLock = _singleWriterMutex.Wait(0); + if (!gotLock) +#else + token = _singleWriterMutex.TryWait(WaitOptions.NoDelay); + if (!token.Success) +#endif + { + // If we can't get it *instantaneously*, pass it to the backlog for throughput + if (TryPushToBacklog(message, onlyIfExists: false)) + { + return WriteResult.Success; // queued counts as success + } + + // no backlog... try to wait with the timeout; + // if we *still* can't get it: that counts as + // an actual timeout +#if NETCOREAPP + gotLock = _singleWriterMutex.Wait(TimeoutMilliseconds); + if (!gotLock) return TimedOutBeforeWrite(message); +#else + token = _singleWriterMutex.TryWait(); + if (!token.Success) return TimedOutBeforeWrite(message); +#endif + } + + var result = WriteMessageInsideLock(physical, message); + + if (result == WriteResult.Success) + { + result = physical.FlushSync(false, TimeoutMilliseconds); + } + + physical.SetIdle(); + return result; + } + catch (Exception ex) { return HandleWriteException(message, ex); } + finally + { + UnmarkActiveMessage(message); +#if NETCOREAPP + if (gotLock) + { + _singleWriterMutex.Release(); + } +#else + token.Dispose(); +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryPushToBacklog(Message message, bool onlyIfExists, bool bypassBacklog = false) + { + // In the handshake case: send the command directly through. + // If we're disconnected *in the middle of a handshake*, we've bombed a brand new socket and failing, + // backing off, and retrying next heartbeat is best anyway. + // Internal calls also shouldn't queue - try immediately. If these aren't errors (most aren't), we + // won't alert the user. + if (bypassBacklog || message.IsInternalCall) + { + return false; + } + + // Note, for deciding emptiness for whether to push onlyIfExists, and start worker, + // we only need care if WE are able to see the queue when its empty. + // Not whether anyone else sees it as empty. + // So strong synchronization is not required. + if (onlyIfExists && Volatile.Read(ref _backlogCurrentEnqueued) == 0) + { + return false; + } + + BacklogEnqueue(message); + + // The correct way to decide to start backlog process is not based on previously empty + // but based on a) not empty now (we enqueued!) and b) no backlog processor already running. + // Which StartBacklogProcessor will check. + StartBacklogProcessor(); + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void BacklogEnqueue(Message message) + { + bool wasEmpty = _backlog.IsEmpty; + // important that this *precedes* enqueue, to play well with HasPendingCallerFacingItems + Interlocked.Increment(ref _backlogCurrentEnqueued); + Interlocked.Increment(ref _backlogTotalEnqueued); + _backlog.Enqueue(message); + message.SetBacklogged(); + + if (wasEmpty) + { + // it is important to do this *after* adding, so that we can't + // get into a thread-race where the heartbeat checks too fast; + // the fact that we're accessing Multiplexer down here means that + // we're rooting it ourselves via the stack, so we don't need + // to worry about it being collected until at least after this + Multiplexer.Root(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool BacklogTryDequeue([NotNullWhen(true)] out Message? message) + { + if (_backlog.TryDequeue(out message)) + { + Interlocked.Decrement(ref _backlogCurrentEnqueued); + return true; + } + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void StartBacklogProcessor() + { + if (Interlocked.CompareExchange(ref _backlogProcessorIsRunning, 1, 0) == 0) + { + _backlogStatus = BacklogStatus.Activating; + + // Start the backlog processor; this is a bit unorthodox, as you would *expect* this to just + // be Task.Run; that would work fine when healthy, but when we're falling on our face, it is + // easy to get into a thread-pool-starvation "spiral of death" if we rely on the thread-pool + // to unblock the thread-pool when there could be sync-over-async callers. Note that in reality, + // the initial "enough" of the back-log processor is typically sync, which means that the thread + // we start is actually useful, despite thinking "but that will just go async and back to the pool" + var thread = new Thread(s => ((PhysicalBridge)s!).ProcessBacklog()) + { + IsBackground = true, // don't keep process alive (also: act like the thread-pool used to) + Name = "StackExchange.Redis Backlog", // help anyone looking at thread-dumps + }; + thread.Start(this); + } + else + { + _backlogAutoReset.Set(); + } + } + + /// + /// Crawls from the head of the backlog queue, consuming anything that should have timed out + /// and pruning it accordingly (these messages will get timeout exceptions). + /// + private void CheckBacklogForTimeouts() + { + var now = Environment.TickCount; + var timeout = TimeoutMilliseconds; + + // Because peeking at the backlog, checking message and then dequeuing, is not thread-safe, we do have to use + // a lock here, for mutual exclusion of backlog DEQUEUERS. Unfortunately. + // But we reduce contention by only locking if we see something that looks timed out. + while (_backlog.TryPeek(out Message? message)) + { + // See if the message has pass our async timeout threshold + // Note: All timed out messages must be dequeued, even when no completion is needed, to be able to dequeue and complete other timed out messages. + if (!message.HasTimedOut(now, timeout, out var _)) break; // not a timeout - we can stop looking + lock (_backlog) + { + // Peek again since we didn't have lock before... + // and rerun the exact same checks as above, note that it may be a different message now + if (!_backlog.TryPeek(out message)) break; + if (!message.HasTimedOut(now, timeout, out var _)) break; + + if (!BacklogTryDequeue(out var message2) || (message != message2)) // consume it for real + { + throw new RedisException("Thread safety bug detected! A queue message disappeared while we had the backlog lock"); + } + } + + // We only handle async timeouts here, synchronous timeouts are handled upstream. + // Those sync timeouts happen in ConnectionMultiplexer.ExecuteSyncImpl() via Monitor.Wait. + if (message.ResultBoxIsAsync) + { + // Tell the message it has failed + // Note: Attempting to *avoid* reentrancy/deadlock issues by not holding the lock while completing messages. + var ex = Multiplexer.GetException(WriteResult.TimeoutBeforeWrite, message, ServerEndPoint, this); + message.SetExceptionAndComplete(ex, this); + } + } + } + + internal enum BacklogStatus : byte + { + Inactive, + Activating, + Starting, + Started, + CheckingForWork, + SpinningDown, + CheckingForTimeout, + CheckingForTimeoutComplete, + RecordingTimeout, + WritingMessage, + Flushing, + MarkingInactive, + RecordingWriteFailure, + RecordingFault, + SettingIdle, + Faulted, + NotifyingDisposed, + } + + private volatile BacklogStatus _backlogStatus; + + /// + /// Process the backlog(s) in play if any. + /// This means flushing commands to an available/active connection (if any) or spinning until timeout if not. + /// + private void ProcessBacklog() + { + _backlogStatus = BacklogStatus.Starting; + try + { + while (!isDisposed) + { + if (!_backlog.IsEmpty) + { + // TODO: vNext handoff this backlog to another primary ("can handle everything") connection + // and remove any per-server commands. This means we need to track a bit of whether something + // was server-endpoint-specific in PrepareToPushMessageToBridge (was the server ref null or not) + ProcessBridgeBacklog(); + } + + // The cost of starting a new thread is high, and we can bounce in and out of the backlog a lot. + // So instead of just exiting, keep this thread waiting for 5 seconds to see if we got another backlog item. + _backlogStatus = BacklogStatus.SpinningDown; + // Note this is happening *outside* the lock + var gotMore = _backlogAutoReset.WaitOne(5000); + if (!gotMore) + { + break; + } + } + // If we're being disposed but have items in the backlog, we need to complete them or async messages can linger forever. + if (isDisposed && BacklogHasItems) + { + _backlogStatus = BacklogStatus.NotifyingDisposed; + // Because peeking at the backlog, checking message and then dequeuing, is not thread-safe, we do have to use + // a lock here, for mutual exclusion of backlog DEQUEUERS. Unfortunately. + // But we reduce contention by only locking if we see something that looks timed out. + while (BacklogHasItems) + { + Message? message = null; + lock (_backlog) + { + if (!BacklogTryDequeue(out message)) + { + break; + } + } + + var ex = ExceptionFactory.Timeout(Multiplexer, "The message was in the backlog when connection was disposed", message, ServerEndPoint, WriteResult.TimeoutBeforeWrite, this); + message.SetExceptionAndComplete(ex, this); + } + } + } + catch (ObjectDisposedException) when (!BacklogHasItems) + { + // We're being torn down and we have no backlog to process - all good. + } + catch (Exception) + { + _backlogStatus = BacklogStatus.Faulted; + } + finally + { + // Do this in finally block, so that thread aborts can't convince us the backlog processor is running forever + if (Interlocked.CompareExchange(ref _backlogProcessorIsRunning, 0, 1) != 1) + { + throw new RedisException("Bug detection, couldn't indicate shutdown of backlog processor"); + } + + // Now that nobody is processing the backlog, we should consider starting a new backlog processor + // in case a new message came in after we ended this loop. + if (BacklogHasItems) + { + // Check for faults mainly to prevent unlimited tasks spawning in a fault scenario + // This won't cause a StackOverflowException due to the Task.Run() handoff + if (_backlogStatus != BacklogStatus.Faulted) + { + StartBacklogProcessor(); + } + } + } + } + + /// + /// Reset event for monitoring backlog additions mid-run. + /// This allows us to keep the thread around for a full flush and prevent "feathering the throttle" trying + /// to flush it. In short, we don't start and stop so many threads with a bit of linger. + /// + private readonly AutoResetEvent _backlogAutoReset = new AutoResetEvent(false); + + private void ProcessBridgeBacklog() + { + // Importantly: don't assume we have a physical connection here + // We are very likely to hit a state where it's not re-established or even referenced here +#if NETCOREAPP + bool gotLock = false; +#else + LockToken token = default; +#endif + _backlogAutoReset.Reset(); + try + { + _backlogStatus = BacklogStatus.Starting; + + // First eliminate any messages that have timed out already. + _backlogStatus = BacklogStatus.CheckingForTimeout; + CheckBacklogForTimeouts(); + _backlogStatus = BacklogStatus.CheckingForTimeoutComplete; + + // For the rest of the backlog, if we're not connected there's no point - abort out + while (IsConnected) + { + // check whether the backlog is empty *before* even trying to get the lock + if (_backlog.IsEmpty) return; // nothing to do + + // try and get the lock; if unsuccessful, retry +#if NETCOREAPP + gotLock = _singleWriterMutex.Wait(TimeoutMilliseconds); + if (gotLock) break; // got the lock; now go do something with it +#else + token = _singleWriterMutex.TryWait(); + if (token.Success) break; // got the lock; now go do something with it +#endif + } + _backlogStatus = BacklogStatus.Started; + + // Only execute if we're connected. + // Timeouts are handled above, so we're exclusively into backlog items eligible to write at this point. + // If we can't write them, abort and wait for the next heartbeat or activation to try this again. + while (IsConnected && physical?.HasOutputPipe == true) + { + Message? message; + _backlogStatus = BacklogStatus.CheckingForWork; + + lock (_backlog) + { + // Note that we're actively taking it off the queue here, not peeking + // If there's nothing left in queue, we're done. + if (!BacklogTryDequeue(out message)) + { + break; + } + } + + try + { + _backlogStatus = BacklogStatus.WritingMessage; + var result = WriteMessageInsideLock(physical, message); + + if (result == WriteResult.Success) + { + _backlogStatus = BacklogStatus.Flushing; +#pragma warning disable CS0618 // Type or member is obsolete + result = physical.FlushSync(false, TimeoutMilliseconds); +#pragma warning restore CS0618 // Type or member is obsolete + } + + _backlogStatus = BacklogStatus.MarkingInactive; + if (result != WriteResult.Success) + { + _backlogStatus = BacklogStatus.RecordingWriteFailure; + var ex = Multiplexer.GetException(result, message, ServerEndPoint); + HandleWriteException(message, ex); + } + } + catch (Exception ex) + { + _backlogStatus = BacklogStatus.RecordingFault; + HandleWriteException(message, ex); + } + finally + { + UnmarkActiveMessage(message); + } + } + _backlogStatus = BacklogStatus.SettingIdle; + physical?.SetIdle(); + _backlogStatus = BacklogStatus.Inactive; + } + finally + { +#if NETCOREAPP + if (gotLock) + { + _singleWriterMutex.Release(); + } +#else + token.Dispose(); +#endif + } + } + + public bool HasPendingCallerFacingItems() + { + if (BacklogHasItems) + { + foreach (var item in _backlog) // non-consuming, thread-safe, etc + { + if (!item.IsInternalCall) return true; + } + } + return physical?.HasPendingCallerFacingItems() ?? false; + } + + private WriteResult TimedOutBeforeWrite(Message message) + { + message.Cancel(); + Multiplexer.OnMessageFaulted(message, null); + message.Complete(); + return WriteResult.TimeoutBeforeWrite; + } + + /// + /// This writes a message to the output stream. + /// + /// The physical connection to write to. + /// The message to be written. + /// Whether this message should bypass the backlog, going straight to the pipe or failing. + internal ValueTask WriteMessageTakingWriteLockAsync(PhysicalConnection physical, Message message, bool bypassBacklog = false) + { + Trace("Writing: " + message); + message.SetEnqueued(physical); // this also records the read/write stats at this point + + // AVOID REORDERING MESSAGES + // Prefer to add it to the backlog if this thread can see that there might already be a message backlog. + // We do this before attempting to take the write lock, because we won't actually write, we'll just let the backlog get processed in due course + if (TryPushToBacklog(message, onlyIfExists: true, bypassBacklog: bypassBacklog)) + { + return new ValueTask(WriteResult.Success); // queued counts as success + } + + bool releaseLock = true; // fine to default to true, as it doesn't matter until token is a "success" +#if NETCOREAPP + bool gotLock = false; +#else + LockToken token = default; +#endif + try + { + // try to acquire it synchronously +#if NETCOREAPP + gotLock = _singleWriterMutex.Wait(0); + if (!gotLock) +#else + // note: timeout is specified in mutex-constructor + token = _singleWriterMutex.TryWait(options: WaitOptions.NoDelay); + if (!token.Success) +#endif + { + // If we can't get it *instantaneously*, pass it to the backlog for throughput + if (TryPushToBacklog(message, onlyIfExists: false, bypassBacklog: bypassBacklog)) + { + return new ValueTask(WriteResult.Success); // queued counts as success + } + + // no backlog... try to wait with the timeout; + // if we *still* can't get it: that counts as + // an actual timeout +#if NETCOREAPP + var pending = _singleWriterMutex.WaitAsync(TimeoutMilliseconds); + if (pending.Status != TaskStatus.RanToCompletion) return WriteMessageTakingWriteLockAsync_Awaited(pending, physical, message); + + gotLock = pending.Result; // fine since we know we got a result + if (!gotLock) return new ValueTask(TimedOutBeforeWrite(message)); +#else + var pending = _singleWriterMutex.TryWaitAsync(options: WaitOptions.DisableAsyncContext); + if (!pending.IsCompletedSuccessfully) return WriteMessageTakingWriteLockAsync_Awaited(pending, physical, message); + + token = pending.Result; // fine since we know we got a result + if (!token.Success) return new ValueTask(TimedOutBeforeWrite(message)); +#endif + } + var result = WriteMessageInsideLock(physical, message); + if (result == WriteResult.Success) + { + var flush = physical.FlushAsync(false); + if (!flush.IsCompletedSuccessfully) + { + releaseLock = false; // so we don't release prematurely +#if NETCOREAPP + return CompleteWriteAndReleaseLockAsync(flush, message); +#else + return CompleteWriteAndReleaseLockAsync(token, flush, message); +#endif + } + + result = flush.Result; // .Result: we know it was completed, so this is fine + } + + physical.SetIdle(); + + return new ValueTask(result); + } + catch (Exception ex) + { + return new ValueTask(HandleWriteException(message, ex)); + } + finally + { +#if NETCOREAPP + if (gotLock) +#else + if (token.Success) +#endif + { + UnmarkActiveMessage(message); + + if (releaseLock) + { +#if NETCOREAPP + _singleWriterMutex.Release(); +#else + token.Dispose(); +#endif + } + } + } + } + + private async ValueTask WriteMessageTakingWriteLockAsync_Awaited( +#if NETCOREAPP + Task pending, +#else + ValueTask pending, +#endif + PhysicalConnection physical, + Message message) + { +#if NETCOREAPP + bool gotLock = false; +#endif + + try + { +#if NETCOREAPP + gotLock = await pending.ForAwait(); + if (!gotLock) return TimedOutBeforeWrite(message); +#else + using var token = await pending.ForAwait(); +#endif + var result = WriteMessageInsideLock(physical, message); + + if (result == WriteResult.Success) + { + result = await physical.FlushAsync(false).ForAwait(); + } + + physical.SetIdle(); + + return result; + } + catch (Exception ex) + { + return HandleWriteException(message, ex); + } + finally + { + UnmarkActiveMessage(message); +#if NETCOREAPP + if (gotLock) + { + _singleWriterMutex.Release(); + } +#endif + } + } + + [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1519:Braces should not be omitted from multi-line child statement", Justification = "Detector is confused with the #ifdefs here")] + private async ValueTask CompleteWriteAndReleaseLockAsync( +#if !NETCOREAPP + LockToken lockToken, +#endif + ValueTask flush, + Message message) + { +#if !NETCOREAPP + using (lockToken) +#endif + try + { + var result = await flush.ForAwait(); + physical?.SetIdle(); + return result; + } + catch (Exception ex) + { + return HandleWriteException(message, ex); + } + finally + { +#if NETCOREAPP + _singleWriterMutex.Release(); +#endif + } + } + + private WriteResult HandleWriteException(Message message, Exception ex) + { + var inner = new RedisConnectionException(ConnectionFailureType.InternalFailure, "Failed to write", ex); + message.SetExceptionAndComplete(inner, this); + return WriteResult.WriteFailure; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UnmarkActiveMessage(Message message) + => Interlocked.CompareExchange(ref _activeMessage, null, message); // remove if it is us + + private State ChangeState(State newState) + { + var oldState = (State)Interlocked.Exchange(ref state, (int)newState); + if (oldState != newState) + { + Multiplexer.Trace(ConnectionType + " state changed from " + oldState + " to " + newState); + } + return oldState; + } + + private bool ChangeState(State oldState, State newState) + { + bool result = Interlocked.CompareExchange(ref state, (int)newState, (int)oldState) == (int)oldState; + if (result) + { + Multiplexer.Trace(ConnectionType + " state changed from " + oldState + " to " + newState); + } + return result; + } + + public PhysicalConnection? TryConnect(ILogger? log) + { + if (state == (int)State.Disconnected) + { + try + { + if (!Multiplexer.IsDisposed) + { + log?.LogInformationConnecting(Name); + Multiplexer.Trace("Connecting...", Name); + if (ChangeState(State.Disconnected, State.Connecting)) + { + Interlocked.Increment(ref socketCount); + Interlocked.Exchange(ref connectStartTicks, Environment.TickCount); + // separate creation and connection for case when connection completes synchronously + // in that case PhysicalConnection will call back to PhysicalBridge, and most PhysicalBridge methods assume that physical is not null; + physical = new PhysicalConnection(this); + + physical.BeginConnectAsync(log).RedisFireAndForget(); + } + } + return null; + } + catch (Exception ex) + { + log?.LogErrorConnectFailed(ex, Name, ex.Message); + Multiplexer.Trace("Connect failed: " + ex.Message, Name); + ChangeState(State.Disconnected); + OnInternalError(ex); + throw; + } + } + return physical; + } + + private void LogNonPreferred(CommandFlags flags, bool isReplica) + { + if ((flags & Message.InternalCallFlag) == 0) // don't log internal-call + { + if (isReplica) + { + if (Message.GetPrimaryReplicaFlags(flags) == CommandFlags.PreferMaster) + Interlocked.Increment(ref nonPreferredEndpointCount); + } + else + { + if (Message.GetPrimaryReplicaFlags(flags) == CommandFlags.PreferReplica) + Interlocked.Increment(ref nonPreferredEndpointCount); + } + } + } + + private void OnInternalError(Exception exception, [CallerMemberName] string? origin = null) + { + Multiplexer.OnInternalError(exception, ServerEndPoint.EndPoint, ConnectionType, origin); + } + + private void SelectDatabaseInsideWriteLock(PhysicalConnection connection, Message message) + { + int db = message.Db; + if (db >= 0) + { + var sel = connection.GetSelectDatabaseCommand(db, message); + if (sel != null) + { + connection.EnqueueInsideWriteLock(sel); + sel.WriteTo(connection); + sel.SetRequestSent(); + IncrementOpCount(); + } + } + } + + private WriteResult WriteMessageToServerInsideWriteLock(PhysicalConnection connection, Message message) + { + if (message == null) + { + return WriteResult.Success; // for some definition of success + } + + bool isQueued = false; + try + { + var cmd = message.Command; + LastCommand = cmd; + bool isPrimaryOnly = message.IsPrimaryOnly(); + + if (isPrimaryOnly && !ServerEndPoint.SupportsPrimaryWrites) + { + throw ExceptionFactory.PrimaryOnly(Multiplexer.RawConfig.IncludeDetailInExceptions, message.Command, message, ServerEndPoint); + } + switch (cmd) + { + case RedisCommand.QUIT: + connection.RecordQuit(); + break; + case RedisCommand.EXEC: + Multiplexer.OnPreTransactionExec(message); // testing purposes, to force certain errors + break; + } + + SelectDatabaseInsideWriteLock(connection, message); + + if (!connection.TransactionActive) + { + // If we are executing AUTH, it means we are still unauthenticated + // Setting READONLY before AUTH always fails but we think it succeeded since + // we run it as Fire and Forget. + if (cmd != RedisCommand.AUTH && cmd != RedisCommand.HELLO) + { + var readmode = connection.GetReadModeCommand(isPrimaryOnly); + if (readmode != null) + { + connection.EnqueueInsideWriteLock(readmode); + readmode.WriteTo(connection); + readmode.SetRequestSent(); + IncrementOpCount(); + } + } + if (message.IsAsking) + { + var asking = ReusableAskingCommand; + connection.EnqueueInsideWriteLock(asking); + asking.WriteTo(connection); + asking.SetRequestSent(); + IncrementOpCount(); + } + } + switch (cmd) + { + case RedisCommand.WATCH: + case RedisCommand.MULTI: + connection.TransactionActive = true; + break; + case RedisCommand.UNWATCH: + case RedisCommand.EXEC: + case RedisCommand.DISCARD: + connection.TransactionActive = false; + break; + } + + if (_nextHighIntegrityToken is not 0 + && !connection.TransactionActive // validated in the UNWATCH/EXEC/DISCARD + && message.Command is not RedisCommand.AUTH or RedisCommand.HELLO) // if auth fails, ECHO may also fail; avoid confusion + { + // make sure this value exists early to avoid a race condition + // if the response comes back super quickly + message.WithHighIntegrity(NextHighIntegrityTokenInsideLock()); + Debug.Assert(message.IsHighIntegrity, "message should be high integrity"); + } + else + { + Debug.Assert(!message.IsHighIntegrity, "prior high integrity message found during transaction?"); + } + connection.EnqueueInsideWriteLock(message); + isQueued = true; + message.WriteTo(connection); + + if (message.IsHighIntegrity) + { + message.WriteHighIntegrityChecksumRequest(connection); + IncrementOpCount(); + } + + message.SetRequestSent(); + IncrementOpCount(); + + // Some commands smash our ability to trust the database + // and some commands demand an immediate flush + switch (cmd) + { + case RedisCommand.EVAL: + case RedisCommand.EVALSHA: + if (!ServerEndPoint.GetFeatures().ScriptingDatabaseSafe) + { + connection.SetUnknownDatabase(); + } + break; + case RedisCommand.UNKNOWN: + case RedisCommand.DISCARD: + case RedisCommand.EXEC: + if (ServerEndPoint.SupportsDatabases) + { + connection.SetUnknownDatabase(); + } + break; + } + return WriteResult.Success; + } + catch (RedisCommandException ex) when (!isQueued) + { + Trace("Write failed: " + ex.Message); + message.Fail(ConnectionFailureType.InternalFailure, ex, null, Multiplexer); + message.Complete(); + + // This failed without actually writing; we're OK with that... unless there's a transaction + if (connection?.TransactionActive == true) + { + // We left it in a broken state - need to kill the connection + connection.RecordConnectionFailed(ConnectionFailureType.ProtocolFailure, ex); + return WriteResult.WriteFailure; + } + return WriteResult.Success; + } + catch (Exception ex) + { + Trace("Write failed: " + ex.Message); + message.Fail(ConnectionFailureType.InternalFailure, ex, null, Multiplexer); + message.Complete(); + + // We're not sure *what* happened here - probably an IOException; kill the connection + connection?.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); + return WriteResult.WriteFailure; + } + } + + private uint NextHighIntegrityTokenInsideLock() + { + // inside lock: no concurrency concerns here + switch (_nextHighIntegrityToken) + { + case 0: return 0; // disabled + case uint.MaxValue: + // avoid leaving the value at zero due to wrap-around + _nextHighIntegrityToken = 1; + return ushort.MaxValue; + default: + return _nextHighIntegrityToken++; + } + } + + /// + /// For testing only. + /// + internal void SimulateConnectionFailure(SimulatedFailureType failureType) + { + if (!Multiplexer.RawConfig.AllowAdmin) + { + throw ExceptionFactory.AdminModeNotEnabled(Multiplexer.RawConfig.IncludeDetailInExceptions, RedisCommand.DEBUG, null, ServerEndPoint); // close enough + } + physical?.SimulateConnectionFailure(failureType); + } + + internal RedisCommand? GetActiveMessage() => Volatile.Read(ref _activeMessage)?.Command; + } +} diff --git a/src/StackExchange.Redis/PhysicalConnection.cs b/src/StackExchange.Redis/PhysicalConnection.cs new file mode 100644 index 000000000..57bcd608d --- /dev/null +++ b/src/StackExchange.Redis/PhysicalConnection.cs @@ -0,0 +1,2458 @@ +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Pipelines.Sockets.Unofficial; +using Pipelines.Sockets.Unofficial.Arenas; +using static StackExchange.Redis.Message; + +namespace StackExchange.Redis +{ + internal sealed partial class PhysicalConnection : IDisposable + { + internal readonly byte[]? ChannelPrefix; + + private const int DefaultRedisDatabaseCount = 16; + + private static readonly Message[] ReusableChangeDatabaseCommands = Enumerable.Range(0, DefaultRedisDatabaseCount).Select( + i => Message.Create(i, CommandFlags.FireAndForget, RedisCommand.SELECT)).ToArray(); + + private static readonly Message + ReusableReadOnlyCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.READONLY), + ReusableReadWriteCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.READWRITE); + + private static int totalCount; + + private readonly ConnectionType connectionType; + + // things sent to this physical, but not yet received + private readonly Queue _writtenAwaitingResponse = new Queue(); + + private Message? _awaitingToken; + + private readonly string _physicalName; + + private volatile int currentDatabase = 0; + + private ReadMode currentReadMode = ReadMode.NotSpecified; + + private int failureReported; + + private int clientSentQuit; + + private int lastWriteTickCount, lastReadTickCount, lastBeatTickCount; + + private long bytesLastResult; + private long bytesInBuffer; + internal long? ConnectionId { get; set; } + + internal void GetBytes(out long sent, out long received) + { + if (_ioPipe is IMeasuredDuplexPipe sc) + { + sent = sc.TotalBytesSent; + received = sc.TotalBytesReceived; + } + else + { + sent = received = -1; + } + } + + /// + /// Nullable because during simulation of failure, we'll null out. + /// ...but in those cases, we'll accept any null ref in a race - it's fine. + /// + private IDuplexPipe? _ioPipe; + internal bool HasOutputPipe => _ioPipe?.Output != null; + + private Socket? _socket; + internal Socket? VolatileSocket => Volatile.Read(ref _socket); + + public PhysicalConnection(PhysicalBridge bridge) + { + lastWriteTickCount = lastReadTickCount = Environment.TickCount; + lastBeatTickCount = 0; + connectionType = bridge.ConnectionType; + _bridge = new WeakReference(bridge); + ChannelPrefix = bridge.Multiplexer.RawConfig.ChannelPrefix; + if (ChannelPrefix?.Length == 0) ChannelPrefix = null; // null tests are easier than null+empty + var endpoint = bridge.ServerEndPoint.EndPoint; + _physicalName = connectionType + "#" + Interlocked.Increment(ref totalCount) + "@" + Format.ToString(endpoint); + + OnCreateEcho(); + } + + // *definitely* multi-database; this can help identify some unusual config scenarios + internal bool MultiDatabasesOverride { get; set; } // switch to flags-enum if more needed later + + internal async Task BeginConnectAsync(ILogger? log) + { + var bridge = BridgeCouldBeNull; + var endpoint = bridge?.ServerEndPoint?.EndPoint; + if (bridge == null || endpoint == null) + { + log?.LogErrorNoEndpoint(new ArgumentNullException(nameof(endpoint))); + return; + } + + Trace("Connecting..."); + var tunnel = bridge.Multiplexer.RawConfig.Tunnel; + var connectTo = endpoint; + if (tunnel is not null) + { + connectTo = await tunnel.GetSocketConnectEndpointAsync(endpoint, CancellationToken.None).ForAwait(); + } + if (connectTo is not null) + { + _socket = SocketManager.CreateSocket(connectTo); + } + + if (_socket is not null) + { + bridge.Multiplexer.RawConfig.BeforeSocketConnect?.Invoke(endpoint, bridge.ConnectionType, _socket); + if (tunnel is not null) + { + // same functionality as part of a tunnel + await tunnel.BeforeSocketConnectAsync(endpoint, bridge.ConnectionType, _socket, CancellationToken.None).ForAwait(); + } + } + bridge.Multiplexer.OnConnecting(endpoint, bridge.ConnectionType); + log?.LogInformationBeginConnectAsync(new(endpoint)); + + CancellationTokenSource? timeoutSource = null; + try + { + using (var args = connectTo is null ? null : new SocketAwaitableEventArgs + { + RemoteEndPoint = connectTo, + }) + { + var x = VolatileSocket; + if (x == null) + { + args?.Abort(); + } + else if (args is not null && x.ConnectAsync(args)) + { + // asynchronous operation is pending + timeoutSource = ConfigureTimeout(args, bridge.Multiplexer.RawConfig.ConnectTimeout); + } + else + { + // completed synchronously + args?.Complete(); + } + + // Complete connection + try + { + // If we're told to ignore connect, abort here + if (BridgeCouldBeNull?.Multiplexer?.IgnoreConnect ?? false) return; + + if (args is not null) + { + await args; // wait for the connect to complete or fail (will throw) + } + if (timeoutSource != null) + { + timeoutSource.Cancel(); + timeoutSource.Dispose(); + } + + x = VolatileSocket; + if (x == null && args is not null) + { + ConnectionMultiplexer.TraceWithoutContext("Socket was already aborted"); + } + else if (await ConnectedAsync(x, log, bridge.Multiplexer.SocketManager!).ForAwait()) + { + log?.LogInformationStartingRead(new(endpoint)); + try + { + StartReading(); + // Normal return + } + catch (Exception ex) + { + ConnectionMultiplexer.TraceWithoutContext(ex.Message); + Shutdown(); + } + } + else + { + ConnectionMultiplexer.TraceWithoutContext("Aborting socket"); + Shutdown(); + } + } + catch (ObjectDisposedException ex) + { + log?.LogErrorSocketShutdown(ex, new(endpoint)); + try { RecordConnectionFailed(ConnectionFailureType.UnableToConnect, isInitialConnect: true); } + catch (Exception inner) + { + ConnectionMultiplexer.TraceWithoutContext(inner.Message); + } + } + catch (Exception outer) + { + ConnectionMultiplexer.TraceWithoutContext(outer.Message); + try { RecordConnectionFailed(ConnectionFailureType.UnableToConnect, isInitialConnect: true); } + catch (Exception inner) + { + ConnectionMultiplexer.TraceWithoutContext(inner.Message); + } + } + } + } + catch (NotImplementedException ex) when (endpoint is not IPEndPoint) + { + throw new InvalidOperationException("BeginConnect failed with NotImplementedException; consider using IP endpoints, or enable ResolveDns in the configuration", ex); + } + finally + { + if (timeoutSource != null) try { timeoutSource.Dispose(); } catch { } + } + } + + private static CancellationTokenSource ConfigureTimeout(SocketAwaitableEventArgs args, int timeoutMilliseconds) + { + var cts = new CancellationTokenSource(); + var timeout = Task.Delay(timeoutMilliseconds, cts.Token); + timeout.ContinueWith( + (_, state) => + { + try + { + var a = (SocketAwaitableEventArgs)state!; + a.Abort(SocketError.TimedOut); + Socket.CancelConnectAsync(a); + } + catch { } + }, + args); + return cts; + } + + private enum ReadMode : byte + { + NotSpecified, + ReadOnly, + ReadWrite, + } + + private readonly WeakReference _bridge; + public PhysicalBridge? BridgeCouldBeNull => (PhysicalBridge?)_bridge.Target; + + public long LastReadSecondsAgo => unchecked(Environment.TickCount - Volatile.Read(ref lastReadTickCount)) / 1000; + public long LastWriteSecondsAgo => unchecked(Environment.TickCount - Volatile.Read(ref lastWriteTickCount)) / 1000; + + private bool IncludeDetailInExceptions => BridgeCouldBeNull?.Multiplexer.RawConfig.IncludeDetailInExceptions ?? false; + + [Conditional("VERBOSE")] + internal void Trace(string message) => BridgeCouldBeNull?.Multiplexer?.Trace(message, ToString()); + + public long SubscriptionCount { get; set; } + + public bool TransactionActive { get; internal set; } + + private RedisProtocol _protocol; // note starts at **zero**, not RESP2 + public RedisProtocol? Protocol => _protocol == 0 ? null : _protocol; + + internal void SetProtocol(RedisProtocol value) + { + _protocol = value; + BridgeCouldBeNull?.SetProtocol(value); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Trust me yo")] + internal void Shutdown() + { + var ioPipe = Interlocked.Exchange(ref _ioPipe, null); // compare to the critical read + var socket = Interlocked.Exchange(ref _socket, null); + + if (ioPipe != null) + { + Trace("Disconnecting..."); + try { BridgeCouldBeNull?.OnDisconnected(ConnectionFailureType.ConnectionDisposed, this, out _, out _); } catch { } + try { ioPipe.Input?.CancelPendingRead(); } catch { } + try { ioPipe.Input?.Complete(); } catch { } + try { ioPipe.Output?.CancelPendingFlush(); } catch { } + try { ioPipe.Output?.Complete(); } catch { } + try { using (ioPipe as IDisposable) { } } catch { } + } + + if (socket != null) + { + try { socket.Shutdown(SocketShutdown.Both); } catch { } + try { socket.Close(); } catch { } + try { socket.Dispose(); } catch { } + } + } + + public void Dispose() + { + bool markDisposed = VolatileSocket != null; + Shutdown(); + if (markDisposed) + { + Trace("Disconnected"); + RecordConnectionFailed(ConnectionFailureType.ConnectionDisposed); + } + OnCloseEcho(); + _arena.Dispose(); + _reusableFlushSyncTokenSource?.Dispose(); + GC.SuppressFinalize(this); + } + + private async Task AwaitedFlush(ValueTask flush) + { + await flush.ForAwait(); + _writeStatus = WriteStatus.Flushed; + UpdateLastWriteTime(); + } + internal void UpdateLastWriteTime() => Interlocked.Exchange(ref lastWriteTickCount, Environment.TickCount); + public Task FlushAsync() + { + var tmp = _ioPipe?.Output; + if (tmp != null) + { + _writeStatus = WriteStatus.Flushing; + var flush = tmp.FlushAsync(); + if (!flush.IsCompletedSuccessfully) + { + return AwaitedFlush(flush); + } + _writeStatus = WriteStatus.Flushed; + UpdateLastWriteTime(); + } + return Task.CompletedTask; + } + + internal void SimulateConnectionFailure(SimulatedFailureType failureType) + { + var raiseFailed = false; + if (connectionType == ConnectionType.Interactive) + { + if (failureType.HasFlag(SimulatedFailureType.InteractiveInbound)) + { + _ioPipe?.Input.Complete(new Exception("Simulating interactive input failure")); + raiseFailed = true; + } + if (failureType.HasFlag(SimulatedFailureType.InteractiveOutbound)) + { + _ioPipe?.Output.Complete(new Exception("Simulating interactive output failure")); + raiseFailed = true; + } + } + else if (connectionType == ConnectionType.Subscription) + { + if (failureType.HasFlag(SimulatedFailureType.SubscriptionInbound)) + { + _ioPipe?.Input.Complete(new Exception("Simulating subscription input failure")); + raiseFailed = true; + } + if (failureType.HasFlag(SimulatedFailureType.SubscriptionOutbound)) + { + _ioPipe?.Output.Complete(new Exception("Simulating subscription output failure")); + raiseFailed = true; + } + } + if (raiseFailed) + { + RecordConnectionFailed(ConnectionFailureType.SocketFailure); + } + } + + public void RecordConnectionFailed( + ConnectionFailureType failureType, + Exception? innerException = null, + [CallerMemberName] string? origin = null, + bool isInitialConnect = false, + IDuplexPipe? connectingPipe = null) + { + bool weAskedForThis; + Exception? outerException = innerException; + IdentifyFailureType(innerException, ref failureType); + var bridge = BridgeCouldBeNull; + Message? nextMessage; + + if (_ioPipe != null || isInitialConnect) // if *we* didn't burn the pipe: flag it + { + if (failureType == ConnectionFailureType.InternalFailure && innerException is not null) + { + OnInternalError(innerException, origin); + } + + // stop anything new coming in... + bridge?.Trace("Failed: " + failureType); + ConnectionStatus connStatus = ConnectionStatus.Default; + PhysicalBridge.State oldState = PhysicalBridge.State.Disconnected; + bool isCurrent = false; + bridge?.OnDisconnected(failureType, this, out isCurrent, out oldState); + if (oldState == PhysicalBridge.State.ConnectedEstablished) + { + try + { + connStatus = GetStatus(); + } + catch { /* best effort only */ } + } + + if (isCurrent && Interlocked.CompareExchange(ref failureReported, 1, 0) == 0) + { + int now = Environment.TickCount, lastRead = Volatile.Read(ref lastReadTickCount), lastWrite = Volatile.Read(ref lastWriteTickCount), + lastBeat = Volatile.Read(ref lastBeatTickCount); + + int unansweredWriteTime = 0; + lock (_writtenAwaitingResponse) + { + // find oldest message awaiting a response + if (_writtenAwaitingResponse.TryPeek(out nextMessage)) + { + unansweredWriteTime = nextMessage.GetWriteTime(); + } + } + + var exMessage = new StringBuilder(failureType.ToString()); + + // If the reason for the shutdown was we asked for the socket to die, don't log it as an error (only informational) + weAskedForThis = Volatile.Read(ref clientSentQuit) != 0; + + var pipe = connectingPipe ?? _ioPipe; + if (pipe is SocketConnection sc) + { + exMessage.Append(" (").Append(sc.ShutdownKind); + if (sc.SocketError != SocketError.Success) + { + exMessage.Append('/').Append(sc.SocketError); + } + if (sc.BytesRead == 0) exMessage.Append(", 0-read"); + if (sc.BytesSent == 0) exMessage.Append(", 0-sent"); + exMessage.Append(", last-recv: ").Append(sc.LastReceived).Append(')'); + } + else if (pipe is IMeasuredDuplexPipe mdp) + { + long sent = mdp.TotalBytesSent, recd = mdp.TotalBytesReceived; + + if (sent == 0) { exMessage.Append(recd == 0 ? " (0-read, 0-sent)" : " (0-sent)"); } + else if (recd == 0) { exMessage.Append(" (0-read)"); } + } + + var data = new List>(); + void AddData(string lk, string sk, string? v) + { + if (lk != null) data.Add(Tuple.Create(lk, v)); + if (sk != null) exMessage.Append(", ").Append(sk).Append(": ").Append(v); + } + + if (IncludeDetailInExceptions) + { + if (bridge != null) + { + exMessage.Append(" on ").Append(Format.ToString(bridge.ServerEndPoint?.EndPoint)).Append('/').Append(connectionType) + .Append(", ").Append(_writeStatus).Append('/').Append(_readStatus) + .Append(", last: ").Append(bridge.LastCommand); + + data.Add(Tuple.Create("FailureType", failureType.ToString())); + data.Add(Tuple.Create("EndPoint", Format.ToString(bridge.ServerEndPoint?.EndPoint))); + + AddData("Origin", "origin", origin); + // add("Input-Buffer", "input-buffer", _ioPipe.Input); + AddData("Outstanding-Responses", "outstanding", GetSentAwaitingResponseCount().ToString()); + AddData("Last-Read", "last-read", (unchecked(now - lastRead) / 1000) + "s ago"); + AddData("Last-Write", "last-write", (unchecked(now - lastWrite) / 1000) + "s ago"); + if (unansweredWriteTime != 0) AddData("Unanswered-Write", "unanswered-write", (unchecked(now - unansweredWriteTime) / 1000) + "s ago"); + AddData("Keep-Alive", "keep-alive", bridge.ServerEndPoint?.WriteEverySeconds + "s"); + AddData("Previous-Physical-State", "state", oldState.ToString()); + AddData("Manager", "mgr", bridge.Multiplexer.SocketManager?.GetState()); + if (connStatus.BytesAvailableOnSocket >= 0) AddData("Inbound-Bytes", "in", connStatus.BytesAvailableOnSocket.ToString()); + if (connStatus.BytesInReadPipe >= 0) AddData("Inbound-Pipe-Bytes", "in-pipe", connStatus.BytesInReadPipe.ToString()); + if (connStatus.BytesInWritePipe >= 0) AddData("Outbound-Pipe-Bytes", "out-pipe", connStatus.BytesInWritePipe.ToString()); + + AddData("Last-Heartbeat", "last-heartbeat", (lastBeat == 0 ? "never" : ((unchecked(now - lastBeat) / 1000) + "s ago")) + (bridge.IsBeating ? " (mid-beat)" : "")); + var mbeat = bridge.Multiplexer.LastHeartbeatSecondsAgo; + if (mbeat >= 0) + { + AddData("Last-Multiplexer-Heartbeat", "last-mbeat", mbeat + "s ago"); + } + AddData("Last-Global-Heartbeat", "global", ConnectionMultiplexer.LastGlobalHeartbeatSecondsAgo + "s ago"); + } + } + + AddData("Version", "v", Utils.GetLibVersion()); + + outerException = new RedisConnectionException(failureType, exMessage.ToString(), innerException); + + foreach (var kv in data) + { + outerException.Data["Redis-" + kv.Item1] = kv.Item2; + } + + bridge?.OnConnectionFailed(this, failureType, outerException, wasRequested: weAskedForThis); + } + } + // clean up (note: avoid holding the lock when we complete things, even if this means taking + // the lock multiple times; this is fine here - we shouldn't be fighting anyone, and we're already toast) + lock (_writtenAwaitingResponse) + { + bridge?.Trace(_writtenAwaitingResponse.Count != 0, "Failing outstanding messages: " + _writtenAwaitingResponse.Count); + } + + var ex = innerException is RedisException ? innerException : outerException; + + nextMessage = Interlocked.Exchange(ref _awaitingToken, null); + if (nextMessage is not null) + { + RecordMessageFailed(nextMessage, ex, origin, bridge); + } + + while (TryDequeueLocked(_writtenAwaitingResponse, out nextMessage)) + { + RecordMessageFailed(nextMessage, ex, origin, bridge); + } + + // burn the socket + Shutdown(); + + static bool TryDequeueLocked(Queue queue, [NotNullWhen(true)] out Message? message) + { + lock (queue) + { + return queue.TryDequeue(out message); + } + } + } + + private void RecordMessageFailed(Message next, Exception? ex, string? origin, PhysicalBridge? bridge) + { + if (next.Command == RedisCommand.QUIT && next.TrySetResult(true)) + { + // fine, death of a socket is close enough + next.Complete(); + } + else + { + if (bridge != null) + { + bridge.Trace("Failing: " + next); + bridge.Multiplexer?.OnMessageFaulted(next, ex, origin); + } + next.SetExceptionAndComplete(ex!, bridge); + } + } + + internal bool IsIdle() => _writeStatus == WriteStatus.Idle; + internal void SetIdle() => _writeStatus = WriteStatus.Idle; + internal void SetWriting() => _writeStatus = WriteStatus.Writing; + + private volatile WriteStatus _writeStatus; + + internal WriteStatus GetWriteStatus() => _writeStatus; + + internal enum WriteStatus + { + Initializing, + Idle, + Writing, + Flushing, + Flushed, + + NA = -1, + } + + /// Returns a string that represents the current object. + /// A string that represents the current object. + public override string ToString() => $"{_physicalName} ({_writeStatus})"; + + internal static void IdentifyFailureType(Exception? exception, ref ConnectionFailureType failureType) + { + if (exception != null && failureType == ConnectionFailureType.InternalFailure) + { + if (exception is AggregateException) + { + exception = exception.InnerException ?? exception; + } + + failureType = exception switch + { + AuthenticationException => ConnectionFailureType.AuthenticationFailure, + EndOfStreamException or ObjectDisposedException => ConnectionFailureType.SocketClosed, + SocketException or IOException => ConnectionFailureType.SocketFailure, + _ => failureType, + }; + } + } + + internal void EnqueueInsideWriteLock(Message next) + { + var multiplexer = BridgeCouldBeNull?.Multiplexer; + if (multiplexer is null) + { + // multiplexer already collected? then we're almost certainly doomed; + // we can still process it to avoid making things worse/more complex, + // but: we can't reliably assume this works, so: shout now! + next.Cancel(); + next.Complete(); + } + + bool wasEmpty; + lock (_writtenAwaitingResponse) + { + wasEmpty = _writtenAwaitingResponse.Count == 0; + _writtenAwaitingResponse.Enqueue(next); + } + if (wasEmpty) + { + // it is important to do this *after* adding, so that we can't + // get into a thread-race where the heartbeat checks too fast; + // the fact that we're accessing Multiplexer down here means that + // we're rooting it ourselves via the stack, so we don't need + // to worry about it being collected until at least after this + multiplexer?.Root(); + } + } + + internal void GetCounters(ConnectionCounters counters) + { + lock (_writtenAwaitingResponse) + { + counters.SentItemsAwaitingResponse = _writtenAwaitingResponse.Count; + } + counters.Subscriptions = SubscriptionCount; + } + + internal Message? GetReadModeCommand(bool isPrimaryOnly) + { + if (BridgeCouldBeNull?.ServerEndPoint?.RequiresReadMode == true) + { + ReadMode requiredReadMode = isPrimaryOnly ? ReadMode.ReadWrite : ReadMode.ReadOnly; + if (requiredReadMode != currentReadMode) + { + currentReadMode = requiredReadMode; + switch (requiredReadMode) + { + case ReadMode.ReadOnly: return ReusableReadOnlyCommand; + case ReadMode.ReadWrite: return ReusableReadWriteCommand; + } + } + } + else if (currentReadMode == ReadMode.ReadOnly) + { + // we don't need it (because we're not a cluster, or not a replica), + // but we are in read-only mode; switch to read-write + currentReadMode = ReadMode.ReadWrite; + return ReusableReadWriteCommand; + } + return null; + } + + internal Message? GetSelectDatabaseCommand(int targetDatabase, Message message) + { + if (targetDatabase < 0 || targetDatabase == currentDatabase) + { + return null; + } + + if (BridgeCouldBeNull?.ServerEndPoint is not ServerEndPoint serverEndpoint) + { + return null; + } + int available = serverEndpoint.Databases; + + // Only db0 is available on cluster/twemproxy/envoyproxy + if (!serverEndpoint.SupportsDatabases) + { + if (targetDatabase != 0) + { + // We should never see this, since the API doesn't allow it; thus not too worried about ExceptionFactory + throw new RedisCommandException("Multiple databases are not supported on this server; cannot switch to database: " + targetDatabase); + } + return null; + } + + if (message.Command == RedisCommand.SELECT) + { + // This could come from an EVAL/EVALSHA inside a transaction, for example; we'll accept it + BridgeCouldBeNull?.Trace("Switching database: " + targetDatabase); + currentDatabase = targetDatabase; + return null; + } + + if (TransactionActive) + { + // Should never see this, since the API doesn't allow it, thus not too worried about ExceptionFactory + throw new RedisCommandException("Multiple databases inside a transaction are not currently supported: " + targetDatabase); + } + + // We positively know it is out of range + if (available != 0 && targetDatabase >= available) + { + throw ExceptionFactory.DatabaseOutfRange(IncludeDetailInExceptions, targetDatabase, message, serverEndpoint); + } + BridgeCouldBeNull?.Trace("Switching database: " + targetDatabase); + currentDatabase = targetDatabase; + return GetSelectDatabaseCommand(targetDatabase); + } + + internal static Message GetSelectDatabaseCommand(int targetDatabase) + { + return targetDatabase < DefaultRedisDatabaseCount + ? ReusableChangeDatabaseCommands[targetDatabase] // 0-15 by default + : Message.Create(targetDatabase, CommandFlags.FireAndForget, RedisCommand.SELECT); + } + + internal int GetSentAwaitingResponseCount() + { + lock (_writtenAwaitingResponse) + { + return _writtenAwaitingResponse.Count; + } + } + + internal void GetStormLog(StringBuilder sb) + { + lock (_writtenAwaitingResponse) + { + if (_writtenAwaitingResponse.Count == 0) return; + sb.Append("Sent, awaiting response from server: ").Append(_writtenAwaitingResponse.Count).AppendLine(); + int total = 0; + foreach (var item in _writtenAwaitingResponse) + { + if (++total >= 500) break; + item.AppendStormLog(sb); + sb.AppendLine(); + } + } + } + + /// + /// Runs on every heartbeat for a bridge, timing out any commands that are overdue and returning an integer of how many we timed out. + /// + /// How many commands were overdue and threw timeout exceptions. + internal int OnBridgeHeartbeat() + { + var result = 0; + var now = Environment.TickCount; + Interlocked.Exchange(ref lastBeatTickCount, now); + + lock (_writtenAwaitingResponse) + { + if (_writtenAwaitingResponse.Count != 0 && BridgeCouldBeNull is PhysicalBridge bridge) + { + var server = bridge.ServerEndPoint; + var multiplexer = bridge.Multiplexer; + var timeout = multiplexer.AsyncTimeoutMilliseconds; + foreach (var msg in _writtenAwaitingResponse) + { + // We only handle async timeouts here, synchronous timeouts are handled upstream. + // Those sync timeouts happen in ConnectionMultiplexer.ExecuteSyncImpl() via Monitor.Wait. + if (msg.HasTimedOut(now, timeout, out var elapsed)) + { + if (msg.ResultBoxIsAsync) + { + bool haveDeltas = msg.TryGetPhysicalState(out _, out _, out long sentDelta, out var receivedDelta) && sentDelta >= 0 && receivedDelta >= 0; + var baseErrorMessage = haveDeltas + ? $"Timeout awaiting response (outbound={sentDelta >> 10}KiB, inbound={receivedDelta >> 10}KiB, {elapsed}ms elapsed, timeout is {timeout}ms)" + : $"Timeout awaiting response ({elapsed}ms elapsed, timeout is {timeout}ms)"; + var timeoutEx = ExceptionFactory.Timeout(multiplexer, baseErrorMessage, msg, server); + multiplexer.OnMessageFaulted(msg, timeoutEx); + msg.SetExceptionAndComplete(timeoutEx, bridge); // tell the message that it is doomed + multiplexer.OnAsyncTimeout(); + result++; + } + } + else + { + // This is a head-of-line queue, which means the first thing we hit that *hasn't* timed out means no more will timeout + // and we can stop looping and release the lock early. + break; + } + // Note: it is important that we **do not** remove the message unless we're tearing down the socket; that + // would disrupt the chain for MatchResult; we just preemptively abort the message from the caller's + // perspective, and set a flag on the message so we don't keep doing it + } + } + } + return result; + } + + internal void OnInternalError(Exception exception, [CallerMemberName] string? origin = null) + { + if (BridgeCouldBeNull is PhysicalBridge bridge) + { + bridge.Multiplexer.OnInternalError(exception, bridge.ServerEndPoint.EndPoint, connectionType, origin); + } + } + + internal void SetUnknownDatabase() + { + // forces next db-specific command to issue a select + currentDatabase = -1; + } + + internal void Write(in RedisKey key) + { + var val = key.KeyValue; + if (val is string s) + { + WriteUnifiedPrefixedString(_ioPipe?.Output, key.KeyPrefix, s); + } + else + { + WriteUnifiedPrefixedBlob(_ioPipe?.Output, key.KeyPrefix, (byte[]?)val); + } + } + + internal void Write(in RedisChannel channel) + => WriteUnifiedPrefixedBlob(_ioPipe?.Output, ChannelPrefix, channel.Value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void WriteBulkString(in RedisValue value) + => WriteBulkString(value, _ioPipe?.Output); + internal static void WriteBulkString(in RedisValue value, PipeWriter? maybeNullWriter) + { + if (maybeNullWriter is not PipeWriter writer) + { + return; // Prevent null refs during disposal + } + + switch (value.Type) + { + case RedisValue.StorageType.Null: + WriteUnifiedBlob(writer, (byte[]?)null); + break; + case RedisValue.StorageType.Int64: + WriteUnifiedInt64(writer, value.OverlappedValueInt64); + break; + case RedisValue.StorageType.UInt64: + WriteUnifiedUInt64(writer, value.OverlappedValueUInt64); + break; + case RedisValue.StorageType.Double: + WriteUnifiedDouble(writer, value.OverlappedValueDouble); + break; + case RedisValue.StorageType.String: + WriteUnifiedPrefixedString(writer, null, (string?)value); + break; + case RedisValue.StorageType.Raw: + WriteUnifiedSpan(writer, ((ReadOnlyMemory)value).Span); + break; + default: + throw new InvalidOperationException($"Unexpected {value.Type} value: '{value}'"); + } + } + + internal void WriteBulkString(ReadOnlySpan value) + { + if (_ioPipe?.Output is { } writer) + { + WriteUnifiedSpan(writer, value); + } + } + + internal const int REDIS_MAX_ARGS = 1024 * 1024; // there is a <= 1024*1024 max constraint inside redis itself: https://github.com/antirez/redis/blob/6c60526db91e23fb2d666fc52facc9a11780a2a3/src/networking.c#L1024 + + internal void WriteHeader(RedisCommand command, int arguments, CommandBytes commandBytes = default) + { + if (_ioPipe?.Output is not PipeWriter writer) + { + return; // Prevent null refs during disposal + } + + var bridge = BridgeCouldBeNull ?? throw new ObjectDisposedException(ToString()); + + if (command == RedisCommand.UNKNOWN) + { + // using >= here because we will be adding 1 for the command itself (which is an arg for the purposes of the multi-bulk protocol) + if (arguments >= REDIS_MAX_ARGS) throw ExceptionFactory.TooManyArgs(commandBytes.ToString(), arguments); + } + else + { + // using >= here because we will be adding 1 for the command itself (which is an arg for the purposes of the multi-bulk protocol) + if (arguments >= REDIS_MAX_ARGS) throw ExceptionFactory.TooManyArgs(command.ToString(), arguments); + + // for everything that isn't custom commands: ask the muxer for the actual bytes + commandBytes = bridge.Multiplexer.CommandMap.GetBytes(command); + } + + // in theory we should never see this; CheckMessage dealt with "regular" messages, and + // ExecuteMessage should have dealt with everything else + if (commandBytes.IsEmpty) throw ExceptionFactory.CommandDisabled(command); + + // *{argCount}\r\n = 3 + MaxInt32TextLen + // ${cmd-len}\r\n = 3 + MaxInt32TextLen + // {cmd}\r\n = 2 + commandBytes.Length + var span = writer.GetSpan(commandBytes.Length + 8 + Format.MaxInt32TextLen + Format.MaxInt32TextLen); + span[0] = (byte)'*'; + + int offset = WriteRaw(span, arguments + 1, offset: 1); + + offset = AppendToSpanCommand(span, commandBytes, offset: offset); + + writer.Advance(offset); + } + + internal void WriteRaw(ReadOnlySpan bytes) => _ioPipe?.Output?.Write(bytes); + + internal void RecordQuit() + { + // don't blame redis if we fired the first shot + Thread.VolatileWrite(ref clientSentQuit, 1); + (_ioPipe as SocketConnection)?.TrySetProtocolShutdown(PipeShutdownKind.ProtocolExitClient); + } + + internal static void WriteMultiBulkHeader(PipeWriter output, long count) + { + // *{count}\r\n = 3 + MaxInt32TextLen + var span = output.GetSpan(3 + Format.MaxInt32TextLen); + span[0] = (byte)'*'; + int offset = WriteRaw(span, count, offset: 1); + output.Advance(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteCrlf(Span span, int offset) + { + span[offset++] = (byte)'\r'; + span[offset++] = (byte)'\n'; + return offset; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void WriteCrlf(PipeWriter writer) + { + var span = writer.GetSpan(2); + span[0] = (byte)'\r'; + span[1] = (byte)'\n'; + writer.Advance(2); + } + + internal static int WriteRaw(Span span, long value, bool withLengthPrefix = false, int offset = 0) + { + if (value >= 0 && value <= 9) + { + if (withLengthPrefix) + { + span[offset++] = (byte)'1'; + offset = WriteCrlf(span, offset); + } + span[offset++] = (byte)((int)'0' + (int)value); + } + else if (value >= 10 && value < 100) + { + if (withLengthPrefix) + { + span[offset++] = (byte)'2'; + offset = WriteCrlf(span, offset); + } + span[offset++] = (byte)((int)'0' + ((int)value / 10)); + span[offset++] = (byte)((int)'0' + ((int)value % 10)); + } + else if (value >= 100 && value < 1000) + { + int v = (int)value; + int units = v % 10; + v /= 10; + int tens = v % 10, hundreds = v / 10; + if (withLengthPrefix) + { + span[offset++] = (byte)'3'; + offset = WriteCrlf(span, offset); + } + span[offset++] = (byte)((int)'0' + hundreds); + span[offset++] = (byte)((int)'0' + tens); + span[offset++] = (byte)((int)'0' + units); + } + else if (value < 0 && value >= -9) + { + if (withLengthPrefix) + { + span[offset++] = (byte)'2'; + offset = WriteCrlf(span, offset); + } + span[offset++] = (byte)'-'; + span[offset++] = (byte)((int)'0' - (int)value); + } + else if (value <= -10 && value > -100) + { + if (withLengthPrefix) + { + span[offset++] = (byte)'3'; + offset = WriteCrlf(span, offset); + } + value = -value; + span[offset++] = (byte)'-'; + span[offset++] = (byte)((int)'0' + ((int)value / 10)); + span[offset++] = (byte)((int)'0' + ((int)value % 10)); + } + else + { + // we're going to write it, but *to the wrong place* + var availableChunk = span.Slice(offset); + var formattedLength = Format.FormatInt64(value, availableChunk); + if (withLengthPrefix) + { + // now we know how large the prefix is: write the prefix, then write the value + var prefixLength = Format.FormatInt32(formattedLength, availableChunk); + offset += prefixLength; + offset = WriteCrlf(span, offset); + + availableChunk = span.Slice(offset); + var finalLength = Format.FormatInt64(value, availableChunk); + offset += finalLength; + Debug.Assert(finalLength == formattedLength); + } + else + { + offset += formattedLength; + } + } + + return WriteCrlf(span, offset); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "DEBUG uses instance data")] + private async ValueTask FlushAsync_Awaited(PhysicalConnection connection, ValueTask flush, bool throwOnFailure) + { + try + { + await flush.ForAwait(); + connection._writeStatus = WriteStatus.Flushed; + connection.UpdateLastWriteTime(); + return WriteResult.Success; + } + catch (ConnectionResetException ex) when (!throwOnFailure) + { + connection.RecordConnectionFailed(ConnectionFailureType.SocketClosed, ex); + return WriteResult.WriteFailure; + } + } + + private CancellationTokenSource? _reusableFlushSyncTokenSource; + [Obsolete("this is an anti-pattern; work to reduce reliance on this is in progress")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0062:Make local function 'static'", Justification = "DEBUG uses instance data")] + internal WriteResult FlushSync(bool throwOnFailure, int millisecondsTimeout) + { + var cts = _reusableFlushSyncTokenSource ??= new CancellationTokenSource(); + var flush = FlushAsync(throwOnFailure, cts.Token); + if (!flush.IsCompletedSuccessfully) + { + // only schedule cancellation if it doesn't complete synchronously; at this point, it is doomed + _reusableFlushSyncTokenSource = null; + cts.CancelAfter(TimeSpan.FromMilliseconds(millisecondsTimeout)); + try + { + // here lies the evil + flush.AsTask().Wait(); + } + catch (AggregateException ex) when (ex.InnerExceptions.Any(e => e is TaskCanceledException)) + { + ThrowTimeout(); + } + finally + { + cts.Dispose(); + } + } + return flush.Result; + + void ThrowTimeout() + { + throw new TimeoutException("timeout while synchronously flushing"); + } + } + internal ValueTask FlushAsync(bool throwOnFailure, CancellationToken cancellationToken = default) + { + var tmp = _ioPipe?.Output; + if (tmp == null) return new ValueTask(WriteResult.NoConnectionAvailable); + try + { + _writeStatus = WriteStatus.Flushing; + var flush = tmp.FlushAsync(cancellationToken); + if (!flush.IsCompletedSuccessfully) return FlushAsync_Awaited(this, flush, throwOnFailure); + _writeStatus = WriteStatus.Flushed; + UpdateLastWriteTime(); + return new ValueTask(WriteResult.Success); + } + catch (ConnectionResetException ex) when (!throwOnFailure) + { + RecordConnectionFailed(ConnectionFailureType.SocketClosed, ex); + return new ValueTask(WriteResult.WriteFailure); + } + } + + private static readonly ReadOnlyMemory NullBulkString = Encoding.ASCII.GetBytes("$-1\r\n"), EmptyBulkString = Encoding.ASCII.GetBytes("$0\r\n\r\n"); + + private static void WriteUnifiedBlob(PipeWriter writer, byte[]? value) + { + if (value == null) + { + // special case: + writer.Write(NullBulkString.Span); + } + else + { + WriteUnifiedSpan(writer, new ReadOnlySpan(value)); + } + } + + private static void WriteUnifiedSpan(PipeWriter writer, ReadOnlySpan value) + { + // ${len}\r\n = 3 + MaxInt32TextLen + // {value}\r\n = 2 + value.Length + const int MaxQuickSpanSize = 512; + if (value.Length == 0) + { + // special case: + writer.Write(EmptyBulkString.Span); + } + else if (value.Length <= MaxQuickSpanSize) + { + var span = writer.GetSpan(5 + Format.MaxInt32TextLen + value.Length); + span[0] = (byte)'$'; + int bytes = AppendToSpan(span, value, 1); + writer.Advance(bytes); + } + else + { + // too big to guarantee can do in a single span + var span = writer.GetSpan(3 + Format.MaxInt32TextLen); + span[0] = (byte)'$'; + int bytes = WriteRaw(span, value.Length, offset: 1); + writer.Advance(bytes); + + writer.Write(value); + + WriteCrlf(writer); + } + } + + private static int AppendToSpanCommand(Span span, in CommandBytes value, int offset = 0) + { + span[offset++] = (byte)'$'; + int len = value.Length; + offset = WriteRaw(span, len, offset: offset); + value.CopyTo(span.Slice(offset, len)); + offset += value.Length; + return WriteCrlf(span, offset); + } + + private static int AppendToSpan(Span span, ReadOnlySpan value, int offset = 0) + { + offset = WriteRaw(span, value.Length, offset: offset); + value.CopyTo(span.Slice(offset, value.Length)); + offset += value.Length; + return WriteCrlf(span, offset); + } + + internal void WriteSha1AsHex(byte[] value) + { + if (_ioPipe?.Output is not PipeWriter writer) + { + return; // Prevent null refs during disposal + } + + if (value == null) + { + writer.Write(NullBulkString.Span); + } + else if (value.Length == ResultProcessor.ScriptLoadProcessor.Sha1HashLength) + { + // $40\r\n = 5 + // {40 bytes}\r\n = 42 + var span = writer.GetSpan(47); + span[0] = (byte)'$'; + span[1] = (byte)'4'; + span[2] = (byte)'0'; + span[3] = (byte)'\r'; + span[4] = (byte)'\n'; + + int offset = 5; + for (int i = 0; i < value.Length; i++) + { + var b = value[i]; + span[offset++] = ToHexNibble(b >> 4); + span[offset++] = ToHexNibble(b & 15); + } + span[offset++] = (byte)'\r'; + span[offset++] = (byte)'\n'; + + writer.Advance(offset); + } + else + { + throw new InvalidOperationException("Invalid SHA1 length: " + value.Length); + } + } + + internal static byte ToHexNibble(int value) + { + return value < 10 ? (byte)('0' + value) : (byte)('a' - 10 + value); + } + + internal static void WriteUnifiedPrefixedString(PipeWriter? maybeNullWriter, byte[]? prefix, string? value) + { + if (maybeNullWriter is not PipeWriter writer) + { + return; // Prevent null refs during disposal + } + + if (value == null) + { + // special case + writer.Write(NullBulkString.Span); + } + else + { + // ${total-len}\r\n 3 + MaxInt32TextLen + // {prefix}{value}\r\n + int encodedLength = Encoding.UTF8.GetByteCount(value), + prefixLength = prefix?.Length ?? 0, + totalLength = prefixLength + encodedLength; + + if (totalLength == 0) + { + // special-case + writer.Write(EmptyBulkString.Span); + } + else + { + var span = writer.GetSpan(3 + Format.MaxInt32TextLen); + span[0] = (byte)'$'; + int bytes = WriteRaw(span, totalLength, offset: 1); + writer.Advance(bytes); + + if (prefixLength != 0) writer.Write(prefix); + if (encodedLength != 0) WriteRaw(writer, value, encodedLength); + WriteCrlf(writer); + } + } + } + + [ThreadStatic] + private static Encoder? s_PerThreadEncoder; + internal static Encoder GetPerThreadEncoder() + { + var encoder = s_PerThreadEncoder; + if (encoder == null) + { + s_PerThreadEncoder = encoder = Encoding.UTF8.GetEncoder(); + } + else + { + encoder.Reset(); + } + return encoder; + } + + internal static unsafe void WriteRaw(PipeWriter writer, string value, int expectedLength) + { + const int MaxQuickEncodeSize = 512; + + fixed (char* cPtr = value) + { + int totalBytes; + if (expectedLength <= MaxQuickEncodeSize) + { + // encode directly in one hit + var span = writer.GetSpan(expectedLength); + fixed (byte* bPtr = span) + { + totalBytes = Encoding.UTF8.GetBytes(cPtr, value.Length, bPtr, expectedLength); + } + writer.Advance(expectedLength); + } + else + { + // use an encoder in a loop + var encoder = GetPerThreadEncoder(); + int charsRemaining = value.Length, charOffset = 0; + totalBytes = 0; + + bool final = false; + while (true) + { + var span = writer.GetSpan(5); // get *some* memory - at least enough for 1 character (but hopefully lots more) + + int charsUsed, bytesUsed; + bool completed; + fixed (byte* bPtr = span) + { + encoder.Convert(cPtr + charOffset, charsRemaining, bPtr, span.Length, final, out charsUsed, out bytesUsed, out completed); + } + writer.Advance(bytesUsed); + totalBytes += bytesUsed; + charOffset += charsUsed; + charsRemaining -= charsUsed; + + if (charsRemaining <= 0) + { + if (charsRemaining < 0) throw new InvalidOperationException("String encode went negative"); + if (completed) break; // fine + if (final) throw new InvalidOperationException("String encode failed to complete"); + final = true; // flush the encoder to one more span, then exit + } + } + } + if (totalBytes != expectedLength) throw new InvalidOperationException("String encode length check failure"); + } + } + + private static void WriteUnifiedPrefixedBlob(PipeWriter? maybeNullWriter, byte[]? prefix, byte[]? value) + { + if (maybeNullWriter is not PipeWriter writer) + { + return; // Prevent null refs during disposal + } + + // ${total-len}\r\n + // {prefix}{value}\r\n + if (prefix == null || prefix.Length == 0 || value == null) + { + // if no prefix, just use the non-prefixed version; + // even if prefixed, a null value writes as null, so can use the non-prefixed version + WriteUnifiedBlob(writer, value); + } + else + { + var span = writer.GetSpan(3 + Format.MaxInt32TextLen); // note even with 2 max-len, we're still in same text range + span[0] = (byte)'$'; + int bytes = WriteRaw(span, prefix.LongLength + value.LongLength, offset: 1); + writer.Advance(bytes); + + writer.Write(prefix); + writer.Write(value); + + span = writer.GetSpan(2); + WriteCrlf(span, 0); + writer.Advance(2); + } + } + + private static void WriteUnifiedInt64(PipeWriter writer, long value) + { + // note from specification: A client sends to the Redis server a RESP Array consisting of just Bulk Strings. + // (i.e. we can't just send ":123\r\n", we need to send "$3\r\n123\r\n" + + // ${asc-len}\r\n = 4/5 (asc-len at most 2 digits) + // {asc}\r\n = MaxInt64TextLen + 2 + var span = writer.GetSpan(7 + Format.MaxInt64TextLen); + + span[0] = (byte)'$'; + var bytes = WriteRaw(span, value, withLengthPrefix: true, offset: 1); + writer.Advance(bytes); + } + + private static void WriteUnifiedUInt64(PipeWriter writer, ulong value) + { + // note from specification: A client sends to the Redis server a RESP Array consisting of just Bulk Strings. + // (i.e. we can't just send ":123\r\n", we need to send "$3\r\n123\r\n" + Span valueSpan = stackalloc byte[Format.MaxInt64TextLen]; + + var len = Format.FormatUInt64(value, valueSpan); + // ${asc-len}\r\n = 4/5 (asc-len at most 2 digits) + // {asc}\r\n = {len} + 2 + var span = writer.GetSpan(7 + len); + span[0] = (byte)'$'; + int offset = WriteRaw(span, len, withLengthPrefix: false, offset: 1); + valueSpan.Slice(0, len).CopyTo(span.Slice(offset)); + offset += len; + offset = WriteCrlf(span, offset); + writer.Advance(offset); + } + + private static void WriteUnifiedDouble(PipeWriter writer, double value) + { +#if NET8_0_OR_GREATER + Span valueSpan = stackalloc byte[Format.MaxDoubleTextLen]; + var len = Format.FormatDouble(value, valueSpan); + + // ${asc-len}\r\n = 4/5 (asc-len at most 2 digits) + // {asc}\r\n = {len} + 2 + var span = writer.GetSpan(7 + len); + span[0] = (byte)'$'; + int offset = WriteRaw(span, len, withLengthPrefix: false, offset: 1); + valueSpan.Slice(0, len).CopyTo(span.Slice(offset)); + offset += len; + offset = WriteCrlf(span, offset); + writer.Advance(offset); +#else + // fallback: drop to string + WriteUnifiedPrefixedString(writer, null, Format.ToString(value)); +#endif + } + + internal static void WriteInteger(PipeWriter writer, long value) + { + // note: client should never write integer; only server does this + // :{asc}\r\n = MaxInt64TextLen + 3 + var span = writer.GetSpan(3 + Format.MaxInt64TextLen); + + span[0] = (byte)':'; + var bytes = WriteRaw(span, value, withLengthPrefix: false, offset: 1); + writer.Advance(bytes); + } + + internal readonly struct ConnectionStatus + { + /// + /// Number of messages sent outbound, but we don't yet have a response for. + /// + public int MessagesSentAwaitingResponse { get; init; } + + /// + /// Bytes available on the socket, not yet read into the pipe. + /// + public long BytesAvailableOnSocket { get; init; } + + /// + /// Bytes read from the socket, pending in the reader pipe. + /// + public long BytesInReadPipe { get; init; } + + /// + /// Bytes in the writer pipe, waiting to be written to the socket. + /// + public long BytesInWritePipe { get; init; } + + /// + /// Byte size of the last result we processed. + /// + public long BytesLastResult { get; init; } + + /// + /// Byte size on the buffer that isn't processed yet. + /// + public long BytesInBuffer { get; init; } + + /// + /// The inbound pipe reader status. + /// + public ReadStatus ReadStatus { get; init; } + + /// + /// The outbound pipe writer status. + /// + public WriteStatus WriteStatus { get; init; } + + public override string ToString() => + $"SentAwaitingResponse: {MessagesSentAwaitingResponse}, AvailableOnSocket: {BytesAvailableOnSocket} byte(s), InReadPipe: {BytesInReadPipe} byte(s), InWritePipe: {BytesInWritePipe} byte(s), ReadStatus: {ReadStatus}, WriteStatus: {WriteStatus}"; + + /// + /// The default connection stats, notable *not* the same as default since initializers don't run. + /// + public static ConnectionStatus Default { get; } = new() + { + BytesAvailableOnSocket = -1, + BytesInReadPipe = -1, + BytesInWritePipe = -1, + ReadStatus = ReadStatus.NA, + WriteStatus = WriteStatus.NA, + }; + + /// + /// The zeroed connection stats, which we want to display as zero for default exception cases. + /// + public static ConnectionStatus Zero { get; } = new() + { + BytesAvailableOnSocket = 0, + BytesInReadPipe = 0, + BytesInWritePipe = 0, + ReadStatus = ReadStatus.NA, + WriteStatus = WriteStatus.NA, + }; + } + + public ConnectionStatus GetStatus() + { + if (_ioPipe is SocketConnection conn) + { + var counters = conn.GetCounters(); + return new ConnectionStatus() + { + MessagesSentAwaitingResponse = GetSentAwaitingResponseCount(), + BytesAvailableOnSocket = counters.BytesAvailableOnSocket, + BytesInReadPipe = counters.BytesWaitingToBeRead, + BytesInWritePipe = counters.BytesWaitingToBeSent, + ReadStatus = _readStatus, + WriteStatus = _writeStatus, + BytesLastResult = bytesLastResult, + BytesInBuffer = bytesInBuffer, + }; + } + + // Fall back to bytes waiting on the socket if we can + int fallbackBytesAvailable; + try + { + fallbackBytesAvailable = VolatileSocket?.Available ?? -1; + } + catch + { + // If this fails, we're likely in a race disposal situation and do not want to blow sky high here. + fallbackBytesAvailable = -1; + } + + return new ConnectionStatus() + { + BytesAvailableOnSocket = fallbackBytesAvailable, + BytesInReadPipe = -1, + BytesInWritePipe = -1, + ReadStatus = _readStatus, + WriteStatus = _writeStatus, + BytesLastResult = bytesLastResult, + BytesInBuffer = bytesInBuffer, + }; + } + + internal static RemoteCertificateValidationCallback? GetAmbientIssuerCertificateCallback() + { + try + { + var issuerPath = Environment.GetEnvironmentVariable("SERedis_IssuerCertPath"); + if (!string.IsNullOrEmpty(issuerPath)) return ConfigurationOptions.TrustIssuerCallback(issuerPath); + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + return null; + } + internal static LocalCertificateSelectionCallback? GetAmbientClientCertificateCallback() + { + try + { + var certificatePath = Environment.GetEnvironmentVariable("SERedis_ClientCertPfxPath"); + if (!string.IsNullOrEmpty(certificatePath) && File.Exists(certificatePath)) + { + var password = Environment.GetEnvironmentVariable("SERedis_ClientCertPassword"); + var pfxStorageFlags = Environment.GetEnvironmentVariable("SERedis_ClientCertStorageFlags"); + X509KeyStorageFlags storageFlags = X509KeyStorageFlags.DefaultKeySet; + if (!string.IsNullOrEmpty(pfxStorageFlags) && Enum.TryParse(pfxStorageFlags, true, out var typedFlags)) + { + storageFlags = typedFlags; + } + + return ConfigurationOptions.CreatePfxUserCertificateCallback(certificatePath, password, storageFlags); + } + +#if NET5_0_OR_GREATER + certificatePath = Environment.GetEnvironmentVariable("SERedis_ClientCertPemPath"); + if (!string.IsNullOrEmpty(certificatePath) && File.Exists(certificatePath)) + { + var passwordPath = Environment.GetEnvironmentVariable("SERedis_ClientCertPasswordPath"); + return ConfigurationOptions.CreatePemUserCertificateCallback(certificatePath, passwordPath); + } +#endif + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + return null; + } + + internal async ValueTask ConnectedAsync(Socket? socket, ILogger? log, SocketManager manager) + { + var bridge = BridgeCouldBeNull; + if (bridge == null) return false; + + IDuplexPipe? pipe = null; + try + { + // disallow connection in some cases + OnDebugAbort(); + + // the order is important here: + // non-TLS: [Socket]<==[SocketConnection:IDuplexPipe] + // TLS: [Socket]<==[NetworkStream]<==[SslStream]<==[StreamConnection:IDuplexPipe] + var config = bridge.Multiplexer.RawConfig; + + var tunnel = config.Tunnel; + Stream? stream = null; + if (tunnel is not null) + { + stream = await tunnel.BeforeAuthenticateAsync(bridge.ServerEndPoint.EndPoint, bridge.ConnectionType, socket, CancellationToken.None).ForAwait(); + } + + if (config.Ssl) + { + log?.LogInformationConfiguringTLS(); + var host = config.SslHost; + if (host.IsNullOrWhiteSpace()) + { + host = Format.ToStringHostOnly(bridge.ServerEndPoint.EndPoint); + } + + stream ??= new NetworkStream(socket ?? throw new InvalidOperationException("No socket or stream available - possibly a tunnel error")); + var ssl = new SslStream( + innerStream: stream, + leaveInnerStreamOpen: false, + userCertificateValidationCallback: config.CertificateValidationCallback ?? GetAmbientIssuerCertificateCallback(), + userCertificateSelectionCallback: config.CertificateSelectionCallback ?? GetAmbientClientCertificateCallback(), + encryptionPolicy: EncryptionPolicy.RequireEncryption); + try + { + try + { +#if NETCOREAPP3_1_OR_GREATER + var configOptions = config.SslClientAuthenticationOptions?.Invoke(host); + if (configOptions is not null) + { + await ssl.AuthenticateAsClientAsync(configOptions).ForAwait(); + } + else + { + await ssl.AuthenticateAsClientAsync(host, config.SslProtocols, config.CheckCertificateRevocation).ForAwait(); + } +#else + await ssl.AuthenticateAsClientAsync(host, config.SslProtocols, config.CheckCertificateRevocation).ForAwait(); +#endif + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + bridge.Multiplexer.SetAuthSuspect(ex); + bridge.Multiplexer.Logger?.LogErrorConnectionIssue(ex, ex.Message); + throw; + } + log?.LogInformationTLSConnectionEstablished(ssl.SslProtocol); + } + catch (AuthenticationException authexception) + { + RecordConnectionFailed(ConnectionFailureType.AuthenticationFailure, authexception, isInitialConnect: true); + bridge.Multiplexer.Trace("Encryption failure"); + return false; + } + stream = ssl; + } + + if (stream is not null) + { + pipe = StreamConnection.GetDuplex(stream, manager.SendPipeOptions, manager.ReceivePipeOptions, name: bridge.Name); + } + else + { + pipe = SocketConnection.Create(socket, manager.SendPipeOptions, manager.ReceivePipeOptions, name: bridge.Name); + } + OnWrapForLogging(ref pipe, _physicalName, manager); + + _ioPipe = pipe; + + log?.LogInformationConnected(bridge.Name); + + await bridge.OnConnectedAsync(this, log).ForAwait(); + return true; + } + catch (Exception ex) + { + RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex, isInitialConnect: true, connectingPipe: pipe); // includes a bridge.OnDisconnected + bridge.Multiplexer.Trace("Could not connect: " + ex.Message, ToString()); + return false; + } + } + + private enum PushKind + { + None, + Message, + PMessage, + SMessage, + Subscribe, + PSubscribe, + SSubscribe, + Unsubscribe, + PUnsubscribe, + SUnsubscribe, + } + private PushKind GetPushKind(in Sequence result, out RedisChannel channel) + { + var len = result.Length; + if (len < 2) + { + // for supported cases, we demand at least the kind and the subscription channel + channel = default; + return PushKind.None; + } + + const int MAX_LEN = 16; + Debug.Assert(MAX_LEN >= Enumerable.Max( + [ + PushMessage.Length, PushPMessage.Length, PushSMessage.Length, + PushSubscribe.Length, PushPSubscribe.Length, PushSSubscribe.Length, + PushUnsubscribe.Length, PushPUnsubscribe.Length, PushSUnsubscribe.Length, + ])); + ref readonly RawResult pushKind = ref result[0]; + var multiSegmentPayload = pushKind.Payload; + if (multiSegmentPayload.Length <= MAX_LEN) + { + var span = multiSegmentPayload.IsSingleSegment + ? multiSegmentPayload.First.Span + : CopyTo(stackalloc byte[MAX_LEN], multiSegmentPayload); + + var hash = FastHash.Hash64(span); + RedisChannel.RedisChannelOptions channelOptions = RedisChannel.RedisChannelOptions.None; + PushKind kind; + switch (hash) + { + case PushMessage.Hash when PushMessage.Is(hash, span) & len >= 3: + kind = PushKind.Message; + break; + case PushPMessage.Hash when PushPMessage.Is(hash, span) & len >= 4: + channelOptions = RedisChannel.RedisChannelOptions.Pattern; + kind = PushKind.PMessage; + break; + case PushSMessage.Hash when PushSMessage.Is(hash, span) & len >= 3: + channelOptions = RedisChannel.RedisChannelOptions.Sharded; + kind = PushKind.SMessage; + break; + case PushSubscribe.Hash when PushSubscribe.Is(hash, span): + kind = PushKind.Subscribe; + break; + case PushPSubscribe.Hash when PushPSubscribe.Is(hash, span): + channelOptions = RedisChannel.RedisChannelOptions.Pattern; + kind = PushKind.PSubscribe; + break; + case PushSSubscribe.Hash when PushSSubscribe.Is(hash, span): + channelOptions = RedisChannel.RedisChannelOptions.Sharded; + kind = PushKind.SSubscribe; + break; + case PushUnsubscribe.Hash when PushUnsubscribe.Is(hash, span): + kind = PushKind.Unsubscribe; + break; + case PushPUnsubscribe.Hash when PushPUnsubscribe.Is(hash, span): + channelOptions = RedisChannel.RedisChannelOptions.Pattern; + kind = PushKind.PUnsubscribe; + break; + case PushSUnsubscribe.Hash when PushSUnsubscribe.Is(hash, span): + channelOptions = RedisChannel.RedisChannelOptions.Sharded; + kind = PushKind.SUnsubscribe; + break; + default: + kind = PushKind.None; + break; + } + if (kind != PushKind.None) + { + // the channel is always the second element + channel = result[1].AsRedisChannel(ChannelPrefix, channelOptions); + return kind; + } + } + channel = default; + return PushKind.None; + + static ReadOnlySpan CopyTo(Span target, in ReadOnlySequence source) + { + source.CopyTo(target); + return target.Slice(0, (int)source.Length); + } + } + + [FastHash("message")] + private static partial class PushMessage { } + + [FastHash("pmessage")] + private static partial class PushPMessage { } + + [FastHash("smessage")] + private static partial class PushSMessage { } + + [FastHash("subscribe")] + private static partial class PushSubscribe { } + + [FastHash("psubscribe")] + private static partial class PushPSubscribe { } + + [FastHash("ssubscribe")] + private static partial class PushSSubscribe { } + + [FastHash("unsubscribe")] + private static partial class PushUnsubscribe { } + + [FastHash("punsubscribe")] + private static partial class PushPUnsubscribe { } + + [FastHash("sunsubscribe")] + private static partial class PushSUnsubscribe { } + + private void MatchResult(in RawResult result) + { + // check to see if it could be an out-of-band pubsub message + if ((connectionType == ConnectionType.Subscription && result.Resp2TypeArray == ResultType.Array) || result.Resp3Type == ResultType.Push) + { + var muxer = BridgeCouldBeNull?.Multiplexer; + if (muxer == null) return; + + // out of band message does not match to a queued message + var items = result.GetItems(); + var kind = GetPushKind(items, out var subscriptionChannel); + switch (kind) + { + case PushKind.Message: + case PushKind.SMessage: + _readStatus = kind is PushKind.Message ? ReadStatus.PubSubMessage : ReadStatus.PubSubSMessage; + + // special-case the configuration change broadcasts (we don't keep that in the usual pub/sub registry) + var configChanged = muxer.ConfigurationChangedChannel; + if (configChanged != null && items[1].IsEqual(configChanged)) + { + EndPoint? blame = null; + try + { + if (!items[2].IsEqual(CommonReplies.wildcard)) + { + // We don't want to fail here, just trying to identify + _ = Format.TryParseEndPoint(items[2].GetString(), out blame); + } + } + catch + { + /* no biggie */ + } + + Trace("Configuration changed: " + Format.ToString(blame)); + _readStatus = ReadStatus.Reconfigure; + muxer.ReconfigureIfNeeded(blame, true, "broadcast"); + } + + // invoke the handlers + if (!subscriptionChannel.IsNull) + { + Trace($"{kind}: {subscriptionChannel}"); + if (TryGetPubSubPayload(items[2], out var payload)) + { + _readStatus = ReadStatus.InvokePubSub; + muxer.OnMessage(subscriptionChannel, subscriptionChannel, payload); + } + // could be multi-message: https://github.com/StackExchange/StackExchange.Redis/issues/2507 + else if (TryGetMultiPubSubPayload(items[2], out var payloads)) + { + _readStatus = ReadStatus.InvokePubSub; + muxer.OnMessage(subscriptionChannel, subscriptionChannel, payloads); + } + } + return; // and stop processing + case PushKind.PMessage: + _readStatus = ReadStatus.PubSubPMessage; + + var messageChannel = items[2].AsRedisChannel(ChannelPrefix, RedisChannel.RedisChannelOptions.None); + if (!messageChannel.IsNull) + { + Trace($"{kind}: {messageChannel} via {subscriptionChannel}"); + if (TryGetPubSubPayload(items[3], out var payload)) + { + _readStatus = ReadStatus.InvokePubSub; + muxer.OnMessage(subscriptionChannel, messageChannel, payload); + } + else if (TryGetMultiPubSubPayload(items[3], out var payloads)) + { + _readStatus = ReadStatus.InvokePubSub; + muxer.OnMessage(subscriptionChannel, messageChannel, payloads); + } + } + return; // and stop processing + case PushKind.SUnsubscribe when !PeekChannelMessage(RedisCommand.SUNSUBSCRIBE, subscriptionChannel): + // then it was *unsolicited* - this probably means the slot was migrated + // (otherwise, we'll let the command-processor deal with it) + _readStatus = ReadStatus.PubSubUnsubscribe; + var server = BridgeCouldBeNull?.ServerEndPoint; + if (server is not null && muxer.TryGetSubscription(subscriptionChannel, out var subscription)) + { + // wipe and reconnect; but: to where? + // counter-intuitively, the only server we *know* already knows the new route is: + // the outgoing server, since it had to change to MIGRATING etc; the new INCOMING server + // knows, but *we don't know who that is*, and other nodes: aren't guaranteed to know (yet) + muxer.DefaultSubscriber.ResubscribeToServer(subscription, subscriptionChannel, server, cause: PushSUnsubscribe.Text); + } + return; // and STOP PROCESSING; unsolicited + } + } + Trace("Matching result..."); + + Message? msg = null; + // check whether we're waiting for a high-integrity mode post-response checksum (using cheap null-check first) + if (_awaitingToken is not null && (msg = Interlocked.Exchange(ref _awaitingToken, null)) is not null) + { + _readStatus = ReadStatus.ResponseSequenceCheck; + if (!ProcessHighIntegrityResponseToken(msg, in result, BridgeCouldBeNull)) + { + RecordConnectionFailed(ConnectionFailureType.ResponseIntegrityFailure, origin: nameof(ReadStatus.ResponseSequenceCheck)); + } + return; + } + + _readStatus = ReadStatus.DequeueResult; + lock (_writtenAwaitingResponse) + { + if (msg is not null) + { + _awaitingToken = null; + } + + if (!_writtenAwaitingResponse.TryDequeue(out msg)) + { + throw new InvalidOperationException("Received response with no message waiting: " + result.ToString()); + } + } + _activeMessage = msg; + + Trace("Response to: " + msg); + _readStatus = ReadStatus.ComputeResult; + if (msg.ComputeResult(this, result)) + { + _readStatus = msg.ResultBoxIsAsync ? ReadStatus.CompletePendingMessageAsync : ReadStatus.CompletePendingMessageSync; + if (!msg.IsHighIntegrity) + { + // can't complete yet if needs checksum + msg.Complete(); + } + } + if (msg.IsHighIntegrity) + { + // stash this for the next non-OOB response + Volatile.Write(ref _awaitingToken, msg); + } + + _readStatus = ReadStatus.MatchResultComplete; + _activeMessage = null; + + static bool ProcessHighIntegrityResponseToken(Message message, in RawResult result, PhysicalBridge? bridge) + { + bool isValid = false; + if (result.Resp2TypeBulkString == ResultType.BulkString) + { + var payload = result.Payload; + if (payload.Length == 4) + { + uint interpreted; + if (payload.IsSingleSegment) + { + interpreted = BinaryPrimitives.ReadUInt32LittleEndian(payload.First.Span); + } + else + { + Span span = stackalloc byte[4]; + payload.CopyTo(span); + interpreted = BinaryPrimitives.ReadUInt32LittleEndian(span); + } + isValid = interpreted == message.HighIntegrityToken; + } + } + if (isValid) + { + message.Complete(); + return true; + } + else + { + message.SetExceptionAndComplete(new InvalidOperationException("High-integrity mode detected possible protocol de-sync"), bridge); + return false; + } + } + + static bool TryGetPubSubPayload(in RawResult value, out RedisValue parsed, bool allowArraySingleton = true) + { + if (value.IsNull) + { + parsed = RedisValue.Null; + return true; + } + switch (value.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + parsed = value.AsRedisValue(); + return true; + case ResultType.Array when allowArraySingleton && value.ItemsCount == 1: + return TryGetPubSubPayload(in value[0], out parsed, allowArraySingleton: false); + } + parsed = default; + return false; + } + + static bool TryGetMultiPubSubPayload(in RawResult value, out Sequence parsed) + { + if (value.Resp2TypeArray == ResultType.Array && value.ItemsCount != 0) + { + parsed = value.GetItems(); + return true; + } + parsed = default; + return false; + } + } + + private bool PeekChannelMessage(RedisCommand command, RedisChannel channel) + { + Message? msg; + bool haveMsg; + lock (_writtenAwaitingResponse) + { + haveMsg = _writtenAwaitingResponse.TryPeek(out msg); + } + + return haveMsg && msg is CommandChannelBase typed + && typed.Command == command && typed.Channel == channel; + } + + private volatile Message? _activeMessage; + + internal void GetHeadMessages(out Message? now, out Message? next) + { + now = _activeMessage; + bool haveLock = false; + try + { + // careful locking here; a: don't try too hard (this is error info only), b: avoid deadlock (see #2376) + Monitor.TryEnter(_writtenAwaitingResponse, 10, ref haveLock); + if (haveLock) + { + _writtenAwaitingResponse.TryPeek(out next); + } + else + { + next = UnknownMessage.Instance; + } + } + finally + { + if (haveLock) Monitor.Exit(_writtenAwaitingResponse); + } + } + + partial void OnCloseEcho(); + + partial void OnCreateEcho(); + + private void OnDebugAbort() + { + var bridge = BridgeCouldBeNull; + if (bridge == null || !bridge.Multiplexer.AllowConnect) + { + throw new RedisConnectionException(ConnectionFailureType.InternalFailure, "Aborting (AllowConnect: False)"); + } + } + + partial void OnWrapForLogging(ref IDuplexPipe pipe, string name, SocketManager mgr); + + internal void UpdateLastReadTime() => Interlocked.Exchange(ref lastReadTickCount, Environment.TickCount); + private async Task ReadFromPipe() + { + bool allowSyncRead = true, isReading = false; + try + { + _readStatus = ReadStatus.Init; + while (true) + { + var input = _ioPipe?.Input; + if (input == null) break; + + // note: TryRead will give us back the same buffer in a tight loop + // - so: only use that if we're making progress + isReading = true; + _readStatus = ReadStatus.ReadSync; + if (!(allowSyncRead && input.TryRead(out var readResult))) + { + _readStatus = ReadStatus.ReadAsync; + readResult = await input.ReadAsync().ForAwait(); + } + isReading = false; + _readStatus = ReadStatus.UpdateWriteTime; + UpdateLastReadTime(); + + _readStatus = ReadStatus.ProcessBuffer; + var buffer = readResult.Buffer; + int handled = 0; + if (!buffer.IsEmpty) + { + handled = ProcessBuffer(ref buffer); // updates buffer.Start + } + + allowSyncRead = handled != 0; + + _readStatus = ReadStatus.MarkProcessed; + Trace($"Processed {handled} messages"); + input.AdvanceTo(buffer.Start, buffer.End); + + if (handled == 0 && readResult.IsCompleted) + { + break; // no more data, or trailing incomplete messages + } + } + Trace("EOF"); + RecordConnectionFailed(ConnectionFailureType.SocketClosed); + _readStatus = ReadStatus.RanToCompletion; + } + catch (Exception ex) + { + _readStatus = ReadStatus.Faulted; + // this CEX is just a hardcore "seriously, read the actual value" - there's no + // convenient "Thread.VolatileRead(ref T field) where T : class", and I don't + // want to make the field volatile just for this one place that needs it + if (isReading) + { + var pipe = Volatile.Read(ref _ioPipe); + if (pipe == null) + { + return; + // yeah, that's fine... don't worry about it; we nuked it + } + + // check for confusing read errors - no need to present "Reading is not allowed after reader was completed." + if (pipe is SocketConnection sc && sc.ShutdownKind == PipeShutdownKind.ReadEndOfStream) + { + RecordConnectionFailed(ConnectionFailureType.SocketClosed, new EndOfStreamException()); + return; + } + } + Trace("Faulted"); + RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); + } + } + + private static readonly ArenaOptions s_arenaOptions = new ArenaOptions(); + private readonly Arena _arena = new Arena(s_arenaOptions); + + private int ProcessBuffer(ref ReadOnlySequence buffer) + { + int messageCount = 0; + bytesInBuffer = buffer.Length; + + while (!buffer.IsEmpty) + { + _readStatus = ReadStatus.TryParseResult; + var reader = new BufferReader(buffer); + var result = TryParseResult(_protocol >= RedisProtocol.Resp3, _arena, in buffer, ref reader, IncludeDetailInExceptions, this); + try + { + if (result.HasValue) + { + buffer = reader.SliceFromCurrent(); + + messageCount++; + Trace(result.ToString()); + _readStatus = ReadStatus.MatchResult; + MatchResult(result); + + // Track the last result size *after* processing for the *next* error message + bytesInBuffer = buffer.Length; + bytesLastResult = result.Payload.Length; + } + else + { + break; // remaining buffer isn't enough; give up + } + } + finally + { + _readStatus = ReadStatus.ResetArena; + _arena.Reset(); + } + } + _readStatus = ReadStatus.ProcessBufferComplete; + return messageCount; + } + + private static RawResult.ResultFlags AsNull(RawResult.ResultFlags flags) => flags & ~RawResult.ResultFlags.NonNull; + + private static RawResult ReadArray(ResultType resultType, RawResult.ResultFlags flags, Arena arena, in ReadOnlySequence buffer, ref BufferReader reader, bool includeDetailInExceptions, ServerEndPoint? server) + { + var itemCount = ReadLineTerminatedString(ResultType.Integer, flags, ref reader); + if (itemCount.HasValue) + { + if (!itemCount.TryGetInt64(out long i64)) + { + throw ExceptionFactory.ConnectionFailure( + includeDetailInExceptions, + ConnectionFailureType.ProtocolFailure, + itemCount.Is('?') ? "Streamed aggregate types not yet implemented" : "Invalid array length", + server); + } + + int itemCountActual = checked((int)i64); + + if (itemCountActual < 0) + { + // for null response by command like EXEC, RESP array: *-1\r\n + return new RawResult(resultType, items: default, AsNull(flags)); + } + else if (itemCountActual == 0) + { + // for zero array response by command like SCAN, Resp array: *0\r\n + return new RawResult(resultType, items: default, flags); + } + + if (resultType == ResultType.Map) itemCountActual <<= 1; // if it says "3", it means 3 pairs, i.e. 6 values + + var oversized = arena.Allocate(itemCountActual); + var result = new RawResult(resultType, oversized, flags); + + if (oversized.IsSingleSegment) + { + var span = oversized.FirstSpan; + for (int i = 0; i < span.Length; i++) + { + if (!(span[i] = TryParseResult(flags, arena, in buffer, ref reader, includeDetailInExceptions, server)).HasValue) + { + return RawResult.Nil; + } + } + } + else + { + foreach (var span in oversized.Spans) + { + for (int i = 0; i < span.Length; i++) + { + if (!(span[i] = TryParseResult(flags, arena, in buffer, ref reader, includeDetailInExceptions, server)).HasValue) + { + return RawResult.Nil; + } + } + } + } + return result; + } + return RawResult.Nil; + } + + private static RawResult ReadBulkString(ResultType type, RawResult.ResultFlags flags, ref BufferReader reader, bool includeDetailInExceptions, ServerEndPoint? server) + { + var prefix = ReadLineTerminatedString(ResultType.Integer, flags, ref reader); + if (prefix.HasValue) + { + if (!prefix.TryGetInt64(out long i64)) + { + throw ExceptionFactory.ConnectionFailure( + includeDetailInExceptions, + ConnectionFailureType.ProtocolFailure, + prefix.Is('?') ? "Streamed strings not yet implemented" : "Invalid bulk string length", + server); + } + int bodySize = checked((int)i64); + if (bodySize < 0) + { + return new RawResult(type, ReadOnlySequence.Empty, AsNull(flags)); + } + + if (reader.TryConsumeAsBuffer(bodySize, out var payload)) + { + switch (reader.TryConsumeCRLF()) + { + case ConsumeResult.NeedMoreData: + break; // see NilResult below + case ConsumeResult.Success: + return new RawResult(type, payload, flags); + default: + throw ExceptionFactory.ConnectionFailure(includeDetailInExceptions, ConnectionFailureType.ProtocolFailure, "Invalid bulk string terminator", server); + } + } + } + return RawResult.Nil; + } + + private static RawResult ReadLineTerminatedString(ResultType type, RawResult.ResultFlags flags, ref BufferReader reader) + { + int crlfOffsetFromCurrent = BufferReader.FindNextCrLf(reader); + if (crlfOffsetFromCurrent < 0) return RawResult.Nil; + + var payload = reader.ConsumeAsBuffer(crlfOffsetFromCurrent); + reader.Consume(2); + + return new RawResult(type, payload, flags); + } + + internal enum ReadStatus + { + NotStarted, + Init, + RanToCompletion, + Faulted, + ReadSync, + ReadAsync, + UpdateWriteTime, + ProcessBuffer, + MarkProcessed, + TryParseResult, + MatchResult, + PubSubMessage, + PubSubPMessage, + PubSubSMessage, + Reconfigure, + InvokePubSub, + ResponseSequenceCheck, // high-integrity mode only + DequeueResult, + ComputeResult, + CompletePendingMessageSync, + CompletePendingMessageAsync, + MatchResultComplete, + ResetArena, + ProcessBufferComplete, + PubSubUnsubscribe, + NA = -1, + } + private volatile ReadStatus _readStatus; + internal ReadStatus GetReadStatus() => _readStatus; + + internal void StartReading() => ReadFromPipe().RedisFireAndForget(); + + internal static RawResult TryParseResult( + bool isResp3, + Arena arena, + in ReadOnlySequence buffer, + ref BufferReader reader, + bool includeDetilInExceptions, + PhysicalConnection? connection, + bool allowInlineProtocol = false) + { + return TryParseResult( + isResp3 ? (RawResult.ResultFlags.Resp3 | RawResult.ResultFlags.NonNull) : RawResult.ResultFlags.NonNull, + arena, + buffer, + ref reader, + includeDetilInExceptions, + connection?.BridgeCouldBeNull?.ServerEndPoint, + allowInlineProtocol); + } + + private static RawResult TryParseResult( + RawResult.ResultFlags flags, + Arena arena, + in ReadOnlySequence buffer, + ref BufferReader reader, + bool includeDetilInExceptions, + ServerEndPoint? server, + bool allowInlineProtocol = false) + { + int prefix; + do // this loop is just to allow us to parse (skip) attributes without doing a stack-dive + { + prefix = reader.PeekByte(); + if (prefix < 0) return RawResult.Nil; // EOF + switch (prefix) + { + // RESP2 + case '+': // simple string + reader.Consume(1); + return ReadLineTerminatedString(ResultType.SimpleString, flags, ref reader); + case '-': // error + reader.Consume(1); + return ReadLineTerminatedString(ResultType.Error, flags, ref reader); + case ':': // integer + reader.Consume(1); + return ReadLineTerminatedString(ResultType.Integer, flags, ref reader); + case '$': // bulk string + reader.Consume(1); + return ReadBulkString(ResultType.BulkString, flags, ref reader, includeDetilInExceptions, server); + case '*': // array + reader.Consume(1); + return ReadArray(ResultType.Array, flags, arena, in buffer, ref reader, includeDetilInExceptions, server); + // RESP3 + case '_': // null + reader.Consume(1); + return ReadLineTerminatedString(ResultType.Null, flags, ref reader); + case ',': // double + reader.Consume(1); + return ReadLineTerminatedString(ResultType.Double, flags, ref reader); + case '#': // boolean + reader.Consume(1); + return ReadLineTerminatedString(ResultType.Boolean, flags, ref reader); + case '!': // blob error + reader.Consume(1); + return ReadBulkString(ResultType.BlobError, flags, ref reader, includeDetilInExceptions, server); + case '=': // verbatim string + reader.Consume(1); + return ReadBulkString(ResultType.VerbatimString, flags, ref reader, includeDetilInExceptions, server); + case '(': // big number + reader.Consume(1); + return ReadLineTerminatedString(ResultType.BigInteger, flags, ref reader); + case '%': // map + reader.Consume(1); + return ReadArray(ResultType.Map, flags, arena, in buffer, ref reader, includeDetilInExceptions, server); + case '~': // set + reader.Consume(1); + return ReadArray(ResultType.Set, flags, arena, in buffer, ref reader, includeDetilInExceptions, server); + case '|': // attribute + reader.Consume(1); + var arr = ReadArray(ResultType.Attribute, flags, arena, in buffer, ref reader, includeDetilInExceptions, server); + if (!arr.HasValue) return RawResult.Nil; // failed to parse attribute data + + // for now, we want to just skip attribute data; so + // drop whatever we parsed on the floor and keep looking + break; // exits the SWITCH, not the DO/WHILE + case '>': // push + reader.Consume(1); + return ReadArray(ResultType.Push, flags, arena, in buffer, ref reader, includeDetilInExceptions, server); + } + } + while (prefix == '|'); + + if (allowInlineProtocol) return ParseInlineProtocol(flags, arena, ReadLineTerminatedString(ResultType.SimpleString, flags, ref reader)); + throw new InvalidOperationException("Unexpected response prefix: " + (char)prefix); + } + + private static RawResult ParseInlineProtocol(RawResult.ResultFlags flags, Arena arena, in RawResult line) + { + if (!line.HasValue) return RawResult.Nil; // incomplete line + + int count = 0; + foreach (var _ in line.GetInlineTokenizer()) count++; + var block = arena.Allocate(count); + + var iter = block.GetEnumerator(); + foreach (var token in line.GetInlineTokenizer()) + { + // this assigns *via a reference*, returned via the iterator; just... sweet + iter.GetNext() = new RawResult(line.Resp3Type, token, flags); // spoof RESP2 from RESP1 + } + return new RawResult(ResultType.Array, block, flags); // spoof RESP2 from RESP1 + } + + internal bool HasPendingCallerFacingItems() + { + bool lockTaken = false; + try + { + Monitor.TryEnter(_writtenAwaitingResponse, 0, ref lockTaken); + if (lockTaken) + { + if (_writtenAwaitingResponse.Count != 0) + { + foreach (var item in _writtenAwaitingResponse) + { + if (!item.IsInternalCall) return true; + } + } + return false; + } + else + { + // don't contend the lock; *presume* that something + // qualifies; we can check again next heartbeat + return true; + } + } + finally + { + if (lockTaken) Monitor.Exit(_writtenAwaitingResponse); + } + } + } +} diff --git a/src/StackExchange.Redis/Profiling/IProfiledCommand.cs b/src/StackExchange.Redis/Profiling/IProfiledCommand.cs new file mode 100644 index 000000000..2f9c3cb54 --- /dev/null +++ b/src/StackExchange.Redis/Profiling/IProfiledCommand.cs @@ -0,0 +1,93 @@ +using System; +using System.Net; + +namespace StackExchange.Redis.Profiling +{ + /// + /// A profiled command against a redis instance. + /// + /// TimeSpans returned by this interface use a high precision timer if possible. + /// DateTimes returned by this interface are no more precise than DateTime.UtcNow. + /// + /// + public interface IProfiledCommand + { + /// + /// The endpoint this command was sent to. + /// + EndPoint EndPoint { get; } + + /// + /// The Db this command was sent to. + /// + int Db { get; } + + /// + /// The name of this command. + /// + string Command { get; } + + /// + /// The CommandFlags the command was submitted with. + /// + CommandFlags Flags { get; } + + /// + /// + /// When this command was *created*, will be approximately + /// when the paired method of StackExchange.Redis was called but + /// before that method returned. + /// + /// Note that the resolution of the returned DateTime is limited by DateTime.UtcNow. + /// + DateTime CommandCreated { get; } + + /// + /// How long this command waited to be added to the queue of pending + /// redis commands. A large TimeSpan indicates serious contention for + /// the pending queue. + /// + TimeSpan CreationToEnqueued { get; } + + /// + /// How long this command spent in the pending queue before being sent to redis. + /// A large TimeSpan can indicate a large number of pending events, large pending events, + /// or network issues. + /// + TimeSpan EnqueuedToSending { get; } + + /// + /// How long before Redis responded to this command and it's response could be handled after it was sent. + /// A large TimeSpan can indicate a large response body, an overtaxed redis instance, or network issues. + /// + TimeSpan SentToResponse { get; } + + /// + /// How long between Redis responding to this command and awaiting consumers being notified. + /// + TimeSpan ResponseToCompletion { get; } + + /// + /// How long it took this redis command to be processed, from creation to deserializing the final response. + /// Note that this TimeSpan *does not* include time spent awaiting a Task in consumer code. + /// + TimeSpan ElapsedTime { get; } + + /// + /// + /// If a command has to be resent due to an ASK or MOVED response from redis (in a cluster configuration), + /// the second sending of the command will have this property set to the original IProfiledCommand. + /// + /// This can only be set if redis is configured as a cluster. + /// + IProfiledCommand? RetransmissionOf { get; } + + /// + /// If RetransmissionOf is not null, this property will be set to either Ask or Moved to indicate + /// what sort of response triggered the retransmission. + /// + /// This can be useful for determining the root cause of extra commands. + /// + RetransmissionReasonType? RetransmissionReason { get; } + } +} diff --git a/src/StackExchange.Redis/Profiling/ProfiledCommand.cs b/src/StackExchange.Redis/Profiling/ProfiledCommand.cs new file mode 100644 index 000000000..84ea23f08 --- /dev/null +++ b/src/StackExchange.Redis/Profiling/ProfiledCommand.cs @@ -0,0 +1,130 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace StackExchange.Redis.Profiling +{ + internal sealed class ProfiledCommand : IProfiledCommand + { + private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; + + public EndPoint EndPoint => Server.EndPoint; + + public int Db => Message!.Db; + + public string Command => Message!.CommandString; + + public CommandFlags Flags => Message!.Flags; + + public DateTime CommandCreated { get; private set; } + + public TimeSpan CreationToEnqueued => GetElapsedTime(EnqueuedTimeStamp - MessageCreatedTimeStamp); + + public TimeSpan EnqueuedToSending => GetElapsedTime(RequestSentTimeStamp - EnqueuedTimeStamp); + + public TimeSpan SentToResponse => GetElapsedTime(ResponseReceivedTimeStamp - RequestSentTimeStamp); + + public TimeSpan ResponseToCompletion => GetElapsedTime(CompletedTimeStamp - ResponseReceivedTimeStamp); + + public TimeSpan ElapsedTime => GetElapsedTime(CompletedTimeStamp - MessageCreatedTimeStamp); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TimeSpan GetElapsedTime(long timestampDelta) + { + return new TimeSpan((long)(TimestampToTicks * timestampDelta)); + } + + public IProfiledCommand? RetransmissionOf => OriginalProfiling; + + public RetransmissionReasonType? RetransmissionReason { get; } + + public ProfiledCommand? NextElement { get; set; } + + private Message? Message; + private readonly ServerEndPoint Server; + private readonly ProfiledCommand? OriginalProfiling; + private long MessageCreatedTimeStamp; + private long EnqueuedTimeStamp; + private long RequestSentTimeStamp; + private long ResponseReceivedTimeStamp; + private long CompletedTimeStamp; + private ConnectionType? ConnectionType; + + private readonly ProfilingSession PushToWhenFinished; + + private ProfiledCommand(ProfilingSession pushTo, ServerEndPoint server, ProfiledCommand? resentFor, RetransmissionReasonType? reason) + { + PushToWhenFinished = pushTo; + OriginalProfiling = resentFor; + Server = server; + RetransmissionReason = reason; + } + + public static ProfiledCommand NewWithContext(ProfilingSession pushTo, ServerEndPoint server) + { + return new ProfiledCommand(pushTo, server, null, null); + } + + public static ProfiledCommand NewAttachedToSameContext(ProfiledCommand resentFor, ServerEndPoint server, bool isMoved) + { + return new ProfiledCommand(resentFor.PushToWhenFinished, server, resentFor, isMoved ? RetransmissionReasonType.Moved : RetransmissionReasonType.Ask); + } + + [MemberNotNull(nameof(Message))] + public void SetMessage(Message msg) + { + // This method should never be called twice + if (Message is not null) + { + throw new InvalidOperationException($"{nameof(SetMessage)} called more than once"); + } + + Message = msg; + CommandCreated = msg.CreatedDateTime; + MessageCreatedTimeStamp = msg.CreatedTimestamp; + } + + public void SetEnqueued(ConnectionType? connType) + { + SetTimestamp(ref EnqueuedTimeStamp); + ConnectionType = connType; + } + + public void SetRequestSent() => SetTimestamp(ref RequestSentTimeStamp); + + public void SetResponseReceived() => SetTimestamp(ref ResponseReceivedTimeStamp); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetTimestamp(ref long field) + { + var now = Stopwatch.GetTimestamp(); + Interlocked.CompareExchange(ref field, now, 0); + } + + public void SetCompleted() + { + // this method can be called multiple times, depending on how the task completed (async vs not) + // so we actually have to guard against it. + var now = Stopwatch.GetTimestamp(); + var oldVal = Interlocked.CompareExchange(ref CompletedTimeStamp, now, 0); + + // only push on the first call, no dupes! + if (oldVal == 0) + { + // fake a response if we completed prematurely (timeout, broken connection, etc) + Interlocked.CompareExchange(ref ResponseReceivedTimeStamp, now, 0); + PushToWhenFinished?.Add(this); + } + } + + public override string ToString() => +$@"{Command} (DB: {Db}, Flags: {Flags}) + EndPoint = {EndPoint} ({ConnectionType}) + Created = {CommandCreated:HH:mm:ss.ffff} + ElapsedTime = {ElapsedTime.TotalMilliseconds} ms (CreationToEnqueued: {CreationToEnqueued.TotalMilliseconds} ms, EnqueuedToSending: {EnqueuedToSending.TotalMilliseconds} ms, SentToResponse: {SentToResponse.TotalMilliseconds} ms, ResponseToCompletion = {ResponseToCompletion.TotalMilliseconds} ms){(RetransmissionOf != null ? @" + RetransmissionOf = " + RetransmissionOf : "")}"; + } +} diff --git a/src/StackExchange.Redis/Profiling/ProfiledCommandEnumerable.cs b/src/StackExchange.Redis/Profiling/ProfiledCommandEnumerable.cs new file mode 100644 index 000000000..4505410a3 --- /dev/null +++ b/src/StackExchange.Redis/Profiling/ProfiledCommandEnumerable.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace StackExchange.Redis.Profiling +{ + /// + /// A collection of IProfiledCommands. + /// This is a very light weight data structure, only supporting enumeration. + /// + /// While it implements IEnumerable, it there are fewer allocations if one uses + /// it's explicit GetEnumerator() method. Using `foreach` does this automatically. + /// + /// This type is not threadsafe. + /// + public readonly struct ProfiledCommandEnumerable : IEnumerable + { + /// + /// + /// Implements IEnumerator for ProfiledCommandEnumerable. + /// This implementation is comparable to List.Enumerator and Dictionary.Enumerator, + /// and is provided to reduce allocations in the common (ie. foreach) case. + /// + /// This type is not threadsafe. + /// + public struct Enumerator : IEnumerator + { + private ProfiledCommand? Head, CurrentBacker; + + private bool IsEmpty => Head == null; + private bool IsUnstartedOrFinished => CurrentBacker == null; + + internal Enumerator(ProfiledCommand? head) + { + Head = head; + CurrentBacker = null; + } + + /// + /// The current element. + /// + public IProfiledCommand Current => CurrentBacker!; + + object System.Collections.IEnumerator.Current => CurrentBacker!; + + /// + /// Advances the enumeration, returning true if there is a new element to consume and false + /// if enumeration is complete. + /// + public bool MoveNext() + { + if (IsEmpty) return false; + + if (IsUnstartedOrFinished) + { + CurrentBacker = Head; + } + else + { + CurrentBacker = CurrentBacker!.NextElement; + } + + return CurrentBacker != null; + } + + /// + /// Resets the enumeration. + /// + public void Reset() => CurrentBacker = null; + + /// + /// Disposes the enumeration. + /// subsequent attempts to enumerate results in undefined behavior. + /// + public void Dispose() => CurrentBacker = Head = null; + } + + private readonly ProfiledCommand? _head; + private readonly int _count; + + /// + /// Returns the number of commands captured in this snapshot. + /// + public int Count() => _count; + + /// + /// Returns the number of commands captured in this snapshot that match a condition. + /// + /// The predicate to match. + public int Count(Func predicate) + { + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); + if (_count == 0) return 0; + + int result = 0; + var cur = _head; + for (int i = 0; i < _count; i++) + { + if (cur != null && predicate(cur)) result++; + cur = cur!.NextElement; + } + return result; + } + + /// + /// Returns the captured commands as an array. + /// + public IProfiledCommand[] ToArray() + { + // exploit the fact that we know the length + if (_count == 0) return Array.Empty(); + + var arr = new IProfiledCommand[_count]; + ProfiledCommand? cur = _head; + for (int i = 0; i < _count; i++) + { + arr[i] = cur!; + cur = cur!.NextElement; + } + return arr; + } + + /// + /// Returns the captured commands as a list. + /// + public List ToList() + { + // exploit the fact that we know the length + var list = new List(_count); + ProfiledCommand? cur = _head; + while (cur != null) + { + list.Add(cur); + cur = cur.NextElement; + } + return list; + } + internal ProfiledCommandEnumerable(int count, ProfiledCommand? head) + { + _count = count; + _head = head; + + Debug.Assert(_count == Enumerable.Count(this)); + } + + /// + /// + /// Returns an implementor of IEnumerator that, provided it isn't accessed + /// though an interface, avoids allocations. + /// + /// `foreach` will automatically use this method. + /// + public Enumerator GetEnumerator() => new Enumerator(_head); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/StackExchange.Redis/Profiling/ProfilingSession.cs b/src/StackExchange.Redis/Profiling/ProfilingSession.cs new file mode 100644 index 000000000..3bc3caf38 --- /dev/null +++ b/src/StackExchange.Redis/Profiling/ProfilingSession.cs @@ -0,0 +1,60 @@ +using System.Threading; + +namespace StackExchange.Redis.Profiling +{ + /// + /// Lightweight profiling session that can be optionally registered (via ConnectionMultiplexer.RegisterProfiler) to track messages. + /// + public sealed class ProfilingSession + { + /// + /// Caller-defined state object. + /// + public object? UserToken { get; } + + /// + /// Create a new profiling session, optionally including a caller-defined state object. + /// + /// The state object to use for this session. + public ProfilingSession(object? userToken = null) => UserToken = userToken; + + private object? _untypedHead; + + internal void Add(ProfiledCommand command) + { + if (command == null) return; + + object? cur = Volatile.Read(ref _untypedHead); + while (true) + { + command.NextElement = (ProfiledCommand?)cur; + var got = Interlocked.CompareExchange(ref _untypedHead, command, cur); + if (ReferenceEquals(got, cur)) break; // successful update + cur = got; // retry; no need to re-fetch the field, we just did that + } + } + + /// + /// Reset the session and yield the commands that were captured for enumeration; if additional commands + /// are added, they can be retrieved via additional calls to FinishProfiling. + /// + public ProfiledCommandEnumerable FinishProfiling() + { + var head = (ProfiledCommand?)Interlocked.Exchange(ref _untypedHead, null); + + // reverse the list so everything is ordered the way the consumer expected them + int count = 0; + ProfiledCommand? previous = null, current = head, next; + while (current != null) + { + next = current.NextElement; + current.NextElement = previous; + previous = current; + current = next; + count++; + } + + return new ProfiledCommandEnumerable(count, previous); + } + } +} diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt new file mode 100644 index 000000000..0abb20043 --- /dev/null +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -0,0 +1,2054 @@ +#nullable enable +abstract StackExchange.Redis.RedisResult.IsNull.get -> bool +override StackExchange.Redis.ChannelMessage.Equals(object? obj) -> bool +override StackExchange.Redis.ChannelMessage.GetHashCode() -> int +override StackExchange.Redis.ChannelMessage.ToString() -> string! +override StackExchange.Redis.ChannelMessageQueue.ToString() -> string? +override StackExchange.Redis.ClientInfo.ToString() -> string! +override StackExchange.Redis.ClusterNode.Equals(object? obj) -> bool +override StackExchange.Redis.ClusterNode.GetHashCode() -> int +override StackExchange.Redis.ClusterNode.ToString() -> string! +override StackExchange.Redis.CommandMap.ToString() -> string! +override StackExchange.Redis.Configuration.AzureOptionsProvider.AbortOnConnectFail.get -> bool +override StackExchange.Redis.Configuration.AzureOptionsProvider.AfterConnectAsync(StackExchange.Redis.ConnectionMultiplexer! muxer, System.Action! log) -> System.Threading.Tasks.Task! +override StackExchange.Redis.Configuration.AzureOptionsProvider.DefaultVersion.get -> System.Version! +override StackExchange.Redis.Configuration.AzureOptionsProvider.GetDefaultSsl(StackExchange.Redis.EndPointCollection! endPoints) -> bool +override StackExchange.Redis.Configuration.AzureOptionsProvider.IsMatch(System.Net.EndPoint! endpoint) -> bool +override StackExchange.Redis.ConfigurationOptions.ToString() -> string! +override StackExchange.Redis.ConnectionCounters.ToString() -> string! +override StackExchange.Redis.ConnectionFailedEventArgs.ToString() -> string! +override StackExchange.Redis.ConnectionMultiplexer.ToString() -> string! +override StackExchange.Redis.GeoEntry.Equals(object? obj) -> bool +override StackExchange.Redis.GeoEntry.GetHashCode() -> int +override StackExchange.Redis.GeoEntry.ToString() -> string! +override StackExchange.Redis.GeoPosition.Equals(object? obj) -> bool +override StackExchange.Redis.GeoPosition.GetHashCode() -> int +override StackExchange.Redis.GeoPosition.ToString() -> string! +override StackExchange.Redis.GeoRadiusResult.ToString() -> string! +override StackExchange.Redis.HashEntry.Equals(object? obj) -> bool +override StackExchange.Redis.HashEntry.GetHashCode() -> int +override StackExchange.Redis.HashEntry.ToString() -> string! +override StackExchange.Redis.Maintenance.ServerMaintenanceEvent.ToString() -> string? +override StackExchange.Redis.NameValueEntry.Equals(object? obj) -> bool +override StackExchange.Redis.NameValueEntry.GetHashCode() -> int +override StackExchange.Redis.NameValueEntry.ToString() -> string! +override StackExchange.Redis.RedisChannel.Equals(object? obj) -> bool +override StackExchange.Redis.RedisChannel.GetHashCode() -> int +override StackExchange.Redis.RedisChannel.ToString() -> string! +override StackExchange.Redis.RedisConnectionException.GetObjectData(System.Runtime.Serialization.SerializationInfo! info, System.Runtime.Serialization.StreamingContext context) -> void +override StackExchange.Redis.RedisFeatures.Equals(object? obj) -> bool +override StackExchange.Redis.RedisFeatures.GetHashCode() -> int +override StackExchange.Redis.RedisFeatures.ToString() -> string! +override StackExchange.Redis.RedisKey.Equals(object? obj) -> bool +override StackExchange.Redis.RedisKey.GetHashCode() -> int +override StackExchange.Redis.RedisKey.ToString() -> string! +override StackExchange.Redis.RedisTimeoutException.GetObjectData(System.Runtime.Serialization.SerializationInfo! info, System.Runtime.Serialization.StreamingContext context) -> void +override StackExchange.Redis.RedisValue.Equals(object? obj) -> bool +override StackExchange.Redis.RedisValue.GetHashCode() -> int +override StackExchange.Redis.RedisValue.ToString() -> string! +override StackExchange.Redis.Role.ToString() -> string! +override StackExchange.Redis.ServerCounters.ToString() -> string! +override StackExchange.Redis.SlotRange.Equals(object? obj) -> bool +override StackExchange.Redis.SlotRange.GetHashCode() -> int +override StackExchange.Redis.SlotRange.ToString() -> string! +override StackExchange.Redis.SocketManager.ToString() -> string! +override StackExchange.Redis.SortedSetEntry.Equals(object? obj) -> bool +override StackExchange.Redis.SortedSetEntry.GetHashCode() -> int +override StackExchange.Redis.SortedSetEntry.ToString() -> string! +StackExchange.Redis.Aggregate +StackExchange.Redis.Aggregate.Max = 2 -> StackExchange.Redis.Aggregate +StackExchange.Redis.Aggregate.Min = 1 -> StackExchange.Redis.Aggregate +StackExchange.Redis.Aggregate.Sum = 0 -> StackExchange.Redis.Aggregate +StackExchange.Redis.BacklogPolicy +StackExchange.Redis.BacklogPolicy.AbortPendingOnConnectionFailure.get -> bool +StackExchange.Redis.BacklogPolicy.AbortPendingOnConnectionFailure.init -> void +StackExchange.Redis.BacklogPolicy.BacklogPolicy() -> void +StackExchange.Redis.BacklogPolicy.QueueWhileDisconnected.get -> bool +StackExchange.Redis.BacklogPolicy.QueueWhileDisconnected.init -> void +StackExchange.Redis.Bitwise +StackExchange.Redis.Bitwise.And = 0 -> StackExchange.Redis.Bitwise +StackExchange.Redis.Bitwise.Not = 3 -> StackExchange.Redis.Bitwise +StackExchange.Redis.Bitwise.Or = 1 -> StackExchange.Redis.Bitwise +StackExchange.Redis.Bitwise.Xor = 2 -> StackExchange.Redis.Bitwise +StackExchange.Redis.ChannelMessage +StackExchange.Redis.ChannelMessage.Channel.get -> StackExchange.Redis.RedisChannel +StackExchange.Redis.ChannelMessage.ChannelMessage() -> void +StackExchange.Redis.ChannelMessage.Message.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.ChannelMessage.SubscriptionChannel.get -> StackExchange.Redis.RedisChannel +StackExchange.Redis.ChannelMessageQueue +StackExchange.Redis.ChannelMessageQueue.Channel.get -> StackExchange.Redis.RedisChannel +StackExchange.Redis.ChannelMessageQueue.Completion.get -> System.Threading.Tasks.Task! +StackExchange.Redis.ChannelMessageQueue.GetAsyncEnumerator(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Generic.IAsyncEnumerator! +StackExchange.Redis.ChannelMessageQueue.OnMessage(System.Action! handler) -> void +StackExchange.Redis.ChannelMessageQueue.OnMessage(System.Func! handler) -> void +StackExchange.Redis.ChannelMessageQueue.ReadAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +StackExchange.Redis.ChannelMessageQueue.TryGetCount(out int count) -> bool +StackExchange.Redis.ChannelMessageQueue.TryRead(out StackExchange.Redis.ChannelMessage item) -> bool +StackExchange.Redis.ChannelMessageQueue.Unsubscribe(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.ChannelMessageQueue.UnsubscribeAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.Blocked = 16 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.BroadcastTracking = 16384 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.CloseASAP = 256 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.Closing = 64 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.KeysTracking = 4096 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.Master = 4 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.None = 0 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.PubSubSubscriber = 512 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.ReadOnlyCluster = 1024 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.Replica = 2 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.ReplicaMonitor = 1 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.Slave = 2 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.SlaveMonitor = 1 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.TrackingTargetInvalid = 8192 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.Transaction = 8 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.TransactionDoomed = 32 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.Unblocked = 128 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientFlags.UnixDomainSocket = 2048 -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientInfo +StackExchange.Redis.ClientInfo.Address.get -> System.Net.EndPoint? +StackExchange.Redis.ClientInfo.AgeSeconds.get -> int +StackExchange.Redis.ClientInfo.ClientInfo() -> void +StackExchange.Redis.ClientInfo.ClientType.get -> StackExchange.Redis.ClientType +StackExchange.Redis.ClientInfo.Database.get -> int +StackExchange.Redis.ClientInfo.Flags.get -> StackExchange.Redis.ClientFlags +StackExchange.Redis.ClientInfo.FlagsRaw.get -> string? +StackExchange.Redis.ClientInfo.Host.get -> string? +StackExchange.Redis.ClientInfo.Id.get -> long +StackExchange.Redis.ClientInfo.IdleSeconds.get -> int +StackExchange.Redis.ClientInfo.LastCommand.get -> string? +StackExchange.Redis.ClientInfo.LibraryName.get -> string? +StackExchange.Redis.ClientInfo.LibraryVersion.get -> string? +StackExchange.Redis.ClientInfo.Name.get -> string? +StackExchange.Redis.ClientInfo.PatternSubscriptionCount.get -> int +StackExchange.Redis.ClientInfo.Port.get -> int +StackExchange.Redis.ClientInfo.ProtocolVersion.get -> string? +StackExchange.Redis.ClientInfo.Raw.get -> string? +StackExchange.Redis.ClientInfo.SubscriptionCount.get -> int +StackExchange.Redis.ClientInfo.TransactionCommandLength.get -> int +StackExchange.Redis.ClientKillFilter +StackExchange.Redis.ClientKillFilter.ClientKillFilter() -> void +StackExchange.Redis.ClientKillFilter.ClientType.get -> StackExchange.Redis.ClientType? +StackExchange.Redis.ClientKillFilter.Endpoint.get -> System.Net.EndPoint? +StackExchange.Redis.ClientKillFilter.Id.get -> long? +StackExchange.Redis.ClientKillFilter.MaxAgeInSeconds.get -> long? +StackExchange.Redis.ClientKillFilter.ServerEndpoint.get -> System.Net.EndPoint? +StackExchange.Redis.ClientKillFilter.SkipMe.get -> bool? +StackExchange.Redis.ClientKillFilter.Username.get -> string? +StackExchange.Redis.ClientKillFilter.WithClientType(StackExchange.Redis.ClientType? clientType) -> StackExchange.Redis.ClientKillFilter! +StackExchange.Redis.ClientKillFilter.WithEndpoint(System.Net.EndPoint? endpoint) -> StackExchange.Redis.ClientKillFilter! +StackExchange.Redis.ClientKillFilter.WithId(long? id) -> StackExchange.Redis.ClientKillFilter! +StackExchange.Redis.ClientKillFilter.WithMaxAgeInSeconds(long? maxAgeInSeconds) -> StackExchange.Redis.ClientKillFilter! +StackExchange.Redis.ClientKillFilter.WithServerEndpoint(System.Net.EndPoint? serverEndpoint) -> StackExchange.Redis.ClientKillFilter! +StackExchange.Redis.ClientKillFilter.WithSkipMe(bool? skipMe) -> StackExchange.Redis.ClientKillFilter! +StackExchange.Redis.ClientKillFilter.WithUsername(string? username) -> StackExchange.Redis.ClientKillFilter! +StackExchange.Redis.ClientType +StackExchange.Redis.ClientType.Normal = 0 -> StackExchange.Redis.ClientType +StackExchange.Redis.ClientType.PubSub = 2 -> StackExchange.Redis.ClientType +StackExchange.Redis.ClientType.Replica = 1 -> StackExchange.Redis.ClientType +StackExchange.Redis.ClientType.Slave = 1 -> StackExchange.Redis.ClientType +StackExchange.Redis.ClusterConfiguration +StackExchange.Redis.ClusterConfiguration.GetBySlot(int slot) -> StackExchange.Redis.ClusterNode? +StackExchange.Redis.ClusterConfiguration.GetBySlot(StackExchange.Redis.RedisKey key) -> StackExchange.Redis.ClusterNode? +StackExchange.Redis.ClusterConfiguration.Nodes.get -> System.Collections.Generic.ICollection! +StackExchange.Redis.ClusterConfiguration.Origin.get -> System.Net.EndPoint! +StackExchange.Redis.ClusterConfiguration.this[System.Net.EndPoint! endpoint].get -> StackExchange.Redis.ClusterNode? +StackExchange.Redis.ClusterNode +StackExchange.Redis.ClusterNode.Children.get -> System.Collections.Generic.IList! +StackExchange.Redis.ClusterNode.CompareTo(StackExchange.Redis.ClusterNode? other) -> int +StackExchange.Redis.ClusterNode.EndPoint.get -> System.Net.EndPoint? +StackExchange.Redis.ClusterNode.Equals(StackExchange.Redis.ClusterNode? other) -> bool +StackExchange.Redis.ClusterNode.IsConnected.get -> bool +StackExchange.Redis.ClusterNode.IsFail.get -> bool +StackExchange.Redis.ClusterNode.IsMyself.get -> bool +StackExchange.Redis.ClusterNode.IsNoAddr.get -> bool +StackExchange.Redis.ClusterNode.IsPossiblyFail.get -> bool +StackExchange.Redis.ClusterNode.IsReplica.get -> bool +StackExchange.Redis.ClusterNode.IsSlave.get -> bool +StackExchange.Redis.ClusterNode.NodeId.get -> string! +StackExchange.Redis.ClusterNode.Parent.get -> StackExchange.Redis.ClusterNode? +StackExchange.Redis.ClusterNode.ParentNodeId.get -> string? +StackExchange.Redis.ClusterNode.Raw.get -> string! +StackExchange.Redis.ClusterNode.Slots.get -> System.Collections.Generic.IList! +StackExchange.Redis.CommandFlags +StackExchange.Redis.CommandFlags.DemandMaster = 4 -> StackExchange.Redis.CommandFlags +StackExchange.Redis.CommandFlags.DemandReplica = StackExchange.Redis.CommandFlags.DemandMaster | StackExchange.Redis.CommandFlags.PreferReplica -> StackExchange.Redis.CommandFlags +StackExchange.Redis.CommandFlags.DemandSlave = StackExchange.Redis.CommandFlags.DemandMaster | StackExchange.Redis.CommandFlags.PreferReplica -> StackExchange.Redis.CommandFlags +StackExchange.Redis.CommandFlags.FireAndForget = 2 -> StackExchange.Redis.CommandFlags +StackExchange.Redis.CommandFlags.HighPriority = 1 -> StackExchange.Redis.CommandFlags +StackExchange.Redis.CommandFlags.None = 0 -> StackExchange.Redis.CommandFlags +StackExchange.Redis.CommandFlags.NoRedirect = 64 -> StackExchange.Redis.CommandFlags +StackExchange.Redis.CommandFlags.NoScriptCache = 512 -> StackExchange.Redis.CommandFlags +StackExchange.Redis.CommandFlags.PreferMaster = 0 -> StackExchange.Redis.CommandFlags +StackExchange.Redis.CommandFlags.PreferReplica = 8 -> StackExchange.Redis.CommandFlags +StackExchange.Redis.CommandFlags.PreferSlave = 8 -> StackExchange.Redis.CommandFlags +StackExchange.Redis.CommandMap +StackExchange.Redis.CommandStatus +StackExchange.Redis.CommandStatus.Sent = 2 -> StackExchange.Redis.CommandStatus +StackExchange.Redis.CommandStatus.Unknown = 0 -> StackExchange.Redis.CommandStatus +StackExchange.Redis.CommandStatus.WaitingToBeSent = 1 -> StackExchange.Redis.CommandStatus +StackExchange.Redis.CommandStatus.WaitingInBacklog = 3 -> StackExchange.Redis.CommandStatus +StackExchange.Redis.CommandTrace +StackExchange.Redis.CommandTrace.Arguments.get -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.CommandTrace.Duration.get -> System.TimeSpan +StackExchange.Redis.CommandTrace.GetHelpUrl() -> string? +StackExchange.Redis.CommandTrace.Time.get -> System.DateTime +StackExchange.Redis.CommandTrace.UniqueId.get -> long +StackExchange.Redis.Condition +StackExchange.Redis.ConditionResult +StackExchange.Redis.ConditionResult.WasSatisfied.get -> bool +StackExchange.Redis.Configuration.AzureOptionsProvider +StackExchange.Redis.Configuration.AzureOptionsProvider.AzureOptionsProvider() -> void +StackExchange.Redis.Configuration.DefaultOptionsProvider +StackExchange.Redis.Configuration.DefaultOptionsProvider.ClientName.get -> string! +StackExchange.Redis.Configuration.DefaultOptionsProvider.DefaultOptionsProvider() -> void +StackExchange.Redis.Configuration.Tunnel +StackExchange.Redis.Configuration.Tunnel.Tunnel() -> void +static StackExchange.Redis.Configuration.Tunnel.HttpProxy(System.Net.EndPoint! proxy) -> StackExchange.Redis.Configuration.Tunnel! +virtual StackExchange.Redis.Configuration.Tunnel.BeforeAuthenticateAsync(System.Net.EndPoint! endpoint, StackExchange.Redis.ConnectionType connectionType, System.Net.Sockets.Socket? socket, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +virtual StackExchange.Redis.Configuration.Tunnel.BeforeSocketConnectAsync(System.Net.EndPoint! endPoint, StackExchange.Redis.ConnectionType connectionType, System.Net.Sockets.Socket? socket, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +virtual StackExchange.Redis.Configuration.Tunnel.GetSocketConnectEndpointAsync(System.Net.EndPoint! endpoint, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +StackExchange.Redis.ConfigurationOptions +StackExchange.Redis.ConfigurationOptions.AbortOnConnectFail.get -> bool +StackExchange.Redis.ConfigurationOptions.AbortOnConnectFail.set -> void +StackExchange.Redis.ConfigurationOptions.AllowAdmin.get -> bool +StackExchange.Redis.ConfigurationOptions.AllowAdmin.set -> void +StackExchange.Redis.ConfigurationOptions.Apply(System.Action! configure) -> StackExchange.Redis.ConfigurationOptions! +StackExchange.Redis.ConfigurationOptions.AsyncTimeout.get -> int +StackExchange.Redis.ConfigurationOptions.AsyncTimeout.set -> void +StackExchange.Redis.ConfigurationOptions.BacklogPolicy.get -> StackExchange.Redis.BacklogPolicy! +StackExchange.Redis.ConfigurationOptions.BacklogPolicy.set -> void +StackExchange.Redis.ConfigurationOptions.BeforeSocketConnect.get -> System.Action? +StackExchange.Redis.ConfigurationOptions.BeforeSocketConnect.set -> void +StackExchange.Redis.ConfigurationOptions.CertificateSelection -> System.Net.Security.LocalCertificateSelectionCallback? +StackExchange.Redis.ConfigurationOptions.CertificateValidation -> System.Net.Security.RemoteCertificateValidationCallback? +StackExchange.Redis.ConfigurationOptions.ChannelPrefix.get -> StackExchange.Redis.RedisChannel +StackExchange.Redis.ConfigurationOptions.ChannelPrefix.set -> void +StackExchange.Redis.ConfigurationOptions.CheckCertificateRevocation.get -> bool +StackExchange.Redis.ConfigurationOptions.CheckCertificateRevocation.set -> void +StackExchange.Redis.ConfigurationOptions.ClientName.get -> string? +StackExchange.Redis.ConfigurationOptions.ClientName.set -> void +StackExchange.Redis.ConfigurationOptions.Clone() -> StackExchange.Redis.ConfigurationOptions! +StackExchange.Redis.ConfigurationOptions.CommandMap.get -> StackExchange.Redis.CommandMap! +StackExchange.Redis.ConfigurationOptions.CommandMap.set -> void +StackExchange.Redis.ConfigurationOptions.ConfigCheckSeconds.get -> int +StackExchange.Redis.ConfigurationOptions.ConfigCheckSeconds.set -> void +StackExchange.Redis.ConfigurationOptions.ConfigurationChannel.get -> string! +StackExchange.Redis.ConfigurationOptions.ConfigurationChannel.set -> void +StackExchange.Redis.ConfigurationOptions.ConfigurationOptions() -> void +StackExchange.Redis.ConfigurationOptions.ConnectRetry.get -> int +StackExchange.Redis.ConfigurationOptions.ConnectRetry.set -> void +StackExchange.Redis.ConfigurationOptions.ConnectTimeout.get -> int +StackExchange.Redis.ConfigurationOptions.ConnectTimeout.set -> void +StackExchange.Redis.ConfigurationOptions.DefaultDatabase.get -> int? +StackExchange.Redis.ConfigurationOptions.DefaultDatabase.set -> void +StackExchange.Redis.ConfigurationOptions.Defaults.get -> StackExchange.Redis.Configuration.DefaultOptionsProvider! +StackExchange.Redis.ConfigurationOptions.Defaults.set -> void +StackExchange.Redis.ConfigurationOptions.DefaultVersion.get -> System.Version! +StackExchange.Redis.ConfigurationOptions.DefaultVersion.set -> void +StackExchange.Redis.ConfigurationOptions.EndPoints.get -> StackExchange.Redis.EndPointCollection! +StackExchange.Redis.ConfigurationOptions.EndPoints.init -> void +StackExchange.Redis.ConfigurationOptions.HeartbeatConsistencyChecks.get -> bool +StackExchange.Redis.ConfigurationOptions.HeartbeatConsistencyChecks.set -> void +StackExchange.Redis.ConfigurationOptions.HeartbeatInterval.get -> System.TimeSpan +StackExchange.Redis.ConfigurationOptions.HeartbeatInterval.set -> void +StackExchange.Redis.ConfigurationOptions.HighIntegrity.get -> bool +StackExchange.Redis.ConfigurationOptions.HighIntegrity.set -> void +StackExchange.Redis.ConfigurationOptions.HighPrioritySocketThreads.get -> bool +StackExchange.Redis.ConfigurationOptions.HighPrioritySocketThreads.set -> void +StackExchange.Redis.ConfigurationOptions.IncludeDetailInExceptions.get -> bool +StackExchange.Redis.ConfigurationOptions.IncludeDetailInExceptions.set -> void +StackExchange.Redis.ConfigurationOptions.IncludePerformanceCountersInExceptions.get -> bool +StackExchange.Redis.ConfigurationOptions.IncludePerformanceCountersInExceptions.set -> void +StackExchange.Redis.ConfigurationOptions.KeepAlive.get -> int +StackExchange.Redis.ConfigurationOptions.KeepAlive.set -> void +StackExchange.Redis.ConfigurationOptions.LibraryName.get -> string? +StackExchange.Redis.ConfigurationOptions.LibraryName.set -> void +StackExchange.Redis.ConfigurationOptions.LoggerFactory.get -> Microsoft.Extensions.Logging.ILoggerFactory? +StackExchange.Redis.ConfigurationOptions.LoggerFactory.set -> void +StackExchange.Redis.ConfigurationOptions.Password.get -> string? +StackExchange.Redis.ConfigurationOptions.Password.set -> void +StackExchange.Redis.ConfigurationOptions.PreserveAsyncOrder.get -> bool +StackExchange.Redis.ConfigurationOptions.PreserveAsyncOrder.set -> void +StackExchange.Redis.ConfigurationOptions.Proxy.get -> StackExchange.Redis.Proxy +StackExchange.Redis.ConfigurationOptions.Proxy.set -> void +StackExchange.Redis.ConfigurationOptions.ReconnectRetryPolicy.get -> StackExchange.Redis.IReconnectRetryPolicy! +StackExchange.Redis.ConfigurationOptions.ReconnectRetryPolicy.set -> void +StackExchange.Redis.ConfigurationOptions.ResolveDns.get -> bool +StackExchange.Redis.ConfigurationOptions.ResolveDns.set -> void +StackExchange.Redis.ConfigurationOptions.ResponseTimeout.get -> int +StackExchange.Redis.ConfigurationOptions.ResponseTimeout.set -> void +StackExchange.Redis.ConfigurationOptions.ServiceName.get -> string? +StackExchange.Redis.ConfigurationOptions.ServiceName.set -> void +StackExchange.Redis.ConfigurationOptions.SetClientLibrary.get -> bool +StackExchange.Redis.ConfigurationOptions.SetClientLibrary.set -> void +StackExchange.Redis.ConfigurationOptions.SetDefaultPorts() -> void +StackExchange.Redis.ConfigurationOptions.SocketManager.get -> StackExchange.Redis.SocketManager? +StackExchange.Redis.ConfigurationOptions.SocketManager.set -> void +StackExchange.Redis.ConfigurationOptions.Ssl.get -> bool +StackExchange.Redis.ConfigurationOptions.Ssl.set -> void +StackExchange.Redis.ConfigurationOptions.SslHost.get -> string? +StackExchange.Redis.ConfigurationOptions.SslHost.set -> void +StackExchange.Redis.ConfigurationOptions.SslProtocols.get -> System.Security.Authentication.SslProtocols? +StackExchange.Redis.ConfigurationOptions.SslProtocols.set -> void +StackExchange.Redis.ConfigurationOptions.SyncTimeout.get -> int +StackExchange.Redis.ConfigurationOptions.SyncTimeout.set -> void +StackExchange.Redis.ConfigurationOptions.TieBreaker.get -> string! +StackExchange.Redis.ConfigurationOptions.TieBreaker.set -> void +StackExchange.Redis.ConfigurationOptions.ToString(bool includePassword) -> string! +StackExchange.Redis.ConfigurationOptions.TrustIssuer(string! issuerCertificatePath) -> void +StackExchange.Redis.ConfigurationOptions.TrustIssuer(System.Security.Cryptography.X509Certificates.X509Certificate2! issuer) -> void +StackExchange.Redis.ConfigurationOptions.Tunnel.get -> StackExchange.Redis.Configuration.Tunnel? +StackExchange.Redis.ConfigurationOptions.Tunnel.set -> void +StackExchange.Redis.ConfigurationOptions.User.get -> string? +StackExchange.Redis.ConfigurationOptions.User.set -> void +StackExchange.Redis.ConfigurationOptions.UseSsl.get -> bool +StackExchange.Redis.ConfigurationOptions.UseSsl.set -> void +StackExchange.Redis.ConfigurationOptions.WriteBuffer.get -> int +StackExchange.Redis.ConfigurationOptions.WriteBuffer.set -> void +StackExchange.Redis.ConnectionCounters +StackExchange.Redis.ConnectionCounters.CompletedAsynchronously.get -> long +StackExchange.Redis.ConnectionCounters.CompletedSynchronously.get -> long +StackExchange.Redis.ConnectionCounters.ConnectionType.get -> StackExchange.Redis.ConnectionType +StackExchange.Redis.ConnectionCounters.FailedAsynchronously.get -> long +StackExchange.Redis.ConnectionCounters.IsEmpty.get -> bool +StackExchange.Redis.ConnectionCounters.NonPreferredEndpointCount.get -> long +StackExchange.Redis.ConnectionCounters.OperationCount.get -> long +StackExchange.Redis.ConnectionCounters.PendingUnsentItems.get -> int +StackExchange.Redis.ConnectionCounters.ResponsesAwaitingAsyncCompletion.get -> int +StackExchange.Redis.ConnectionCounters.SentItemsAwaitingResponse.get -> int +StackExchange.Redis.ConnectionCounters.SocketCount.get -> long +StackExchange.Redis.ConnectionCounters.Subscriptions.get -> long +StackExchange.Redis.ConnectionCounters.TotalOutstanding.get -> int +StackExchange.Redis.ConnectionCounters.WriterCount.get -> int +StackExchange.Redis.ConnectionFailedEventArgs +StackExchange.Redis.ConnectionFailedEventArgs.ConnectionFailedEventArgs(object! sender, System.Net.EndPoint! endPoint, StackExchange.Redis.ConnectionType connectionType, StackExchange.Redis.ConnectionFailureType failureType, System.Exception! exception, string! physicalName) -> void +StackExchange.Redis.ConnectionFailedEventArgs.ConnectionType.get -> StackExchange.Redis.ConnectionType +StackExchange.Redis.ConnectionFailedEventArgs.EndPoint.get -> System.Net.EndPoint? +StackExchange.Redis.ConnectionFailedEventArgs.Exception.get -> System.Exception? +StackExchange.Redis.ConnectionFailedEventArgs.FailureType.get -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionFailureType.AuthenticationFailure = 3 -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionFailureType.ConnectionDisposed = 7 -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionFailureType.InternalFailure = 5 -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionFailureType.Loading = 8 -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionFailureType.None = 0 -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionFailureType.ProtocolFailure = 4 -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionFailureType.SocketClosed = 6 -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionFailureType.SocketFailure = 2 -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionFailureType.UnableToConnect = 9 -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionFailureType.ResponseIntegrityFailure = 10 -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionFailureType.UnableToResolvePhysicalConnection = 1 -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.ConnectionMultiplexer +StackExchange.Redis.ConnectionMultiplexer.ClientName.get -> string! +StackExchange.Redis.ConnectionMultiplexer.Close(bool allowCommandsToComplete = true) -> void +StackExchange.Redis.ConnectionMultiplexer.CloseAsync(bool allowCommandsToComplete = true) -> System.Threading.Tasks.Task! +StackExchange.Redis.ConnectionMultiplexer.Configuration.get -> string! +StackExchange.Redis.ConnectionMultiplexer.ConfigurationChanged -> System.EventHandler? +StackExchange.Redis.ConnectionMultiplexer.ConfigurationChangedBroadcast -> System.EventHandler? +StackExchange.Redis.ConnectionMultiplexer.Configure(System.IO.TextWriter? log = null) -> bool +StackExchange.Redis.ConnectionMultiplexer.ConfigureAsync(System.IO.TextWriter? log = null) -> System.Threading.Tasks.Task! +StackExchange.Redis.ConnectionMultiplexer.ConnectionFailed -> System.EventHandler? +StackExchange.Redis.ConnectionMultiplexer.ConnectionRestored -> System.EventHandler? +StackExchange.Redis.ConnectionMultiplexer.Dispose() -> void +StackExchange.Redis.ConnectionMultiplexer.DisposeAsync() -> System.Threading.Tasks.ValueTask +StackExchange.Redis.ConnectionMultiplexer.ErrorMessage -> System.EventHandler? +StackExchange.Redis.ConnectionMultiplexer.ExportConfiguration(System.IO.Stream! destination, StackExchange.Redis.ExportOptions options = (StackExchange.Redis.ExportOptions)-1) -> void +StackExchange.Redis.ConnectionMultiplexer.GetCounters() -> StackExchange.Redis.ServerCounters! +StackExchange.Redis.ConnectionMultiplexer.GetDatabase(int db = -1, object? asyncState = null) -> StackExchange.Redis.IDatabase! +StackExchange.Redis.ConnectionMultiplexer.GetEndPoints(bool configuredOnly = false) -> System.Net.EndPoint![]! +StackExchange.Redis.ConnectionMultiplexer.GetHashSlot(StackExchange.Redis.RedisKey key) -> int +StackExchange.Redis.ConnectionMultiplexer.GetSentinelMasterConnection(StackExchange.Redis.ConfigurationOptions! config, System.IO.TextWriter? log = null) -> StackExchange.Redis.ConnectionMultiplexer! +StackExchange.Redis.ConnectionMultiplexer.GetServer(string! host, int port, object? asyncState = null) -> StackExchange.Redis.IServer! +StackExchange.Redis.ConnectionMultiplexer.GetServer(string! hostAndPort, object? asyncState = null) -> StackExchange.Redis.IServer! +StackExchange.Redis.ConnectionMultiplexer.GetServer(System.Net.EndPoint? endpoint, object? asyncState = null) -> StackExchange.Redis.IServer! +StackExchange.Redis.ConnectionMultiplexer.GetServer(System.Net.IPAddress! host, int port) -> StackExchange.Redis.IServer! +StackExchange.Redis.ConnectionMultiplexer.GetServers() -> StackExchange.Redis.IServer![]! +StackExchange.Redis.ConnectionMultiplexer.GetStatus() -> string! +StackExchange.Redis.ConnectionMultiplexer.GetStatus(System.IO.TextWriter! log) -> void +StackExchange.Redis.ConnectionMultiplexer.GetStormLog() -> string? +StackExchange.Redis.ConnectionMultiplexer.GetSubscriber(object? asyncState = null) -> StackExchange.Redis.ISubscriber! +StackExchange.Redis.ConnectionMultiplexer.HashSlot(StackExchange.Redis.RedisKey key) -> int +StackExchange.Redis.ConnectionMultiplexer.HashSlotMoved -> System.EventHandler? +StackExchange.Redis.ConnectionMultiplexer.IncludeDetailInExceptions.get -> bool +StackExchange.Redis.ConnectionMultiplexer.IncludeDetailInExceptions.set -> void +StackExchange.Redis.ConnectionMultiplexer.IncludePerformanceCountersInExceptions.get -> bool +StackExchange.Redis.ConnectionMultiplexer.IncludePerformanceCountersInExceptions.set -> void +StackExchange.Redis.ConnectionMultiplexer.InternalError -> System.EventHandler? +StackExchange.Redis.ConnectionMultiplexer.IsConnected.get -> bool +StackExchange.Redis.ConnectionMultiplexer.IsConnecting.get -> bool +StackExchange.Redis.ConnectionMultiplexer.OperationCount.get -> long +StackExchange.Redis.ConnectionMultiplexer.PreserveAsyncOrder.get -> bool +StackExchange.Redis.ConnectionMultiplexer.PreserveAsyncOrder.set -> void +StackExchange.Redis.ConnectionMultiplexer.PublishReconfigure(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.ConnectionMultiplexer.PublishReconfigureAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.ConnectionMultiplexer.ReconfigureAsync(string! reason) -> System.Threading.Tasks.Task! +StackExchange.Redis.ConnectionMultiplexer.RegisterProfiler(System.Func! profilingSessionProvider) -> void +StackExchange.Redis.ConnectionMultiplexer.ResetStormLog() -> void +StackExchange.Redis.ConnectionMultiplexer.ServerMaintenanceEvent -> System.EventHandler? +StackExchange.Redis.ConnectionMultiplexer.StormLogThreshold.get -> int +StackExchange.Redis.ConnectionMultiplexer.StormLogThreshold.set -> void +StackExchange.Redis.ConnectionMultiplexer.TimeoutMilliseconds.get -> int +StackExchange.Redis.ConnectionMultiplexer.Wait(System.Threading.Tasks.Task! task) -> void +StackExchange.Redis.ConnectionMultiplexer.Wait(System.Threading.Tasks.Task! task) -> T +StackExchange.Redis.ConnectionMultiplexer.WaitAll(params System.Threading.Tasks.Task![]! tasks) -> void +StackExchange.Redis.ConnectionType +StackExchange.Redis.ConnectionType.Interactive = 1 -> StackExchange.Redis.ConnectionType +StackExchange.Redis.ConnectionType.None = 0 -> StackExchange.Redis.ConnectionType +StackExchange.Redis.ConnectionType.Subscription = 2 -> StackExchange.Redis.ConnectionType +StackExchange.Redis.EndPointCollection +StackExchange.Redis.EndPointCollection.Add(string! host, int port) -> void +StackExchange.Redis.EndPointCollection.Add(string! hostAndPort) -> void +StackExchange.Redis.EndPointCollection.Add(System.Net.IPAddress! host, int port) -> void +StackExchange.Redis.EndPointCollection.EndPointCollection() -> void +StackExchange.Redis.EndPointCollection.EndPointCollection(System.Collections.Generic.IList! endpoints) -> void +StackExchange.Redis.EndPointCollection.GetEnumerator() -> System.Collections.Generic.IEnumerator! +StackExchange.Redis.EndPointCollection.TryAdd(System.Net.EndPoint! endpoint) -> bool +StackExchange.Redis.EndPointEventArgs +StackExchange.Redis.EndPointEventArgs.EndPoint.get -> System.Net.EndPoint! +StackExchange.Redis.EndPointEventArgs.EndPointEventArgs(object! sender, System.Net.EndPoint! endpoint) -> void +StackExchange.Redis.Exclude +StackExchange.Redis.Exclude.Both = StackExchange.Redis.Exclude.Start | StackExchange.Redis.Exclude.Stop -> StackExchange.Redis.Exclude +StackExchange.Redis.Exclude.None = 0 -> StackExchange.Redis.Exclude +StackExchange.Redis.Exclude.Start = 1 -> StackExchange.Redis.Exclude +StackExchange.Redis.Exclude.Stop = 2 -> StackExchange.Redis.Exclude +StackExchange.Redis.ExpireResult +StackExchange.Redis.ExpireResult.ConditionNotMet = 0 -> StackExchange.Redis.ExpireResult +StackExchange.Redis.ExpireResult.Due = 2 -> StackExchange.Redis.ExpireResult +StackExchange.Redis.ExpireResult.NoSuchField = -2 -> StackExchange.Redis.ExpireResult +StackExchange.Redis.ExpireResult.Success = 1 -> StackExchange.Redis.ExpireResult +StackExchange.Redis.ExpireWhen +StackExchange.Redis.ExpireWhen.Always = 0 -> StackExchange.Redis.ExpireWhen +StackExchange.Redis.ExpireWhen.GreaterThanCurrentExpiry = 1 -> StackExchange.Redis.ExpireWhen +StackExchange.Redis.ExpireWhen.HasExpiry = 2 -> StackExchange.Redis.ExpireWhen +StackExchange.Redis.ExpireWhen.HasNoExpiry = 3 -> StackExchange.Redis.ExpireWhen +StackExchange.Redis.ExpireWhen.LessThanCurrentExpiry = 4 -> StackExchange.Redis.ExpireWhen +StackExchange.Redis.ExponentialRetry +StackExchange.Redis.ExponentialRetry.ExponentialRetry(int deltaBackOffMilliseconds) -> void +StackExchange.Redis.ExponentialRetry.ExponentialRetry(int deltaBackOffMilliseconds, int maxDeltaBackOffMilliseconds) -> void +StackExchange.Redis.ExponentialRetry.ShouldRetry(long currentRetryCount, int timeElapsedMillisecondsSinceLastRetry) -> bool +StackExchange.Redis.ExportOptions +StackExchange.Redis.ExportOptions.All = -1 -> StackExchange.Redis.ExportOptions +StackExchange.Redis.ExportOptions.Client = 4 -> StackExchange.Redis.ExportOptions +StackExchange.Redis.ExportOptions.Cluster = 8 -> StackExchange.Redis.ExportOptions +StackExchange.Redis.ExportOptions.Config = 2 -> StackExchange.Redis.ExportOptions +StackExchange.Redis.ExportOptions.Info = 1 -> StackExchange.Redis.ExportOptions +StackExchange.Redis.ExportOptions.None = 0 -> StackExchange.Redis.ExportOptions +StackExchange.Redis.ExtensionMethods +StackExchange.Redis.GeoEntry +StackExchange.Redis.GeoEntry.Equals(StackExchange.Redis.GeoEntry other) -> bool +StackExchange.Redis.GeoEntry.GeoEntry() -> void +StackExchange.Redis.GeoEntry.GeoEntry(double longitude, double latitude, StackExchange.Redis.RedisValue member) -> void +StackExchange.Redis.GeoEntry.Latitude.get -> double +StackExchange.Redis.GeoEntry.Longitude.get -> double +StackExchange.Redis.GeoEntry.Member.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.GeoEntry.Position.get -> StackExchange.Redis.GeoPosition +StackExchange.Redis.GeoPosition +StackExchange.Redis.GeoPosition.Equals(StackExchange.Redis.GeoPosition other) -> bool +StackExchange.Redis.GeoPosition.GeoPosition() -> void +StackExchange.Redis.GeoPosition.GeoPosition(double longitude, double latitude) -> void +StackExchange.Redis.GeoPosition.Latitude.get -> double +StackExchange.Redis.GeoPosition.Longitude.get -> double +StackExchange.Redis.GeoRadiusOptions +StackExchange.Redis.GeoRadiusOptions.Default = StackExchange.Redis.GeoRadiusOptions.WithCoordinates | StackExchange.Redis.GeoRadiusOptions.WithDistance -> StackExchange.Redis.GeoRadiusOptions +StackExchange.Redis.GeoRadiusOptions.None = 0 -> StackExchange.Redis.GeoRadiusOptions +StackExchange.Redis.GeoRadiusOptions.WithCoordinates = 1 -> StackExchange.Redis.GeoRadiusOptions +StackExchange.Redis.GeoRadiusOptions.WithDistance = 2 -> StackExchange.Redis.GeoRadiusOptions +StackExchange.Redis.GeoRadiusOptions.WithGeoHash = 4 -> StackExchange.Redis.GeoRadiusOptions +StackExchange.Redis.GeoRadiusResult +StackExchange.Redis.GeoRadiusResult.Distance.get -> double? +StackExchange.Redis.GeoRadiusResult.GeoRadiusResult() -> void +StackExchange.Redis.GeoRadiusResult.GeoRadiusResult(in StackExchange.Redis.RedisValue member, double? distance, long? hash, StackExchange.Redis.GeoPosition? position) -> void +StackExchange.Redis.GeoRadiusResult.Hash.get -> long? +StackExchange.Redis.GeoRadiusResult.Member.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.GeoRadiusResult.Position.get -> StackExchange.Redis.GeoPosition? +StackExchange.Redis.GeoSearchBox +StackExchange.Redis.GeoSearchBox.GeoSearchBox(double height, double width, StackExchange.Redis.GeoUnit unit = StackExchange.Redis.GeoUnit.Meters) -> void +StackExchange.Redis.GeoSearchCircle +StackExchange.Redis.GeoSearchCircle.GeoSearchCircle(double radius, StackExchange.Redis.GeoUnit unit = StackExchange.Redis.GeoUnit.Meters) -> void +StackExchange.Redis.GeoSearchShape +StackExchange.Redis.GeoSearchShape.GeoSearchShape(StackExchange.Redis.GeoUnit unit) -> void +StackExchange.Redis.GeoSearchShape.Unit.get -> StackExchange.Redis.GeoUnit +StackExchange.Redis.GeoUnit +StackExchange.Redis.GeoUnit.Feet = 3 -> StackExchange.Redis.GeoUnit +StackExchange.Redis.GeoUnit.Kilometers = 1 -> StackExchange.Redis.GeoUnit +StackExchange.Redis.GeoUnit.Meters = 0 -> StackExchange.Redis.GeoUnit +StackExchange.Redis.GeoUnit.Miles = 2 -> StackExchange.Redis.GeoUnit +StackExchange.Redis.HashEntry +StackExchange.Redis.HashEntry.Equals(StackExchange.Redis.HashEntry other) -> bool +StackExchange.Redis.HashEntry.HashEntry() -> void +StackExchange.Redis.HashEntry.HashEntry(StackExchange.Redis.RedisValue name, StackExchange.Redis.RedisValue value) -> void +StackExchange.Redis.HashEntry.Key.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.HashEntry.Name.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.HashEntry.Value.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.HashSlotMovedEventArgs +StackExchange.Redis.HashSlotMovedEventArgs.HashSlot.get -> int +StackExchange.Redis.HashSlotMovedEventArgs.HashSlotMovedEventArgs(object! sender, int hashSlot, System.Net.EndPoint! old, System.Net.EndPoint! new) -> void +StackExchange.Redis.HashSlotMovedEventArgs.NewEndPoint.get -> System.Net.EndPoint! +StackExchange.Redis.HashSlotMovedEventArgs.OldEndPoint.get -> System.Net.EndPoint? +StackExchange.Redis.IBatch +StackExchange.Redis.IBatch.Execute() -> void +StackExchange.Redis.IConnectionMultiplexer +StackExchange.Redis.IConnectionMultiplexer.ClientName.get -> string! +StackExchange.Redis.IConnectionMultiplexer.Close(bool allowCommandsToComplete = true) -> void +StackExchange.Redis.IConnectionMultiplexer.CloseAsync(bool allowCommandsToComplete = true) -> System.Threading.Tasks.Task! +StackExchange.Redis.IConnectionMultiplexer.Configuration.get -> string! +StackExchange.Redis.IConnectionMultiplexer.ConfigurationChanged -> System.EventHandler! +StackExchange.Redis.IConnectionMultiplexer.ConfigurationChangedBroadcast -> System.EventHandler! +StackExchange.Redis.IConnectionMultiplexer.Configure(System.IO.TextWriter? log = null) -> bool +StackExchange.Redis.IConnectionMultiplexer.ConfigureAsync(System.IO.TextWriter? log = null) -> System.Threading.Tasks.Task! +StackExchange.Redis.IConnectionMultiplexer.ConnectionFailed -> System.EventHandler! +StackExchange.Redis.IConnectionMultiplexer.ConnectionRestored -> System.EventHandler! +StackExchange.Redis.IConnectionMultiplexer.ErrorMessage -> System.EventHandler! +StackExchange.Redis.IConnectionMultiplexer.ExportConfiguration(System.IO.Stream! destination, StackExchange.Redis.ExportOptions options = (StackExchange.Redis.ExportOptions)-1) -> void +StackExchange.Redis.IConnectionMultiplexer.GetCounters() -> StackExchange.Redis.ServerCounters! +StackExchange.Redis.IConnectionMultiplexer.GetDatabase(int db = -1, object? asyncState = null) -> StackExchange.Redis.IDatabase! +StackExchange.Redis.IConnectionMultiplexer.GetEndPoints(bool configuredOnly = false) -> System.Net.EndPoint![]! +StackExchange.Redis.IConnectionMultiplexer.GetHashSlot(StackExchange.Redis.RedisKey key) -> int +StackExchange.Redis.IConnectionMultiplexer.GetServer(string! host, int port, object? asyncState = null) -> StackExchange.Redis.IServer! +StackExchange.Redis.IConnectionMultiplexer.GetServer(string! hostAndPort, object? asyncState = null) -> StackExchange.Redis.IServer! +StackExchange.Redis.IConnectionMultiplexer.GetServer(System.Net.EndPoint! endpoint, object? asyncState = null) -> StackExchange.Redis.IServer! +StackExchange.Redis.IConnectionMultiplexer.GetServer(System.Net.IPAddress! host, int port) -> StackExchange.Redis.IServer! +StackExchange.Redis.IConnectionMultiplexer.GetServers() -> StackExchange.Redis.IServer![]! +StackExchange.Redis.IConnectionMultiplexer.GetStatus() -> string! +StackExchange.Redis.IConnectionMultiplexer.GetStatus(System.IO.TextWriter! log) -> void +StackExchange.Redis.IConnectionMultiplexer.GetStormLog() -> string? +StackExchange.Redis.IConnectionMultiplexer.GetSubscriber(object? asyncState = null) -> StackExchange.Redis.ISubscriber! +StackExchange.Redis.IConnectionMultiplexer.HashSlot(StackExchange.Redis.RedisKey key) -> int +StackExchange.Redis.IConnectionMultiplexer.HashSlotMoved -> System.EventHandler! +StackExchange.Redis.IConnectionMultiplexer.IncludeDetailInExceptions.get -> bool +StackExchange.Redis.IConnectionMultiplexer.IncludeDetailInExceptions.set -> void +StackExchange.Redis.IConnectionMultiplexer.InternalError -> System.EventHandler! +StackExchange.Redis.IConnectionMultiplexer.IsConnected.get -> bool +StackExchange.Redis.IConnectionMultiplexer.IsConnecting.get -> bool +StackExchange.Redis.IConnectionMultiplexer.OperationCount.get -> long +StackExchange.Redis.IConnectionMultiplexer.PreserveAsyncOrder.get -> bool +StackExchange.Redis.IConnectionMultiplexer.PreserveAsyncOrder.set -> void +StackExchange.Redis.IConnectionMultiplexer.PublishReconfigure(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IConnectionMultiplexer.PublishReconfigureAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IConnectionMultiplexer.RegisterProfiler(System.Func! profilingSessionProvider) -> void +StackExchange.Redis.IConnectionMultiplexer.ResetStormLog() -> void +StackExchange.Redis.IConnectionMultiplexer.ServerMaintenanceEvent -> System.EventHandler! +StackExchange.Redis.IConnectionMultiplexer.StormLogThreshold.get -> int +StackExchange.Redis.IConnectionMultiplexer.StormLogThreshold.set -> void +StackExchange.Redis.IConnectionMultiplexer.TimeoutMilliseconds.get -> int +StackExchange.Redis.IConnectionMultiplexer.ToString() -> string! +StackExchange.Redis.IConnectionMultiplexer.Wait(System.Threading.Tasks.Task! task) -> void +StackExchange.Redis.IConnectionMultiplexer.Wait(System.Threading.Tasks.Task! task) -> T +StackExchange.Redis.IConnectionMultiplexer.WaitAll(params System.Threading.Tasks.Task![]! tasks) -> void +StackExchange.Redis.IDatabase +StackExchange.Redis.IDatabase.CreateBatch(object? asyncState = null) -> StackExchange.Redis.IBatch! +StackExchange.Redis.IDatabase.CreateTransaction(object? asyncState = null) -> StackExchange.Redis.ITransaction! +StackExchange.Redis.IDatabase.Database.get -> int +StackExchange.Redis.IDatabase.DebugObject(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.Execute(string! command, params object![]! args) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.Execute(string! command, System.Collections.Generic.ICollection! args, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.GeoAdd(StackExchange.Redis.RedisKey key, double longitude, double latitude, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.GeoAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.GeoEntry value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.GeoAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.GeoEntry[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.GeoDistance(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member1, StackExchange.Redis.RedisValue member2, StackExchange.Redis.GeoUnit unit = StackExchange.Redis.GeoUnit.Meters, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> double? +StackExchange.Redis.IDatabase.GeoHash(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string? +StackExchange.Redis.IDatabase.GeoHash(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! members, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string?[]! +StackExchange.Redis.IDatabase.GeoPosition(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.GeoPosition? +StackExchange.Redis.IDatabase.GeoPosition(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! members, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.GeoPosition?[]! +StackExchange.Redis.IDatabase.GeoRadius(StackExchange.Redis.RedisKey key, double longitude, double latitude, double radius, StackExchange.Redis.GeoUnit unit = StackExchange.Redis.GeoUnit.Meters, int count = -1, StackExchange.Redis.Order? order = null, StackExchange.Redis.GeoRadiusOptions options = StackExchange.Redis.GeoRadiusOptions.Default, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.GeoRadiusResult[]! +StackExchange.Redis.IDatabase.GeoRadius(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double radius, StackExchange.Redis.GeoUnit unit = StackExchange.Redis.GeoUnit.Meters, int count = -1, StackExchange.Redis.Order? order = null, StackExchange.Redis.GeoRadiusOptions options = StackExchange.Redis.GeoRadiusOptions.Default, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.GeoRadiusResult[]! +StackExchange.Redis.IDatabase.GeoRemove(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.GeoSearch(StackExchange.Redis.RedisKey key, double longitude, double latitude, StackExchange.Redis.GeoSearchShape! shape, int count = -1, bool demandClosest = true, StackExchange.Redis.Order? order = null, StackExchange.Redis.GeoRadiusOptions options = StackExchange.Redis.GeoRadiusOptions.Default, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.GeoRadiusResult[]! +StackExchange.Redis.IDatabase.GeoSearch(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.GeoSearchShape! shape, int count = -1, bool demandClosest = true, StackExchange.Redis.Order? order = null, StackExchange.Redis.GeoRadiusOptions options = StackExchange.Redis.GeoRadiusOptions.Default, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.GeoRadiusResult[]! +StackExchange.Redis.IDatabase.GeoSearchAndStore(StackExchange.Redis.RedisKey sourceKey, StackExchange.Redis.RedisKey destinationKey, double longitude, double latitude, StackExchange.Redis.GeoSearchShape! shape, int count = -1, bool demandClosest = true, StackExchange.Redis.Order? order = null, bool storeDistances = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.GeoSearchAndStore(StackExchange.Redis.RedisKey sourceKey, StackExchange.Redis.RedisKey destinationKey, StackExchange.Redis.RedisValue member, StackExchange.Redis.GeoSearchShape! shape, int count = -1, bool demandClosest = true, StackExchange.Redis.Order? order = null, bool storeDistances = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.HashDecrement(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> double +StackExchange.Redis.IDatabase.HashDecrement(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, long value = 1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.HashDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.HashDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.HashExists(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.HashFieldExpire(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ExpireResult[]! +StackExchange.Redis.IDatabase.HashFieldExpire(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ExpireResult[]! +StackExchange.Redis.IDatabase.HashFieldGetExpireDateTime(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long[]! +StackExchange.Redis.IDatabase.HashFieldGetTimeToLive(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long[]! +StackExchange.Redis.IDatabase.HashFieldPersist(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.PersistResult[]! +StackExchange.Redis.IDatabase.HashGet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashGet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashGetAll(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.HashEntry[]! +StackExchange.Redis.IDatabase.HashGetLease(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +StackExchange.Redis.IDatabase.HashIncrement(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> double +StackExchange.Redis.IDatabase.HashIncrement(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, long value = 1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.HashKeys(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.HashRandomField(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashRandomFields(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashRandomFieldsWithValues(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.HashEntry[]! +StackExchange.Redis.IDatabase.HashScan(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IEnumerable! +StackExchange.Redis.IDatabase.HashScan(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern, int pageSize, StackExchange.Redis.CommandFlags flags) -> System.Collections.Generic.IEnumerable! +StackExchange.Redis.IDatabase.HashScanNoValues(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IEnumerable! +StackExchange.Redis.IDatabase.HashSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IDatabase.HashSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.RedisValue value, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.HashStringLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.HashValues(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HyperLogLogAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.HyperLogLogAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.HyperLogLogLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.HyperLogLogLength(StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.HyperLogLogMerge(StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IDatabase.HyperLogLogMerge(StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey[]! sourceKeys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IDatabase.IdentifyEndpoint(StackExchange.Redis.RedisKey key = default(StackExchange.Redis.RedisKey), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Net.EndPoint? +StackExchange.Redis.IDatabase.KeyCopy(StackExchange.Redis.RedisKey sourceKey, StackExchange.Redis.RedisKey destinationKey, int destinationDatabase = -1, bool replace = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.KeyDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.KeyDelete(StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.KeyDump(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> byte[]? +StackExchange.Redis.IDatabase.KeyEncoding(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string? +StackExchange.Redis.IDatabase.KeyExists(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.KeyExists(StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.KeyExpire(StackExchange.Redis.RedisKey key, System.DateTime? expiry, StackExchange.Redis.CommandFlags flags) -> bool +StackExchange.Redis.IDatabase.KeyExpire(StackExchange.Redis.RedisKey key, System.DateTime? expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.KeyExpire(StackExchange.Redis.RedisKey key, System.TimeSpan? expiry, StackExchange.Redis.CommandFlags flags) -> bool +StackExchange.Redis.IDatabase.KeyExpire(StackExchange.Redis.RedisKey key, System.TimeSpan? expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.KeyExpireTime(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.DateTime? +StackExchange.Redis.IDatabase.KeyFrequency(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long? +StackExchange.Redis.IDatabase.KeyIdleTime(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.TimeSpan? +StackExchange.Redis.IDatabase.KeyMigrate(StackExchange.Redis.RedisKey key, System.Net.EndPoint! toServer, int toDatabase = 0, int timeoutMilliseconds = 0, StackExchange.Redis.MigrateOptions migrateOptions = StackExchange.Redis.MigrateOptions.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IDatabase.KeyMove(StackExchange.Redis.RedisKey key, int database, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.KeyPersist(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.KeyRandom(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisKey +StackExchange.Redis.IDatabase.KeyRefCount(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long? +StackExchange.Redis.IDatabase.KeyRename(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisKey newKey, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.KeyRestore(StackExchange.Redis.RedisKey key, byte[]! value, System.TimeSpan? expiry = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IDatabase.KeyTimeToLive(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.TimeSpan? +StackExchange.Redis.IDatabase.KeyTouch(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.KeyTouch(StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.KeyType(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisType +StackExchange.Redis.IDatabase.ListGetByIndex(StackExchange.Redis.RedisKey key, long index, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.ListInsertAfter(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pivot, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.ListInsertBefore(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pivot, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.ListMove(StackExchange.Redis.RedisKey sourceKey, StackExchange.Redis.RedisKey destinationKey, StackExchange.Redis.ListSide sourceSide, StackExchange.Redis.ListSide destinationSide, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.ListLeftPop(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.ListLeftPop(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.ListLeftPop(StackExchange.Redis.RedisKey[]! keys, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ListPopResult +StackExchange.Redis.IDatabase.ListLeftPush(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.ListLeftPush(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags) -> long +StackExchange.Redis.IDatabase.ListLeftPush(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.ListLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.ListPosition(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue element, long rank = 1, long maxLength = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.ListPositions(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue element, long count, long rank = 1, long maxLength = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long[]! +StackExchange.Redis.IDatabase.ListRange(StackExchange.Redis.RedisKey key, long start = 0, long stop = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.ListRemove(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, long count = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.ListRightPop(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.ListRightPop(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.ListRightPop(StackExchange.Redis.RedisKey[]! keys, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ListPopResult +StackExchange.Redis.IDatabase.ListRightPopLeftPush(StackExchange.Redis.RedisKey source, StackExchange.Redis.RedisKey destination, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.ListRightPush(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.ListRightPush(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags) -> long +StackExchange.Redis.IDatabase.ListRightPush(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.ListSetByIndex(StackExchange.Redis.RedisKey key, long index, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IDatabase.ListTrim(StackExchange.Redis.RedisKey key, long start, long stop, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IDatabase.LockExtend(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.LockQuery(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.LockRelease(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.LockTake(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.Publish(StackExchange.Redis.RedisChannel channel, StackExchange.Redis.RedisValue message, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.ScriptEvaluate(byte[]! hash, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.ScriptEvaluate(StackExchange.Redis.LoadedLuaScript! script, object? parameters = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.ScriptEvaluate(StackExchange.Redis.LuaScript! script, object? parameters = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.ScriptEvaluate(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.ScriptEvaluateReadOnly(byte[]! hash, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.ScriptEvaluateReadOnly(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IDatabase.SetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.SetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SetCombine(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.SetCombine(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.SetCombineAndStore(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SetCombineAndStore(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SetContains(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.SetContains(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool[]! +StackExchange.Redis.IDatabase.SetIntersectionLength(StackExchange.Redis.RedisKey[]! keys, long limit = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SetLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SetMembers(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.SetMove(StackExchange.Redis.RedisKey source, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.SetPop(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.SetPop(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.SetRandomMember(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.SetRandomMembers(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.SetRemove(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.SetRemove(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SetScan(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IEnumerable! +StackExchange.Redis.IDatabase.SetScan(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern, int pageSize, StackExchange.Redis.CommandFlags flags) -> System.Collections.Generic.IEnumerable! +StackExchange.Redis.IDatabase.Sort(StackExchange.Redis.RedisKey key, long skip = 0, long take = -1, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.SortType sortType = StackExchange.Redis.SortType.Numeric, StackExchange.Redis.RedisValue by = default(StackExchange.Redis.RedisValue), StackExchange.Redis.RedisValue[]? get = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.SortAndStore(StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey key, long skip = 0, long take = -1, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.SortType sortType = StackExchange.Redis.SortType.Numeric, StackExchange.Redis.RedisValue by = default(StackExchange.Redis.RedisValue), StackExchange.Redis.RedisValue[]? get = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double score, StackExchange.Redis.CommandFlags flags) -> bool +StackExchange.Redis.IDatabase.SortedSetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double score, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.SortedSetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double score, StackExchange.Redis.SortedSetWhen when = StackExchange.Redis.SortedSetWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.SortedSetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.SortedSetEntry[]! values, StackExchange.Redis.CommandFlags flags) -> long +StackExchange.Redis.IDatabase.SortedSetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.SortedSetEntry[]! values, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.SortedSetEntry[]! values, StackExchange.Redis.SortedSetWhen when = StackExchange.Redis.SortedSetWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetCombine(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey[]! keys, double[]? weights = null, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.SortedSetCombineWithScores(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey[]! keys, double[]? weights = null, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.SortedSetEntry[]! +StackExchange.Redis.IDatabase.SortedSetCombineAndStore(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetCombineAndStore(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey[]! keys, double[]? weights = null, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetDecrement(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> double +StackExchange.Redis.IDatabase.SortedSetIncrement(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> double +StackExchange.Redis.IDatabase.SortedSetIntersectionLength(StackExchange.Redis.RedisKey[]! keys, long limit = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetLength(StackExchange.Redis.RedisKey key, double min = -Infinity, double max = Infinity, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetLengthByValue(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue min, StackExchange.Redis.RedisValue max, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetPop(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.SortedSetEntry[]! +StackExchange.Redis.IDatabase.SortedSetPop(StackExchange.Redis.RedisKey key, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.SortedSetEntry? +StackExchange.Redis.IDatabase.SortedSetPop(StackExchange.Redis.RedisKey[]! keys, long count, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.SortedSetPopResult +StackExchange.Redis.IDatabase.SortedSetRandomMember(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.SortedSetRandomMembers(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.SortedSetRandomMembersWithScores(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.SortedSetEntry[]! +StackExchange.Redis.IDatabase.SortedSetRangeByRank(StackExchange.Redis.RedisKey key, long start = 0, long stop = -1, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.SortedSetRangeAndStore(StackExchange.Redis.RedisKey sourceKey, StackExchange.Redis.RedisKey destinationKey, StackExchange.Redis.RedisValue start, StackExchange.Redis.RedisValue stop, StackExchange.Redis.SortedSetOrder sortedSetOrder = StackExchange.Redis.SortedSetOrder.ByRank, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, long skip = 0, long? take = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetRangeByRankWithScores(StackExchange.Redis.RedisKey key, long start = 0, long stop = -1, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.SortedSetEntry[]! +StackExchange.Redis.IDatabase.SortedSetRangeByScore(StackExchange.Redis.RedisKey key, double start = -Infinity, double stop = Infinity, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, long skip = 0, long take = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.SortedSetRangeByScoreWithScores(StackExchange.Redis.RedisKey key, double start = -Infinity, double stop = Infinity, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, long skip = 0, long take = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.SortedSetEntry[]! +StackExchange.Redis.IDatabase.SortedSetRangeByValue(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue min = default(StackExchange.Redis.RedisValue), StackExchange.Redis.RedisValue max = default(StackExchange.Redis.RedisValue), StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, long skip = 0, long take = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.SortedSetRangeByValue(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue min, StackExchange.Redis.RedisValue max, StackExchange.Redis.Exclude exclude, long skip, long take = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.SortedSetRank(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long? +StackExchange.Redis.IDatabase.SortedSetRemove(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.SortedSetRemove(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! members, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetRemoveRangeByRank(StackExchange.Redis.RedisKey key, long start, long stop, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetRemoveRangeByScore(StackExchange.Redis.RedisKey key, double start, double stop, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetRemoveRangeByValue(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue min, StackExchange.Redis.RedisValue max, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.SortedSetScan(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IEnumerable! +StackExchange.Redis.IDatabase.SortedSetScan(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern, int pageSize, StackExchange.Redis.CommandFlags flags) -> System.Collections.Generic.IEnumerable! +StackExchange.Redis.IDatabase.SortedSetScore(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> double? +StackExchange.Redis.IDatabase.SortedSetScores(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! members, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> double?[]! +StackExchange.Redis.IDatabase.SortedSetUpdate(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double score, StackExchange.Redis.SortedSetWhen when = StackExchange.Redis.SortedSetWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.SortedSetUpdate(StackExchange.Redis.RedisKey key, StackExchange.Redis.SortedSetEntry[]! values, StackExchange.Redis.SortedSetWhen when = StackExchange.Redis.SortedSetWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StreamAcknowledge(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue messageId, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StreamAcknowledge(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StreamAcknowledgeAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.RedisValue messageId, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamTrimResult +StackExchange.Redis.IDatabase.StreamAcknowledgeAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamTrimResult[]! +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StreamAutoClaim(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue startAtId, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamAutoClaimResult +StackExchange.Redis.IDatabase.StreamAutoClaimIdsOnly(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue startAtId, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamAutoClaimIdsOnlyResult +StackExchange.Redis.IDatabase.StreamClaim(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamEntry[]! +StackExchange.Redis.IDatabase.StreamClaimIdsOnly(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.StreamConsumerGroupSetPosition(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue position, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.StreamConsumerInfo(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamConsumerInfo[]! +StackExchange.Redis.IDatabase.StreamCreateConsumerGroup(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue? position = null, bool createStream = true, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.StreamCreateConsumerGroup(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue? position, StackExchange.Redis.CommandFlags flags) -> bool +StackExchange.Redis.IDatabase.StreamDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StreamDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamTrimResult[]! +StackExchange.Redis.IDatabase.StreamDeleteConsumer(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue consumerName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StreamDeleteConsumerGroup(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.StreamGroupInfo(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamGroupInfo[]! +StackExchange.Redis.IDatabase.StreamInfo(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamInfo +StackExchange.Redis.IDatabase.StreamLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StreamPending(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamPendingInfo +StackExchange.Redis.IDatabase.StreamPendingMessages(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, int count, StackExchange.Redis.RedisValue consumerName, StackExchange.Redis.RedisValue? minId, StackExchange.Redis.RedisValue? maxId, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.StreamPendingMessageInfo[]! +StackExchange.Redis.IDatabase.StreamPendingMessages(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, int count, StackExchange.Redis.RedisValue consumerName, StackExchange.Redis.RedisValue? minId = null, StackExchange.Redis.RedisValue? maxId = null, long? minIdleTimeInMs = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamPendingMessageInfo[]! +StackExchange.Redis.IDatabase.StreamRange(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue? minId = null, StackExchange.Redis.RedisValue? maxId = null, int? count = null, StackExchange.Redis.Order messageOrder = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamEntry[]! +StackExchange.Redis.IDatabase.StreamRead(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue position, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamEntry[]! +StackExchange.Redis.IDatabase.StreamRead(StackExchange.Redis.StreamPosition[]! streamPositions, int? countPerStream = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisStream[]! +StackExchange.Redis.IDatabase.StreamReadGroup(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue consumerName, StackExchange.Redis.RedisValue? position = null, int? count = null, bool noAck = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.StreamEntry[]! +StackExchange.Redis.IDatabase.StreamReadGroup(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue consumerName, StackExchange.Redis.RedisValue? position, int? count, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.StreamEntry[]! +StackExchange.Redis.IDatabase.StreamReadGroup(StackExchange.Redis.StreamPosition[]! streamPositions, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue consumerName, int? countPerStream = null, bool noAck = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisStream[]! +StackExchange.Redis.IDatabase.StreamReadGroup(StackExchange.Redis.StreamPosition[]! streamPositions, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue consumerName, int? countPerStream, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisStream[]! +StackExchange.Redis.IDatabase.StreamTrim(StackExchange.Redis.RedisKey key, int maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> long +StackExchange.Redis.IDatabase.StreamTrim(StackExchange.Redis.RedisKey key, long maxLength, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode mode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StreamTrimByMinId(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue minId, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode mode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StringAppend(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StringBitCount(StackExchange.Redis.RedisKey key, long start, long end, StackExchange.Redis.CommandFlags flags) -> long +StackExchange.Redis.IDatabase.StringBitCount(StackExchange.Redis.RedisKey key, long start = 0, long end = -1, StackExchange.Redis.StringIndexType indexType = StackExchange.Redis.StringIndexType.Byte, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StringBitOperation(StackExchange.Redis.Bitwise operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second = default(StackExchange.Redis.RedisKey), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StringBitOperation(StackExchange.Redis.Bitwise operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StringBitPosition(StackExchange.Redis.RedisKey key, bool bit, long start, long end, StackExchange.Redis.CommandFlags flags) -> long +StackExchange.Redis.IDatabase.StringBitPosition(StackExchange.Redis.RedisKey key, bool bit, long start = 0, long end = -1, StackExchange.Redis.StringIndexType indexType = StackExchange.Redis.StringIndexType.Byte, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StringDecrement(StackExchange.Redis.RedisKey key, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> double +StackExchange.Redis.IDatabase.StringDecrement(StackExchange.Redis.RedisKey key, long value = 1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StringGet(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StringGet(StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.StringGetBit(StackExchange.Redis.RedisKey key, long offset, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.StringGetDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StringGetLease(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +StackExchange.Redis.IDatabase.StringGetRange(StackExchange.Redis.RedisKey key, long start, long end, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StringGetSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StringGetSetExpiry(StackExchange.Redis.RedisKey key, System.TimeSpan? expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StringGetSetExpiry(StackExchange.Redis.RedisKey key, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StringGetWithExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValueWithExpiry +StackExchange.Redis.IDatabase.StringIncrement(StackExchange.Redis.RedisKey key, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> double +StackExchange.Redis.IDatabase.StringIncrement(StackExchange.Redis.RedisKey key, long value = 1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StringLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StringLongestCommonSubsequence(StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string? +StackExchange.Redis.IDatabase.StringLongestCommonSubsequenceLength(StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IDatabase.StringLongestCommonSubsequenceWithMatches(StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, long minLength = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.LCSMatchResult +StackExchange.Redis.IDatabase.StringSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.StringSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.When when) -> bool +StackExchange.Redis.IDatabase.StringSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags) -> bool +StackExchange.Redis.IDatabase.StringSet(System.Collections.Generic.KeyValuePair[]! values, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.StringSetAndGet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StringSetAndGet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.StringSetBit(StackExchange.Redis.RedisKey key, long offset, bool bit, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.StringSetRange(StackExchange.Redis.RedisKey key, long offset, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabaseAsync +StackExchange.Redis.IDatabaseAsync.DebugObjectAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ExecuteAsync(string! command, params object![]! args) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ExecuteAsync(string! command, System.Collections.Generic.ICollection? args, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoAddAsync(StackExchange.Redis.RedisKey key, double longitude, double latitude, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.GeoEntry value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.GeoEntry[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoDistanceAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member1, StackExchange.Redis.RedisValue member2, StackExchange.Redis.GeoUnit unit = StackExchange.Redis.GeoUnit.Meters, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoHashAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoHashAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! members, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoPositionAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoPositionAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! members, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoRadiusAsync(StackExchange.Redis.RedisKey key, double longitude, double latitude, double radius, StackExchange.Redis.GeoUnit unit = StackExchange.Redis.GeoUnit.Meters, int count = -1, StackExchange.Redis.Order? order = null, StackExchange.Redis.GeoRadiusOptions options = StackExchange.Redis.GeoRadiusOptions.Default, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoRadiusAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double radius, StackExchange.Redis.GeoUnit unit = StackExchange.Redis.GeoUnit.Meters, int count = -1, StackExchange.Redis.Order? order = null, StackExchange.Redis.GeoRadiusOptions options = StackExchange.Redis.GeoRadiusOptions.Default, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoRemoveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoSearchAsync(StackExchange.Redis.RedisKey key, double longitude, double latitude, StackExchange.Redis.GeoSearchShape! shape, int count = -1, bool demandClosest = true, StackExchange.Redis.Order? order = null, StackExchange.Redis.GeoRadiusOptions options = StackExchange.Redis.GeoRadiusOptions.Default, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoSearchAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.GeoSearchShape! shape, int count = -1, bool demandClosest = true, StackExchange.Redis.Order? order = null, StackExchange.Redis.GeoRadiusOptions options = StackExchange.Redis.GeoRadiusOptions.Default, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoSearchAndStoreAsync(StackExchange.Redis.RedisKey sourceKey, StackExchange.Redis.RedisKey destinationKey, double longitude, double latitude, StackExchange.Redis.GeoSearchShape! shape, int count = -1, bool demandClosest = true, StackExchange.Redis.Order? order = null, bool storeDistances = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.GeoSearchAndStoreAsync(StackExchange.Redis.RedisKey sourceKey, StackExchange.Redis.RedisKey destinationKey, StackExchange.Redis.RedisValue member, StackExchange.Redis.GeoSearchShape! shape, int count = -1, bool demandClosest = true, StackExchange.Redis.Order? order = null, bool storeDistances = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashDecrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashDecrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, long value = 1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashExistsAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! + +StackExchange.Redis.IDatabaseAsync.HashFieldExpireAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldExpireAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetExpireDateTimeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetTimeToLiveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldPersistAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! + +StackExchange.Redis.IDatabaseAsync.HashGetAllAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashGetLeaseAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +StackExchange.Redis.IDatabaseAsync.HashIncrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashIncrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, long value = 1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashKeysAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashRandomFieldAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashRandomFieldsAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashRandomFieldsWithValuesAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashScanAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IAsyncEnumerable! +StackExchange.Redis.IDatabaseAsync.HashScanNoValuesAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IAsyncEnumerable! +StackExchange.Redis.IDatabaseAsync.HashSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.RedisValue value, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashStringLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashValuesAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HyperLogLogAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HyperLogLogAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HyperLogLogLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HyperLogLogLengthAsync(StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HyperLogLogMergeAsync(StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HyperLogLogMergeAsync(StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey[]! sourceKeys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.IdentifyEndpointAsync(StackExchange.Redis.RedisKey key = default(StackExchange.Redis.RedisKey), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.IsConnected(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabaseAsync.KeyCopyAsync(StackExchange.Redis.RedisKey sourceKey, StackExchange.Redis.RedisKey destinationKey, int destinationDatabase = -1, bool replace = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyDeleteAsync(StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyDumpAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyEncodingAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyExistsAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyExistsAsync(StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyExpireAsync(StackExchange.Redis.RedisKey key, System.DateTime? expiry, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyExpireAsync(StackExchange.Redis.RedisKey key, System.DateTime? expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyExpireAsync(StackExchange.Redis.RedisKey key, System.TimeSpan? expiry, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyExpireAsync(StackExchange.Redis.RedisKey key, System.TimeSpan? expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyExpireTimeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyFrequencyAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyIdleTimeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyMigrateAsync(StackExchange.Redis.RedisKey key, System.Net.EndPoint! toServer, int toDatabase = 0, int timeoutMilliseconds = 0, StackExchange.Redis.MigrateOptions migrateOptions = StackExchange.Redis.MigrateOptions.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyMoveAsync(StackExchange.Redis.RedisKey key, int database, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyPersistAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyRandomAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyRefCountAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyRenameAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisKey newKey, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyRestoreAsync(StackExchange.Redis.RedisKey key, byte[]! value, System.TimeSpan? expiry = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyTimeToLiveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyTouchAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyTouchAsync(StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.KeyTypeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListGetByIndexAsync(StackExchange.Redis.RedisKey key, long index, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListInsertAfterAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pivot, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListInsertBeforeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pivot, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListMoveAsync(StackExchange.Redis.RedisKey sourceKey, StackExchange.Redis.RedisKey destinationKey, StackExchange.Redis.ListSide sourceSide, StackExchange.Redis.ListSide destinationSide, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListLeftPopAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListLeftPopAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListLeftPopAsync(StackExchange.Redis.RedisKey[]! keys, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListLeftPushAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListLeftPushAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListLeftPushAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListPositionAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue element, long rank = 1, long maxLength = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListPositionsAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue element, long count, long rank = 1, long maxLength = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListRangeAsync(StackExchange.Redis.RedisKey key, long start = 0, long stop = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListRemoveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, long count = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListRightPopAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListRightPopAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListRightPopAsync(StackExchange.Redis.RedisKey[]! keys, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListRightPopLeftPushAsync(StackExchange.Redis.RedisKey source, StackExchange.Redis.RedisKey destination, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListRightPushAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListRightPushAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListRightPushAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListSetByIndexAsync(StackExchange.Redis.RedisKey key, long index, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ListTrimAsync(StackExchange.Redis.RedisKey key, long start, long stop, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.LockExtendAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.LockQueryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.LockReleaseAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.LockTakeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.PublishAsync(StackExchange.Redis.RedisChannel channel, StackExchange.Redis.RedisValue message, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(byte[]! hash, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(StackExchange.Redis.LoadedLuaScript! script, object? parameters = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(StackExchange.Redis.LuaScript! script, object? parameters = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateAsync(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateReadOnlyAsync(byte[]! hash, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.ScriptEvaluateReadOnlyAsync(string! script, StackExchange.Redis.RedisKey[]? keys = null, StackExchange.Redis.RedisValue[]? values = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetCombineAndStoreAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetCombineAndStoreAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetCombineAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetCombineAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetContainsAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetContainsAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetIntersectionLengthAsync(StackExchange.Redis.RedisKey[]! keys, long limit = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetMembersAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetMoveAsync(StackExchange.Redis.RedisKey source, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetPopAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetPopAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetRandomMemberAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetRandomMembersAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetRemoveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetRemoveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SetScanAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IAsyncEnumerable! +StackExchange.Redis.IDatabaseAsync.SortAndStoreAsync(StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey key, long skip = 0, long take = -1, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.SortType sortType = StackExchange.Redis.SortType.Numeric, StackExchange.Redis.RedisValue by = default(StackExchange.Redis.RedisValue), StackExchange.Redis.RedisValue[]? get = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortAsync(StackExchange.Redis.RedisKey key, long skip = 0, long take = -1, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.SortType sortType = StackExchange.Redis.SortType.Numeric, StackExchange.Redis.RedisValue by = default(StackExchange.Redis.RedisValue), StackExchange.Redis.RedisValue[]? get = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double score, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double score, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double score, StackExchange.Redis.SortedSetWhen when = StackExchange.Redis.SortedSetWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.SortedSetEntry[]! values, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.SortedSetEntry[]! values, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.SortedSetEntry[]! values, StackExchange.Redis.SortedSetWhen when = StackExchange.Redis.SortedSetWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetCombineAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey[]! keys, double[]? weights = null, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetCombineWithScoresAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey[]! keys, double[]? weights = null, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetCombineAndStoreAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetCombineAndStoreAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey[]! keys, double[]? weights = null, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetDecrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetIncrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetIntersectionLengthAsync(StackExchange.Redis.RedisKey[]! keys, long limit = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetLengthAsync(StackExchange.Redis.RedisKey key, double min = -Infinity, double max = Infinity, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetLengthByValueAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue min, StackExchange.Redis.RedisValue max, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetPopAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetPopAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetPopAsync(StackExchange.Redis.RedisKey[]! keys, long count, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRandomMemberAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRandomMembersAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRandomMembersWithScoresAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRangeAndStoreAsync(StackExchange.Redis.RedisKey sourceKey, StackExchange.Redis.RedisKey destinationKey, StackExchange.Redis.RedisValue start, StackExchange.Redis.RedisValue stop, StackExchange.Redis.SortedSetOrder sortedSetOrder = StackExchange.Redis.SortedSetOrder.ByRank, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, long skip = 0, long? take = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRangeByRankAsync(StackExchange.Redis.RedisKey key, long start = 0, long stop = -1, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRangeByRankWithScoresAsync(StackExchange.Redis.RedisKey key, long start = 0, long stop = -1, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRangeByScoreAsync(StackExchange.Redis.RedisKey key, double start = -Infinity, double stop = Infinity, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, long skip = 0, long take = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRangeByScoreWithScoresAsync(StackExchange.Redis.RedisKey key, double start = -Infinity, double stop = Infinity, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, long skip = 0, long take = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRangeByValueAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue min = default(StackExchange.Redis.RedisValue), StackExchange.Redis.RedisValue max = default(StackExchange.Redis.RedisValue), StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, long skip = 0, long take = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRangeByValueAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue min, StackExchange.Redis.RedisValue max, StackExchange.Redis.Exclude exclude, long skip, long take = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRankAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRemoveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRemoveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! members, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRemoveRangeByRankAsync(StackExchange.Redis.RedisKey key, long start, long stop, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRemoveRangeByScoreAsync(StackExchange.Redis.RedisKey key, double start, double stop, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetRemoveRangeByValueAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue min, StackExchange.Redis.RedisValue max, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetScanAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IAsyncEnumerable! +StackExchange.Redis.IDatabaseAsync.SortedSetScoreAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetScoresAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! members, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetUpdateAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double score, StackExchange.Redis.SortedSetWhen when = StackExchange.Redis.SortedSetWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.SortedSetUpdateAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.SortedSetEntry[]! values, StackExchange.Redis.SortedSetWhen when = StackExchange.Redis.SortedSetWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAcknowledgeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue messageId, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAcknowledgeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAcknowledgeAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.RedisValue messageId, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAcknowledgeAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.NameValueEntry[]! streamPairs, StackExchange.Redis.RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode trimMode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue streamField, StackExchange.Redis.RedisValue streamValue, StackExchange.Redis.RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAutoClaimAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue startAtId, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamAutoClaimIdsOnlyAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue startAtId, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamClaimAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamClaimIdsOnlyAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue consumerGroup, StackExchange.Redis.RedisValue claimingConsumer, long minIdleTimeInMs, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamConsumerGroupSetPositionAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue position, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamConsumerInfoAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamCreateConsumerGroupAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue? position = null, bool createStream = true, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamCreateConsumerGroupAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue? position, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! messageIds, StackExchange.Redis.StreamTrimMode mode, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamDeleteConsumerAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue consumerName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamDeleteConsumerGroupAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamGroupInfoAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamInfoAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamPendingAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamPendingMessagesAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, int count, StackExchange.Redis.RedisValue consumerName, StackExchange.Redis.RedisValue? minId, StackExchange.Redis.RedisValue? maxId, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamPendingMessagesAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, int count, StackExchange.Redis.RedisValue consumerName, StackExchange.Redis.RedisValue? minId = null, StackExchange.Redis.RedisValue? maxId = null, long? minIdleTimeInMs = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamRangeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue? minId = null, StackExchange.Redis.RedisValue? maxId = null, int? count = null, StackExchange.Redis.Order messageOrder = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamReadAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue position, int? count = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamReadAsync(StackExchange.Redis.StreamPosition[]! streamPositions, int? countPerStream = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamReadGroupAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue consumerName, StackExchange.Redis.RedisValue? position = null, int? count = null, bool noAck = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamReadGroupAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue consumerName, StackExchange.Redis.RedisValue? position, int? count, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamReadGroupAsync(StackExchange.Redis.StreamPosition[]! streamPositions, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue consumerName, int? countPerStream = null, bool noAck = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamReadGroupAsync(StackExchange.Redis.StreamPosition[]! streamPositions, StackExchange.Redis.RedisValue groupName, StackExchange.Redis.RedisValue consumerName, int? countPerStream, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamTrimAsync(StackExchange.Redis.RedisKey key, int maxLength, bool useApproximateMaxLength, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamTrimAsync(StackExchange.Redis.RedisKey key, long maxLength, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode mode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StreamTrimByMinIdAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue minId, bool useApproximateMaxLength = false, long? limit = null, StackExchange.Redis.StreamTrimMode mode = StackExchange.Redis.StreamTrimMode.KeepReferences, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringAppendAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringBitCountAsync(StackExchange.Redis.RedisKey key, long start, long end, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringBitCountAsync(StackExchange.Redis.RedisKey key, long start = 0, long end = -1, StackExchange.Redis.StringIndexType indexType = StackExchange.Redis.StringIndexType.Byte, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringBitOperationAsync(StackExchange.Redis.Bitwise operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second = default(StackExchange.Redis.RedisKey), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringBitOperationAsync(StackExchange.Redis.Bitwise operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringBitPositionAsync(StackExchange.Redis.RedisKey key, bool bit, long start, long end, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringBitPositionAsync(StackExchange.Redis.RedisKey key, bool bit, long start = 0, long end = -1, StackExchange.Redis.StringIndexType indexType = StackExchange.Redis.StringIndexType.Byte, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringDecrementAsync(StackExchange.Redis.RedisKey key, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringDecrementAsync(StackExchange.Redis.RedisKey key, long value = 1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringGetAsync(StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringGetBitAsync(StackExchange.Redis.RedisKey key, long offset, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringGetDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringGetLeaseAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +StackExchange.Redis.IDatabaseAsync.StringGetRangeAsync(StackExchange.Redis.RedisKey key, long start, long end, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringGetSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringGetSetExpiryAsync(StackExchange.Redis.RedisKey key, System.TimeSpan? expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringGetSetExpiryAsync(StackExchange.Redis.RedisKey key, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringGetWithExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringIncrementAsync(StackExchange.Redis.RedisKey key, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringIncrementAsync(StackExchange.Redis.RedisKey key, long value = 1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringLongestCommonSubsequenceAsync(StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringLongestCommonSubsequenceLengthAsync(StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringLongestCommonSubsequenceWithMatchesAsync(StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, long minLength = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringSetAndGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringSetAndGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.When when) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry, StackExchange.Redis.When when, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringSetAsync(System.Collections.Generic.KeyValuePair[]! values, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringSetBitAsync(StackExchange.Redis.RedisKey key, long offset, bool bit, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.StringSetRangeAsync(StackExchange.Redis.RedisKey key, long offset, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.InternalErrorEventArgs +StackExchange.Redis.InternalErrorEventArgs.ConnectionType.get -> StackExchange.Redis.ConnectionType +StackExchange.Redis.InternalErrorEventArgs.EndPoint.get -> System.Net.EndPoint? +StackExchange.Redis.InternalErrorEventArgs.Exception.get -> System.Exception! +StackExchange.Redis.InternalErrorEventArgs.InternalErrorEventArgs(object! sender, System.Net.EndPoint! endpoint, StackExchange.Redis.ConnectionType connectionType, System.Exception! exception, string! origin) -> void +StackExchange.Redis.InternalErrorEventArgs.Origin.get -> string? +StackExchange.Redis.IReconnectRetryPolicy +StackExchange.Redis.IReconnectRetryPolicy.ShouldRetry(long currentRetryCount, int timeElapsedMillisecondsSinceLastRetry) -> bool +StackExchange.Redis.IRedis +StackExchange.Redis.IRedis.Ping(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.TimeSpan +StackExchange.Redis.IRedisAsync +StackExchange.Redis.IRedisAsync.Multiplexer.get -> StackExchange.Redis.IConnectionMultiplexer! +StackExchange.Redis.IRedisAsync.PingAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IRedisAsync.TryWait(System.Threading.Tasks.Task! task) -> bool +StackExchange.Redis.IRedisAsync.Wait(System.Threading.Tasks.Task! task) -> void +StackExchange.Redis.IRedisAsync.Wait(System.Threading.Tasks.Task! task) -> T +StackExchange.Redis.IRedisAsync.WaitAll(params System.Threading.Tasks.Task![]! tasks) -> void +StackExchange.Redis.IScanningCursor +StackExchange.Redis.IScanningCursor.Cursor.get -> long +StackExchange.Redis.IScanningCursor.PageOffset.get -> int +StackExchange.Redis.IScanningCursor.PageSize.get -> int +StackExchange.Redis.IServer +StackExchange.Redis.IServer.AllowReplicaWrites.get -> bool +StackExchange.Redis.IServer.AllowReplicaWrites.set -> void +StackExchange.Redis.IServer.AllowSlaveWrites.get -> bool +StackExchange.Redis.IServer.AllowSlaveWrites.set -> void +StackExchange.Redis.IServer.ClientKill(long? id = null, StackExchange.Redis.ClientType? clientType = null, System.Net.EndPoint? endpoint = null, bool skipMe = true, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IServer.ClientKill(System.Net.EndPoint! endpoint, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.ClientKill(StackExchange.Redis.ClientKillFilter! filter, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IServer.ClientKillAsync(long? id = null, StackExchange.Redis.ClientType? clientType = null, System.Net.EndPoint? endpoint = null, bool skipMe = true, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ClientKillAsync(System.Net.EndPoint! endpoint, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ClientKillAsync(StackExchange.Redis.ClientKillFilter! filter, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ClientList(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ClientInfo![]! +StackExchange.Redis.IServer.ClientListAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ClusterConfiguration.get -> StackExchange.Redis.ClusterConfiguration? +StackExchange.Redis.IServer.ClusterNodes(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ClusterConfiguration? +StackExchange.Redis.IServer.ClusterNodesAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ClusterNodesRaw(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string? +StackExchange.Redis.IServer.ClusterNodesRawAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ConfigGet(StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.KeyValuePair[]! +StackExchange.Redis.IServer.ConfigGetAsync(StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task[]!>! +StackExchange.Redis.IServer.ConfigResetStatistics(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.ConfigResetStatisticsAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ConfigRewrite(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.ConfigRewriteAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ConfigSet(StackExchange.Redis.RedisValue setting, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.ConfigSetAsync(StackExchange.Redis.RedisValue setting, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.CommandCount(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IServer.CommandCountAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.CommandGetKeys(StackExchange.Redis.RedisValue[]! command, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisKey[]! +StackExchange.Redis.IServer.CommandGetKeysAsync(StackExchange.Redis.RedisValue[]! command, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.CommandList(StackExchange.Redis.RedisValue? moduleName = null, StackExchange.Redis.RedisValue? category = null, StackExchange.Redis.RedisValue? pattern = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string![]! +StackExchange.Redis.IServer.CommandListAsync(StackExchange.Redis.RedisValue? moduleName = null, StackExchange.Redis.RedisValue? category = null, StackExchange.Redis.RedisValue? pattern = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.DatabaseCount.get -> int +StackExchange.Redis.IServer.DatabaseSize(int database = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IServer.DatabaseSizeAsync(int database = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.Echo(StackExchange.Redis.RedisValue message, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IServer.EchoAsync(StackExchange.Redis.RedisValue message, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.EndPoint.get -> System.Net.EndPoint! +StackExchange.Redis.IServer.Execute(string! command, params object![]! args) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IServer.Execute(string! command, System.Collections.Generic.ICollection! args, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IServer.ExecuteAsync(string! command, params object![]! args) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ExecuteAsync(string! command, System.Collections.Generic.ICollection! args, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.Features.get -> StackExchange.Redis.RedisFeatures +StackExchange.Redis.IServer.FlushAllDatabases(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.FlushAllDatabasesAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.FlushDatabase(int database = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.FlushDatabaseAsync(int database = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.GetCounters() -> StackExchange.Redis.ServerCounters! +StackExchange.Redis.IServer.Info(StackExchange.Redis.RedisValue section = default(StackExchange.Redis.RedisValue), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Linq.IGrouping>![]! +StackExchange.Redis.IServer.InfoAsync(StackExchange.Redis.RedisValue section = default(StackExchange.Redis.RedisValue), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task>![]!>! +StackExchange.Redis.IServer.InfoRaw(StackExchange.Redis.RedisValue section = default(StackExchange.Redis.RedisValue), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string? +StackExchange.Redis.IServer.InfoRawAsync(StackExchange.Redis.RedisValue section = default(StackExchange.Redis.RedisValue), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.IsConnected.get -> bool +StackExchange.Redis.IServer.IsReplica.get -> bool +StackExchange.Redis.IServer.IsSlave.get -> bool +StackExchange.Redis.IServer.Keys(int database = -1, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IEnumerable! +StackExchange.Redis.IServer.Keys(int database, StackExchange.Redis.RedisValue pattern, int pageSize, StackExchange.Redis.CommandFlags flags) -> System.Collections.Generic.IEnumerable! +StackExchange.Redis.IServer.KeysAsync(int database = -1, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IAsyncEnumerable! +StackExchange.Redis.IServer.LastSave(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.DateTime +StackExchange.Redis.IServer.LastSaveAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.LatencyDoctor(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string! +StackExchange.Redis.IServer.LatencyDoctorAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.LatencyHistory(string! eventName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.LatencyHistoryEntry[]! +StackExchange.Redis.IServer.LatencyHistoryAsync(string! eventName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.LatencyLatest(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.LatencyLatestEntry[]! +StackExchange.Redis.IServer.LatencyLatestAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.LatencyReset(string![]? eventNames = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IServer.LatencyResetAsync(string![]? eventNames = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.MakeMaster(StackExchange.Redis.ReplicationChangeOptions options, System.IO.TextWriter? log = null) -> void +StackExchange.Redis.IServer.MakePrimaryAsync(StackExchange.Redis.ReplicationChangeOptions options, System.IO.TextWriter? log = null) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.MemoryAllocatorStats(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string? +StackExchange.Redis.IServer.MemoryAllocatorStatsAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.MemoryDoctor(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string! +StackExchange.Redis.IServer.MemoryDoctorAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.MemoryPurge(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.MemoryPurgeAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.MemoryStats(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IServer.MemoryStatsAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ReplicaOf(System.Net.EndPoint! master, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.ReplicaOfAsync(System.Net.EndPoint! master, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.Role(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Role! +StackExchange.Redis.IServer.RoleAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.Save(StackExchange.Redis.SaveType type, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.SaveAsync(StackExchange.Redis.SaveType type, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ScriptExists(byte[]! sha1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IServer.ScriptExists(string! script, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IServer.ScriptExistsAsync(byte[]! sha1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ScriptExistsAsync(string! script, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ScriptFlush(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.ScriptFlushAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ScriptLoad(StackExchange.Redis.LuaScript! script, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.LoadedLuaScript! +StackExchange.Redis.IServer.ScriptLoad(string! script, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> byte[]! +StackExchange.Redis.IServer.ScriptLoadAsync(StackExchange.Redis.LuaScript! script, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.ScriptLoadAsync(string! script, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.SentinelFailover(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.SentinelFailoverAsync(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.SentinelGetMasterAddressByName(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Net.EndPoint? +StackExchange.Redis.IServer.SentinelGetMasterAddressByNameAsync(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.SentinelGetReplicaAddresses(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Net.EndPoint![]! +StackExchange.Redis.IServer.SentinelGetReplicaAddressesAsync(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.SentinelGetSentinelAddresses(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Net.EndPoint![]! +StackExchange.Redis.IServer.SentinelGetSentinelAddressesAsync(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.SentinelMaster(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.KeyValuePair[]! +StackExchange.Redis.IServer.SentinelMasterAsync(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task[]!>! +StackExchange.Redis.IServer.SentinelMasters(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.KeyValuePair[]![]! +StackExchange.Redis.IServer.SentinelMastersAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task[]![]!>! +StackExchange.Redis.IServer.SentinelReplicas(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.KeyValuePair[]![]! +StackExchange.Redis.IServer.SentinelReplicasAsync(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task[]![]!>! +StackExchange.Redis.IServer.SentinelSentinels(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.KeyValuePair[]![]! +StackExchange.Redis.IServer.SentinelSentinelsAsync(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task[]![]!>! +StackExchange.Redis.IServer.SentinelSlaves(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.KeyValuePair[]![]! +StackExchange.Redis.IServer.SentinelSlavesAsync(string! serviceName, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task[]![]!>! +StackExchange.Redis.IServer.ServerType.get -> StackExchange.Redis.ServerType +StackExchange.Redis.IServer.Shutdown(StackExchange.Redis.ShutdownMode shutdownMode = StackExchange.Redis.ShutdownMode.Default, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.SlaveOf(System.Net.EndPoint! master, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.SlaveOfAsync(System.Net.EndPoint! master, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.SlowlogGet(int count = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.CommandTrace![]! +StackExchange.Redis.IServer.SlowlogGetAsync(int count = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.SlowlogReset(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.SlowlogResetAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.SubscriptionChannels(StackExchange.Redis.RedisChannel pattern = default(StackExchange.Redis.RedisChannel), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisChannel[]! +StackExchange.Redis.IServer.SubscriptionChannelsAsync(StackExchange.Redis.RedisChannel pattern = default(StackExchange.Redis.RedisChannel), StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.SubscriptionPatternCount(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IServer.SubscriptionPatternCountAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.SubscriptionSubscriberCount(StackExchange.Redis.RedisChannel channel, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.IServer.SubscriptionSubscriberCountAsync(StackExchange.Redis.RedisChannel channel, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.SwapDatabases(int first, int second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.IServer.SwapDatabasesAsync(int first, int second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.Time(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.DateTime +StackExchange.Redis.IServer.TimeAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IServer.Version.get -> System.Version! +StackExchange.Redis.ISubscriber +StackExchange.Redis.ISubscriber.IdentifyEndpoint(StackExchange.Redis.RedisChannel channel, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Net.EndPoint? +StackExchange.Redis.ISubscriber.IdentifyEndpointAsync(StackExchange.Redis.RedisChannel channel, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.ISubscriber.IsConnected(StackExchange.Redis.RedisChannel channel = default(StackExchange.Redis.RedisChannel)) -> bool +StackExchange.Redis.ISubscriber.Publish(StackExchange.Redis.RedisChannel channel, StackExchange.Redis.RedisValue message, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +StackExchange.Redis.ISubscriber.PublishAsync(StackExchange.Redis.RedisChannel channel, StackExchange.Redis.RedisValue message, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.ISubscriber.Subscribe(StackExchange.Redis.RedisChannel channel, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ChannelMessageQueue! +StackExchange.Redis.ISubscriber.Subscribe(StackExchange.Redis.RedisChannel channel, System.Action! handler, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.ISubscriber.SubscribeAsync(StackExchange.Redis.RedisChannel channel, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.ISubscriber.SubscribeAsync(StackExchange.Redis.RedisChannel channel, System.Action! handler, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.ISubscriber.SubscribedEndpoint(StackExchange.Redis.RedisChannel channel) -> System.Net.EndPoint? +StackExchange.Redis.ISubscriber.Unsubscribe(StackExchange.Redis.RedisChannel channel, System.Action? handler = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.ISubscriber.UnsubscribeAll(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void +StackExchange.Redis.ISubscriber.UnsubscribeAllAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.ISubscriber.UnsubscribeAsync(StackExchange.Redis.RedisChannel channel, System.Action? handler = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.ITransaction +StackExchange.Redis.ITransaction.AddCondition(StackExchange.Redis.Condition! condition) -> StackExchange.Redis.ConditionResult! +StackExchange.Redis.ITransaction.Execute(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.ITransaction.ExecuteAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.KeyspaceIsolation.DatabaseExtensions +StackExchange.Redis.LatencyHistoryEntry +StackExchange.Redis.LatencyHistoryEntry.DurationMilliseconds.get -> int +StackExchange.Redis.LatencyHistoryEntry.LatencyHistoryEntry() -> void +StackExchange.Redis.LatencyHistoryEntry.Timestamp.get -> System.DateTime +StackExchange.Redis.LatencyLatestEntry +StackExchange.Redis.LatencyLatestEntry.DurationMilliseconds.get -> int +StackExchange.Redis.LatencyLatestEntry.EventName.get -> string! +StackExchange.Redis.LatencyLatestEntry.LatencyLatestEntry() -> void +StackExchange.Redis.LatencyLatestEntry.MaxDurationMilliseconds.get -> int +StackExchange.Redis.LatencyLatestEntry.Timestamp.get -> System.DateTime +StackExchange.Redis.Lease +StackExchange.Redis.Lease.ArraySegment.get -> System.ArraySegment +StackExchange.Redis.Lease.Dispose() -> void +StackExchange.Redis.Lease.Length.get -> int +StackExchange.Redis.Lease.Memory.get -> System.Memory +StackExchange.Redis.Lease.Span.get -> System.Span +StackExchange.Redis.LinearRetry +StackExchange.Redis.LinearRetry.LinearRetry(int maxRetryElapsedTimeAllowedMilliseconds) -> void +StackExchange.Redis.LinearRetry.ShouldRetry(long currentRetryCount, int timeElapsedMillisecondsSinceLastRetry) -> bool +StackExchange.Redis.LoadedLuaScript +StackExchange.Redis.LoadedLuaScript.Evaluate(StackExchange.Redis.IDatabase! db, object? ps = null, StackExchange.Redis.RedisKey? withKeyPrefix = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.LoadedLuaScript.EvaluateAsync(StackExchange.Redis.IDatabaseAsync! db, object? ps = null, StackExchange.Redis.RedisKey? withKeyPrefix = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.LoadedLuaScript.ExecutableScript.get -> string! +StackExchange.Redis.LoadedLuaScript.Hash.get -> byte[]! +StackExchange.Redis.LoadedLuaScript.OriginalScript.get -> string! +StackExchange.Redis.LCSMatchResult +StackExchange.Redis.LCSMatchResult.IsEmpty.get -> bool +StackExchange.Redis.LCSMatchResult.LCSMatchResult() -> void +StackExchange.Redis.LCSMatchResult.LongestMatchLength.get -> long +StackExchange.Redis.LCSMatchResult.Matches.get -> StackExchange.Redis.LCSMatchResult.LCSMatch[]! +StackExchange.Redis.LCSMatchResult.LCSMatch +StackExchange.Redis.LCSMatchResult.LCSMatch.LCSMatch() -> void +StackExchange.Redis.LCSMatchResult.LCSMatch.FirstStringIndex.get -> long +StackExchange.Redis.LCSMatchResult.LCSMatch.SecondStringIndex.get -> long +StackExchange.Redis.LCSMatchResult.LCSMatch.Length.get -> long +StackExchange.Redis.ListPopResult +StackExchange.Redis.ListPopResult.IsNull.get -> bool +StackExchange.Redis.ListPopResult.Key.get -> StackExchange.Redis.RedisKey +StackExchange.Redis.ListPopResult.ListPopResult() -> void +StackExchange.Redis.ListPopResult.Values.get -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.ListSide +StackExchange.Redis.ListSide.Left = 0 -> StackExchange.Redis.ListSide +StackExchange.Redis.ListSide.Right = 1 -> StackExchange.Redis.ListSide +StackExchange.Redis.LuaScript +StackExchange.Redis.LuaScript.Evaluate(StackExchange.Redis.IDatabase! db, object? ps = null, StackExchange.Redis.RedisKey? withKeyPrefix = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.LuaScript.EvaluateAsync(StackExchange.Redis.IDatabaseAsync! db, object? ps = null, StackExchange.Redis.RedisKey? withKeyPrefix = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.LuaScript.ExecutableScript.get -> string! +StackExchange.Redis.LuaScript.Load(StackExchange.Redis.IServer! server, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.LoadedLuaScript! +StackExchange.Redis.LuaScript.LoadAsync(StackExchange.Redis.IServer! server, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.LuaScript.OriginalScript.get -> string! +StackExchange.Redis.Maintenance.AzureMaintenanceEvent +StackExchange.Redis.Maintenance.AzureMaintenanceEvent.IPAddress.get -> System.Net.IPAddress? +StackExchange.Redis.Maintenance.AzureMaintenanceEvent.IsReplica.get -> bool +StackExchange.Redis.Maintenance.AzureMaintenanceEvent.NonSslPort.get -> int +StackExchange.Redis.Maintenance.AzureMaintenanceEvent.NotificationType.get -> StackExchange.Redis.Maintenance.AzureNotificationType +StackExchange.Redis.Maintenance.AzureMaintenanceEvent.NotificationTypeString.get -> string! +StackExchange.Redis.Maintenance.AzureMaintenanceEvent.SslPort.get -> int +StackExchange.Redis.Maintenance.AzureNotificationType +StackExchange.Redis.Maintenance.AzureNotificationType.NodeMaintenanceEnded = 4 -> StackExchange.Redis.Maintenance.AzureNotificationType +StackExchange.Redis.Maintenance.AzureNotificationType.NodeMaintenanceFailoverComplete = 5 -> StackExchange.Redis.Maintenance.AzureNotificationType +StackExchange.Redis.Maintenance.AzureNotificationType.NodeMaintenanceScaleComplete = 6 -> StackExchange.Redis.Maintenance.AzureNotificationType +StackExchange.Redis.Maintenance.AzureNotificationType.NodeMaintenanceScheduled = 1 -> StackExchange.Redis.Maintenance.AzureNotificationType +StackExchange.Redis.Maintenance.AzureNotificationType.NodeMaintenanceStart = 3 -> StackExchange.Redis.Maintenance.AzureNotificationType +StackExchange.Redis.Maintenance.AzureNotificationType.NodeMaintenanceStarting = 2 -> StackExchange.Redis.Maintenance.AzureNotificationType +StackExchange.Redis.Maintenance.AzureNotificationType.Unknown = 0 -> StackExchange.Redis.Maintenance.AzureNotificationType +StackExchange.Redis.Maintenance.ServerMaintenanceEvent +StackExchange.Redis.Maintenance.ServerMaintenanceEvent.RawMessage.get -> string? +StackExchange.Redis.Maintenance.ServerMaintenanceEvent.ReceivedTimeUtc.get -> System.DateTime +StackExchange.Redis.Maintenance.ServerMaintenanceEvent.StartTimeUtc.get -> System.DateTime? +StackExchange.Redis.MigrateOptions +StackExchange.Redis.MigrateOptions.Copy = 1 -> StackExchange.Redis.MigrateOptions +StackExchange.Redis.MigrateOptions.None = 0 -> StackExchange.Redis.MigrateOptions +StackExchange.Redis.MigrateOptions.Replace = 2 -> StackExchange.Redis.MigrateOptions +StackExchange.Redis.NameValueEntry +StackExchange.Redis.NameValueEntry.Equals(StackExchange.Redis.NameValueEntry other) -> bool +StackExchange.Redis.NameValueEntry.Name.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.NameValueEntry.NameValueEntry() -> void +StackExchange.Redis.NameValueEntry.NameValueEntry(StackExchange.Redis.RedisValue name, StackExchange.Redis.RedisValue value) -> void +StackExchange.Redis.NameValueEntry.Value.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.Order +StackExchange.Redis.Order.Ascending = 0 -> StackExchange.Redis.Order +StackExchange.Redis.Order.Descending = 1 -> StackExchange.Redis.Order +StackExchange.Redis.PersistResult +StackExchange.Redis.PersistResult.ConditionNotMet = -1 -> StackExchange.Redis.PersistResult +StackExchange.Redis.PersistResult.NoSuchField = -2 -> StackExchange.Redis.PersistResult +StackExchange.Redis.PersistResult.Success = 1 -> StackExchange.Redis.PersistResult +StackExchange.Redis.Profiling.IProfiledCommand +StackExchange.Redis.Profiling.IProfiledCommand.Command.get -> string! +StackExchange.Redis.Profiling.IProfiledCommand.CommandCreated.get -> System.DateTime +StackExchange.Redis.Profiling.IProfiledCommand.CreationToEnqueued.get -> System.TimeSpan +StackExchange.Redis.Profiling.IProfiledCommand.Db.get -> int +StackExchange.Redis.Profiling.IProfiledCommand.ElapsedTime.get -> System.TimeSpan +StackExchange.Redis.Profiling.IProfiledCommand.EndPoint.get -> System.Net.EndPoint! +StackExchange.Redis.Profiling.IProfiledCommand.EnqueuedToSending.get -> System.TimeSpan +StackExchange.Redis.Profiling.IProfiledCommand.Flags.get -> StackExchange.Redis.CommandFlags +StackExchange.Redis.Profiling.IProfiledCommand.ResponseToCompletion.get -> System.TimeSpan +StackExchange.Redis.Profiling.IProfiledCommand.RetransmissionOf.get -> StackExchange.Redis.Profiling.IProfiledCommand? +StackExchange.Redis.Profiling.IProfiledCommand.RetransmissionReason.get -> StackExchange.Redis.RetransmissionReasonType? +StackExchange.Redis.Profiling.IProfiledCommand.SentToResponse.get -> System.TimeSpan +StackExchange.Redis.Profiling.ProfiledCommandEnumerable +StackExchange.Redis.Profiling.ProfiledCommandEnumerable.Count() -> int +StackExchange.Redis.Profiling.ProfiledCommandEnumerable.Count(System.Func! predicate) -> int +StackExchange.Redis.Profiling.ProfiledCommandEnumerable.Enumerator +StackExchange.Redis.Profiling.ProfiledCommandEnumerable.Enumerator.Current.get -> StackExchange.Redis.Profiling.IProfiledCommand! +StackExchange.Redis.Profiling.ProfiledCommandEnumerable.Enumerator.Dispose() -> void +StackExchange.Redis.Profiling.ProfiledCommandEnumerable.Enumerator.Enumerator() -> void +StackExchange.Redis.Profiling.ProfiledCommandEnumerable.Enumerator.MoveNext() -> bool +StackExchange.Redis.Profiling.ProfiledCommandEnumerable.Enumerator.Reset() -> void +StackExchange.Redis.Profiling.ProfiledCommandEnumerable.GetEnumerator() -> StackExchange.Redis.Profiling.ProfiledCommandEnumerable.Enumerator +StackExchange.Redis.Profiling.ProfiledCommandEnumerable.ProfiledCommandEnumerable() -> void +StackExchange.Redis.Profiling.ProfiledCommandEnumerable.ToArray() -> StackExchange.Redis.Profiling.IProfiledCommand![]! +StackExchange.Redis.Profiling.ProfiledCommandEnumerable.ToList() -> System.Collections.Generic.List! +StackExchange.Redis.Profiling.ProfilingSession +StackExchange.Redis.Profiling.ProfilingSession.FinishProfiling() -> StackExchange.Redis.Profiling.ProfiledCommandEnumerable +StackExchange.Redis.Profiling.ProfilingSession.ProfilingSession(object? userToken = null) -> void +StackExchange.Redis.Profiling.ProfilingSession.UserToken.get -> object? +StackExchange.Redis.Proxy +StackExchange.Redis.Proxy.Envoyproxy = 2 -> StackExchange.Redis.Proxy +StackExchange.Redis.Proxy.None = 0 -> StackExchange.Redis.Proxy +StackExchange.Redis.Proxy.Twemproxy = 1 -> StackExchange.Redis.Proxy +StackExchange.Redis.RedisChannel +StackExchange.Redis.RedisChannel.Equals(StackExchange.Redis.RedisChannel other) -> bool +StackExchange.Redis.RedisChannel.IsNullOrEmpty.get -> bool +StackExchange.Redis.RedisChannel.IsPattern.get -> bool +StackExchange.Redis.RedisChannel.IsSharded.get -> bool +StackExchange.Redis.RedisChannel.PatternMode +StackExchange.Redis.RedisChannel.PatternMode.Auto = 0 -> StackExchange.Redis.RedisChannel.PatternMode +StackExchange.Redis.RedisChannel.PatternMode.Literal = 1 -> StackExchange.Redis.RedisChannel.PatternMode +StackExchange.Redis.RedisChannel.PatternMode.Pattern = 2 -> StackExchange.Redis.RedisChannel.PatternMode +StackExchange.Redis.RedisChannel.RedisChannel() -> void +StackExchange.Redis.RedisChannel.RedisChannel(byte[]? value, StackExchange.Redis.RedisChannel.PatternMode mode) -> void +StackExchange.Redis.RedisChannel.RedisChannel(string! value, StackExchange.Redis.RedisChannel.PatternMode mode) -> void +StackExchange.Redis.RedisCommandException +StackExchange.Redis.RedisCommandException.RedisCommandException(string! message) -> void +StackExchange.Redis.RedisCommandException.RedisCommandException(string! message, System.Exception! innerException) -> void +StackExchange.Redis.RedisConnectionException +StackExchange.Redis.RedisConnectionException.CommandStatus.get -> StackExchange.Redis.CommandStatus +StackExchange.Redis.RedisConnectionException.FailureType.get -> StackExchange.Redis.ConnectionFailureType +StackExchange.Redis.RedisConnectionException.RedisConnectionException(StackExchange.Redis.ConnectionFailureType failureType, string! message) -> void +StackExchange.Redis.RedisConnectionException.RedisConnectionException(StackExchange.Redis.ConnectionFailureType failureType, string! message, System.Exception? innerException) -> void +StackExchange.Redis.RedisConnectionException.RedisConnectionException(StackExchange.Redis.ConnectionFailureType failureType, string! message, System.Exception? innerException, StackExchange.Redis.CommandStatus commandStatus) -> void +StackExchange.Redis.RedisErrorEventArgs +StackExchange.Redis.RedisErrorEventArgs.EndPoint.get -> System.Net.EndPoint! +StackExchange.Redis.RedisErrorEventArgs.Message.get -> string! +StackExchange.Redis.RedisErrorEventArgs.RedisErrorEventArgs(object! sender, System.Net.EndPoint! endpoint, string! message) -> void +StackExchange.Redis.RedisException +StackExchange.Redis.RedisException.RedisException(string! message) -> void +StackExchange.Redis.RedisException.RedisException(string! message, System.Exception? innerException) -> void +StackExchange.Redis.RedisException.RedisException(System.Runtime.Serialization.SerializationInfo! info, System.Runtime.Serialization.StreamingContext ctx) -> void +StackExchange.Redis.RedisFeatures +StackExchange.Redis.RedisFeatures.BitwiseOperations.get -> bool +StackExchange.Redis.RedisFeatures.ClientName.get -> bool +StackExchange.Redis.RedisFeatures.ExecAbort.get -> bool +StackExchange.Redis.RedisFeatures.ExpireOverwrite.get -> bool +StackExchange.Redis.RedisFeatures.Geo.get -> bool +StackExchange.Redis.RedisFeatures.GetDelete.get -> bool +StackExchange.Redis.RedisFeatures.HashStringLength.get -> bool +StackExchange.Redis.RedisFeatures.HashVaradicDelete.get -> bool +StackExchange.Redis.RedisFeatures.HyperLogLogCountReplicaSafe.get -> bool +StackExchange.Redis.RedisFeatures.HyperLogLogCountSlaveSafe.get -> bool +StackExchange.Redis.RedisFeatures.IncrementFloat.get -> bool +StackExchange.Redis.RedisFeatures.InfoSections.get -> bool +StackExchange.Redis.RedisFeatures.KeyTouch.get -> bool +StackExchange.Redis.RedisFeatures.ListInsert.get -> bool +StackExchange.Redis.RedisFeatures.Memory.get -> bool +StackExchange.Redis.RedisFeatures.MillisecondExpiry.get -> bool +StackExchange.Redis.RedisFeatures.Module.get -> bool +StackExchange.Redis.RedisFeatures.MultipleRandom.get -> bool +StackExchange.Redis.RedisFeatures.Persist.get -> bool +StackExchange.Redis.RedisFeatures.PushIfNotExists.get -> bool +StackExchange.Redis.RedisFeatures.PushMultiple.get -> bool +StackExchange.Redis.RedisFeatures.RedisFeatures() -> void +StackExchange.Redis.RedisFeatures.RedisFeatures(System.Version! version) -> void +StackExchange.Redis.RedisFeatures.ReplicaCommands.get -> bool +StackExchange.Redis.RedisFeatures.Scan.get -> bool +StackExchange.Redis.RedisFeatures.Scripting.get -> bool +StackExchange.Redis.RedisFeatures.ScriptingDatabaseSafe.get -> bool +StackExchange.Redis.RedisFeatures.SetAndGet.get -> bool +StackExchange.Redis.RedisFeatures.SetConditional.get -> bool +StackExchange.Redis.RedisFeatures.SetKeepTtl.get -> bool +StackExchange.Redis.RedisFeatures.SetNotExistsAndGet.get -> bool +StackExchange.Redis.RedisFeatures.SetPopMultiple.get -> bool +StackExchange.Redis.RedisFeatures.SetVaradicAddRemove.get -> bool +StackExchange.Redis.RedisFeatures.SortedSetPop.get -> bool +StackExchange.Redis.RedisFeatures.SortedSetRangeStore.get -> bool +StackExchange.Redis.RedisFeatures.Streams.get -> bool +StackExchange.Redis.RedisFeatures.StringLength.get -> bool +StackExchange.Redis.RedisFeatures.StringSetRange.get -> bool +StackExchange.Redis.RedisFeatures.SwapDB.get -> bool +StackExchange.Redis.RedisFeatures.Time.get -> bool +StackExchange.Redis.RedisFeatures.Unlink.get -> bool +StackExchange.Redis.RedisFeatures.Version.get -> System.Version! +StackExchange.Redis.RedisKey +StackExchange.Redis.RedisKey.Append(StackExchange.Redis.RedisKey suffix) -> StackExchange.Redis.RedisKey +StackExchange.Redis.RedisKey.Equals(StackExchange.Redis.RedisKey other) -> bool +StackExchange.Redis.RedisKey.Prepend(StackExchange.Redis.RedisKey prefix) -> StackExchange.Redis.RedisKey +StackExchange.Redis.RedisKey.RedisKey() -> void +StackExchange.Redis.RedisKey.RedisKey(string? key) -> void +StackExchange.Redis.RedisResult +StackExchange.Redis.RedisResult.RedisResult() -> void +StackExchange.Redis.RedisResult.ToDictionary(System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.Dictionary! +StackExchange.Redis.RedisServerException +StackExchange.Redis.RedisServerException.RedisServerException(string! message) -> void +StackExchange.Redis.RedisStream +StackExchange.Redis.RedisStream.Entries.get -> StackExchange.Redis.StreamEntry[]! +StackExchange.Redis.RedisStream.Key.get -> StackExchange.Redis.RedisKey +StackExchange.Redis.RedisStream.RedisStream() -> void +StackExchange.Redis.RedisTimeoutException +StackExchange.Redis.RedisTimeoutException.Commandstatus.get -> StackExchange.Redis.CommandStatus +StackExchange.Redis.RedisTimeoutException.RedisTimeoutException(string! message, StackExchange.Redis.CommandStatus commandStatus) -> void +StackExchange.Redis.RedisType +StackExchange.Redis.RedisType.Hash = 5 -> StackExchange.Redis.RedisType +StackExchange.Redis.RedisType.List = 2 -> StackExchange.Redis.RedisType +StackExchange.Redis.RedisType.None = 0 -> StackExchange.Redis.RedisType +StackExchange.Redis.RedisType.Set = 3 -> StackExchange.Redis.RedisType +StackExchange.Redis.RedisType.SortedSet = 4 -> StackExchange.Redis.RedisType +StackExchange.Redis.RedisType.Stream = 6 -> StackExchange.Redis.RedisType +StackExchange.Redis.RedisType.String = 1 -> StackExchange.Redis.RedisType +StackExchange.Redis.RedisType.Unknown = 7 -> StackExchange.Redis.RedisType +StackExchange.Redis.RedisValue +StackExchange.Redis.RedisValue.Box() -> object? +StackExchange.Redis.RedisValue.CompareTo(StackExchange.Redis.RedisValue other) -> int +StackExchange.Redis.RedisValue.Equals(StackExchange.Redis.RedisValue other) -> bool +StackExchange.Redis.RedisValue.HasValue.get -> bool +StackExchange.Redis.RedisValue.IsInteger.get -> bool +StackExchange.Redis.RedisValue.IsNull.get -> bool +StackExchange.Redis.RedisValue.IsNullOrEmpty.get -> bool +StackExchange.Redis.RedisValue.Length() -> long +StackExchange.Redis.RedisValue.RedisValue() -> void +StackExchange.Redis.RedisValue.RedisValue(string! value) -> void +StackExchange.Redis.RedisValue.StartsWith(StackExchange.Redis.RedisValue value) -> bool +StackExchange.Redis.RedisValue.TryParse(out double val) -> bool +StackExchange.Redis.RedisValue.TryParse(out int val) -> bool +StackExchange.Redis.RedisValue.TryParse(out long val) -> bool +StackExchange.Redis.RedisValueWithExpiry +StackExchange.Redis.RedisValueWithExpiry.Expiry.get -> System.TimeSpan? +StackExchange.Redis.RedisValueWithExpiry.RedisValueWithExpiry() -> void +StackExchange.Redis.RedisValueWithExpiry.RedisValueWithExpiry(StackExchange.Redis.RedisValue value, System.TimeSpan? expiry) -> void +StackExchange.Redis.RedisValueWithExpiry.Value.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.ReplicationChangeOptions +StackExchange.Redis.ReplicationChangeOptions.All = StackExchange.Redis.ReplicationChangeOptions.SetTiebreaker | StackExchange.Redis.ReplicationChangeOptions.Broadcast | StackExchange.Redis.ReplicationChangeOptions.EnslaveSubordinates -> StackExchange.Redis.ReplicationChangeOptions +StackExchange.Redis.ReplicationChangeOptions.Broadcast = 2 -> StackExchange.Redis.ReplicationChangeOptions +StackExchange.Redis.ReplicationChangeOptions.EnslaveSubordinates = 4 -> StackExchange.Redis.ReplicationChangeOptions +StackExchange.Redis.ReplicationChangeOptions.None = 0 -> StackExchange.Redis.ReplicationChangeOptions +StackExchange.Redis.ReplicationChangeOptions.ReplicateToOtherEndpoints = 4 -> StackExchange.Redis.ReplicationChangeOptions +StackExchange.Redis.ReplicationChangeOptions.SetTiebreaker = 1 -> StackExchange.Redis.ReplicationChangeOptions +StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.BulkString = 4 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.Error = 2 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.Integer = 3 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.MultiBulk = 5 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.None = 0 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.SimpleString = 1 -> StackExchange.Redis.ResultType +StackExchange.Redis.RetransmissionReasonType +StackExchange.Redis.RetransmissionReasonType.Ask = 1 -> StackExchange.Redis.RetransmissionReasonType +StackExchange.Redis.RetransmissionReasonType.Moved = 2 -> StackExchange.Redis.RetransmissionReasonType +StackExchange.Redis.RetransmissionReasonType.None = 0 -> StackExchange.Redis.RetransmissionReasonType +StackExchange.Redis.Role +StackExchange.Redis.Role.Master +StackExchange.Redis.Role.Master.Replica +StackExchange.Redis.Role.Master.Replica.Ip.get -> string! +StackExchange.Redis.Role.Master.Replica.Port.get -> int +StackExchange.Redis.Role.Master.Replica.Replica() -> void +StackExchange.Redis.Role.Master.Replica.ReplicationOffset.get -> long +StackExchange.Redis.Role.Master.Replicas.get -> System.Collections.Generic.ICollection! +StackExchange.Redis.Role.Master.ReplicationOffset.get -> long +StackExchange.Redis.Role.Replica +StackExchange.Redis.Role.Replica.MasterIp.get -> string! +StackExchange.Redis.Role.Replica.MasterPort.get -> int +StackExchange.Redis.Role.Replica.ReplicationOffset.get -> long +StackExchange.Redis.Role.Replica.State.get -> string! +StackExchange.Redis.Role.Sentinel +StackExchange.Redis.Role.Sentinel.MonitoredMasters.get -> System.Collections.Generic.ICollection! +StackExchange.Redis.Role.Unknown +StackExchange.Redis.Role.Value.get -> string! +StackExchange.Redis.SaveType +StackExchange.Redis.SaveType.BackgroundRewriteAppendOnlyFile = 0 -> StackExchange.Redis.SaveType +StackExchange.Redis.SaveType.BackgroundSave = 1 -> StackExchange.Redis.SaveType +StackExchange.Redis.SaveType.ForegroundSave = 2 -> StackExchange.Redis.SaveType +StackExchange.Redis.ServerCounters +StackExchange.Redis.ServerCounters.EndPoint.get -> System.Net.EndPoint? +StackExchange.Redis.ServerCounters.Interactive.get -> StackExchange.Redis.ConnectionCounters! +StackExchange.Redis.ServerCounters.Other.get -> StackExchange.Redis.ConnectionCounters! +StackExchange.Redis.ServerCounters.ServerCounters(System.Net.EndPoint? endpoint) -> void +StackExchange.Redis.ServerCounters.Subscription.get -> StackExchange.Redis.ConnectionCounters! +StackExchange.Redis.ServerCounters.TotalOutstanding.get -> long +StackExchange.Redis.ServerType +StackExchange.Redis.ServerType.Cluster = 2 -> StackExchange.Redis.ServerType +StackExchange.Redis.ServerType.Envoyproxy = 4 -> StackExchange.Redis.ServerType +StackExchange.Redis.ServerType.Sentinel = 1 -> StackExchange.Redis.ServerType +StackExchange.Redis.ServerType.Standalone = 0 -> StackExchange.Redis.ServerType +StackExchange.Redis.ServerType.Twemproxy = 3 -> StackExchange.Redis.ServerType +StackExchange.Redis.SetOperation +StackExchange.Redis.SetOperation.Difference = 2 -> StackExchange.Redis.SetOperation +StackExchange.Redis.SetOperation.Intersect = 1 -> StackExchange.Redis.SetOperation +StackExchange.Redis.SetOperation.Union = 0 -> StackExchange.Redis.SetOperation +StackExchange.Redis.ShutdownMode +StackExchange.Redis.ShutdownMode.Always = 2 -> StackExchange.Redis.ShutdownMode +StackExchange.Redis.ShutdownMode.Default = 0 -> StackExchange.Redis.ShutdownMode +StackExchange.Redis.ShutdownMode.Never = 1 -> StackExchange.Redis.ShutdownMode +StackExchange.Redis.SlotRange +StackExchange.Redis.SlotRange.CompareTo(StackExchange.Redis.SlotRange other) -> int +StackExchange.Redis.SlotRange.Equals(StackExchange.Redis.SlotRange other) -> bool +StackExchange.Redis.SlotRange.From.get -> int +StackExchange.Redis.SlotRange.SlotRange() -> void +StackExchange.Redis.SlotRange.SlotRange(int from, int to) -> void +StackExchange.Redis.SlotRange.To.get -> int +StackExchange.Redis.SocketManager +StackExchange.Redis.SocketManager.Dispose() -> void +StackExchange.Redis.SocketManager.Name.get -> string! +StackExchange.Redis.SocketManager.SocketManager(string? name = null, int workerCount = 0, StackExchange.Redis.SocketManager.SocketManagerOptions options = StackExchange.Redis.SocketManager.SocketManagerOptions.None) -> void +StackExchange.Redis.SocketManager.SocketManager(string! name) -> void +StackExchange.Redis.SocketManager.SocketManager(string! name, bool useHighPrioritySocketThreads) -> void +StackExchange.Redis.SocketManager.SocketManager(string! name, int workerCount, bool useHighPrioritySocketThreads) -> void +StackExchange.Redis.SocketManager.SocketManagerOptions +StackExchange.Redis.SocketManager.SocketManagerOptions.None = 0 -> StackExchange.Redis.SocketManager.SocketManagerOptions +StackExchange.Redis.SocketManager.SocketManagerOptions.UseHighPrioritySocketThreads = 1 -> StackExchange.Redis.SocketManager.SocketManagerOptions +StackExchange.Redis.SocketManager.SocketManagerOptions.UseThreadPool = 2 -> StackExchange.Redis.SocketManager.SocketManagerOptions +StackExchange.Redis.SortedSetEntry +StackExchange.Redis.SortedSetEntry.CompareTo(object? obj) -> int +StackExchange.Redis.SortedSetEntry.CompareTo(StackExchange.Redis.SortedSetEntry other) -> int +StackExchange.Redis.SortedSetEntry.Element.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.SortedSetEntry.Equals(StackExchange.Redis.SortedSetEntry other) -> bool +StackExchange.Redis.SortedSetEntry.Key.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.SortedSetEntry.Score.get -> double +StackExchange.Redis.SortedSetEntry.SortedSetEntry() -> void +StackExchange.Redis.SortedSetEntry.SortedSetEntry(StackExchange.Redis.RedisValue element, double score) -> void +StackExchange.Redis.SortedSetEntry.Value.get -> double +StackExchange.Redis.SortedSetOrder +StackExchange.Redis.SortedSetOrder.ByLex = 2 -> StackExchange.Redis.SortedSetOrder +StackExchange.Redis.SortedSetOrder.ByRank = 0 -> StackExchange.Redis.SortedSetOrder +StackExchange.Redis.SortedSetOrder.ByScore = 1 -> StackExchange.Redis.SortedSetOrder +StackExchange.Redis.SortedSetPopResult +StackExchange.Redis.SortedSetPopResult.Entries.get -> StackExchange.Redis.SortedSetEntry[]! +StackExchange.Redis.SortedSetPopResult.IsNull.get -> bool +StackExchange.Redis.SortedSetPopResult.Key.get -> StackExchange.Redis.RedisKey +StackExchange.Redis.SortedSetPopResult.SortedSetPopResult() -> void +StackExchange.Redis.SortType +StackExchange.Redis.SortType.Alphabetic = 1 -> StackExchange.Redis.SortType +StackExchange.Redis.SortType.Numeric = 0 -> StackExchange.Redis.SortType +StackExchange.Redis.StreamAutoClaimIdsOnlyResult +StackExchange.Redis.StreamAutoClaimIdsOnlyResult.ClaimedIds.get -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.StreamAutoClaimIdsOnlyResult.DeletedIds.get -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.StreamAutoClaimIdsOnlyResult.IsNull.get -> bool +StackExchange.Redis.StreamAutoClaimIdsOnlyResult.NextStartId.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamAutoClaimIdsOnlyResult.StreamAutoClaimIdsOnlyResult() -> void +StackExchange.Redis.StreamAutoClaimResult +StackExchange.Redis.StreamAutoClaimResult.ClaimedEntries.get -> StackExchange.Redis.StreamEntry[]! +StackExchange.Redis.StreamAutoClaimResult.DeletedIds.get -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.StreamAutoClaimResult.IsNull.get -> bool +StackExchange.Redis.StreamAutoClaimResult.NextStartId.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamAutoClaimResult.StreamAutoClaimResult() -> void +StackExchange.Redis.StreamConsumer +StackExchange.Redis.StreamConsumer.Name.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamConsumer.PendingMessageCount.get -> int +StackExchange.Redis.StreamConsumer.StreamConsumer() -> void +StackExchange.Redis.StreamConsumerInfo +StackExchange.Redis.StreamConsumerInfo.IdleTimeInMilliseconds.get -> long +StackExchange.Redis.StreamConsumerInfo.Name.get -> string! +StackExchange.Redis.StreamConsumerInfo.PendingMessageCount.get -> int +StackExchange.Redis.StreamConsumerInfo.StreamConsumerInfo() -> void +StackExchange.Redis.StreamEntry +StackExchange.Redis.StreamEntry.Id.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamEntry.IsNull.get -> bool +StackExchange.Redis.StreamEntry.StreamEntry() -> void +StackExchange.Redis.StreamEntry.StreamEntry(StackExchange.Redis.RedisValue id, StackExchange.Redis.NameValueEntry[]! values) -> void +StackExchange.Redis.StreamEntry.this[StackExchange.Redis.RedisValue fieldName].get -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamEntry.Values.get -> StackExchange.Redis.NameValueEntry[]! +StackExchange.Redis.StreamGroupInfo +StackExchange.Redis.StreamGroupInfo.ConsumerCount.get -> int +StackExchange.Redis.StreamGroupInfo.EntriesRead.get -> long? +StackExchange.Redis.StreamGroupInfo.Lag.get -> long? +StackExchange.Redis.StreamGroupInfo.LastDeliveredId.get -> string? +StackExchange.Redis.StreamGroupInfo.Name.get -> string! +StackExchange.Redis.StreamGroupInfo.PendingMessageCount.get -> int +StackExchange.Redis.StreamGroupInfo.StreamGroupInfo() -> void +StackExchange.Redis.StreamInfo +StackExchange.Redis.StreamInfo.ConsumerGroupCount.get -> int +StackExchange.Redis.StreamInfo.FirstEntry.get -> StackExchange.Redis.StreamEntry +StackExchange.Redis.StreamInfo.LastEntry.get -> StackExchange.Redis.StreamEntry +StackExchange.Redis.StreamInfo.LastGeneratedId.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamInfo.Length.get -> int +StackExchange.Redis.StreamInfo.RadixTreeKeys.get -> int +StackExchange.Redis.StreamInfo.RadixTreeNodes.get -> int +StackExchange.Redis.StreamInfo.StreamInfo() -> void +StackExchange.Redis.StreamPendingInfo +StackExchange.Redis.StreamPendingInfo.Consumers.get -> StackExchange.Redis.StreamConsumer[]! +StackExchange.Redis.StreamPendingInfo.HighestPendingMessageId.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamPendingInfo.LowestPendingMessageId.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamPendingInfo.PendingMessageCount.get -> int +StackExchange.Redis.StreamPendingInfo.StreamPendingInfo() -> void +StackExchange.Redis.StreamPendingMessageInfo +StackExchange.Redis.StreamPendingMessageInfo.ConsumerName.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamPendingMessageInfo.DeliveryCount.get -> int +StackExchange.Redis.StreamPendingMessageInfo.IdleTimeInMilliseconds.get -> long +StackExchange.Redis.StreamPendingMessageInfo.MessageId.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamPendingMessageInfo.StreamPendingMessageInfo() -> void +StackExchange.Redis.StreamPosition +StackExchange.Redis.StreamPosition.Key.get -> StackExchange.Redis.RedisKey +StackExchange.Redis.StreamPosition.Position.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.StreamPosition.StreamPosition() -> void +StackExchange.Redis.StreamPosition.StreamPosition(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue position) -> void +StackExchange.Redis.StringIndexType +StackExchange.Redis.StringIndexType.Byte = 0 -> StackExchange.Redis.StringIndexType +StackExchange.Redis.StringIndexType.Bit = 1 -> StackExchange.Redis.StringIndexType +StackExchange.Redis.SortedSetWhen +StackExchange.Redis.SortedSetWhen.Always = 0 -> StackExchange.Redis.SortedSetWhen +StackExchange.Redis.SortedSetWhen.Exists = 1 -> StackExchange.Redis.SortedSetWhen +StackExchange.Redis.SortedSetWhen.GreaterThan = 2 -> StackExchange.Redis.SortedSetWhen +StackExchange.Redis.SortedSetWhen.LessThan = 4 -> StackExchange.Redis.SortedSetWhen +StackExchange.Redis.SortedSetWhen.NotExists = 8 -> StackExchange.Redis.SortedSetWhen +StackExchange.Redis.When +StackExchange.Redis.When.Always = 0 -> StackExchange.Redis.When +StackExchange.Redis.When.Exists = 1 -> StackExchange.Redis.When +StackExchange.Redis.When.NotExists = 2 -> StackExchange.Redis.When +static StackExchange.Redis.BacklogPolicy.Default.get -> StackExchange.Redis.BacklogPolicy! +static StackExchange.Redis.BacklogPolicy.FailFast.get -> StackExchange.Redis.BacklogPolicy! +static StackExchange.Redis.ChannelMessage.operator !=(StackExchange.Redis.ChannelMessage left, StackExchange.Redis.ChannelMessage right) -> bool +static StackExchange.Redis.ChannelMessage.operator ==(StackExchange.Redis.ChannelMessage left, StackExchange.Redis.ChannelMessage right) -> bool +static StackExchange.Redis.CommandMap.Create(System.Collections.Generic.Dictionary? overrides) -> StackExchange.Redis.CommandMap! +static StackExchange.Redis.CommandMap.Create(System.Collections.Generic.HashSet! commands, bool available = true) -> StackExchange.Redis.CommandMap! +static StackExchange.Redis.CommandMap.Default.get -> StackExchange.Redis.CommandMap! +static StackExchange.Redis.CommandMap.Envoyproxy.get -> StackExchange.Redis.CommandMap! +static StackExchange.Redis.CommandMap.Sentinel.get -> StackExchange.Redis.CommandMap! +static StackExchange.Redis.CommandMap.SSDB.get -> StackExchange.Redis.CommandMap! +static StackExchange.Redis.CommandMap.Twemproxy.get -> StackExchange.Redis.CommandMap! +static StackExchange.Redis.Condition.HashEqual(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.RedisValue value) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.HashExists(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.HashLengthEqual(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.HashLengthGreaterThan(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.HashLengthLessThan(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.HashNotEqual(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.RedisValue value) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.HashNotExists(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.KeyExists(StackExchange.Redis.RedisKey key) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.KeyNotExists(StackExchange.Redis.RedisKey key) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.ListIndexEqual(StackExchange.Redis.RedisKey key, long index, StackExchange.Redis.RedisValue value) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.ListIndexExists(StackExchange.Redis.RedisKey key, long index) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.ListIndexNotEqual(StackExchange.Redis.RedisKey key, long index, StackExchange.Redis.RedisValue value) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.ListIndexNotExists(StackExchange.Redis.RedisKey key, long index) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.ListLengthEqual(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.ListLengthGreaterThan(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.ListLengthLessThan(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SetContains(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SetLengthEqual(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SetLengthGreaterThan(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SetLengthLessThan(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SetNotContains(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetContains(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetEqual(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.RedisValue score) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetLengthEqual(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetLengthEqual(StackExchange.Redis.RedisKey key, long length, double min = -Infinity, double max = Infinity) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetLengthGreaterThan(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetLengthGreaterThan(StackExchange.Redis.RedisKey key, long length, double min = -Infinity, double max = Infinity) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetLengthLessThan(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetLengthLessThan(StackExchange.Redis.RedisKey key, long length, double min = -Infinity, double max = Infinity) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetNotContains(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetNotEqual(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.RedisValue score) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetScoreExists(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue score) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetScoreExists(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue score, StackExchange.Redis.RedisValue count) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetScoreNotExists(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue score) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetScoreNotExists(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue score, StackExchange.Redis.RedisValue count) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.StreamLengthEqual(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.StreamLengthGreaterThan(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.StreamLengthLessThan(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.StringEqual(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.StringLengthEqual(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.StringLengthGreaterThan(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.StringLengthLessThan(StackExchange.Redis.RedisKey key, long length) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.StringNotEqual(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Configuration.DefaultOptionsProvider.AddProvider(StackExchange.Redis.Configuration.DefaultOptionsProvider! provider) -> void +static StackExchange.Redis.Configuration.DefaultOptionsProvider.ComputerName.get -> string! +static StackExchange.Redis.Configuration.DefaultOptionsProvider.GetProvider(StackExchange.Redis.EndPointCollection! endpoints) -> StackExchange.Redis.Configuration.DefaultOptionsProvider! +static StackExchange.Redis.Configuration.DefaultOptionsProvider.GetProvider(System.Net.EndPoint! endpoint) -> StackExchange.Redis.Configuration.DefaultOptionsProvider! +static StackExchange.Redis.Configuration.DefaultOptionsProvider.LibraryVersion.get -> string! +static StackExchange.Redis.ConfigurationOptions.Parse(string! configuration) -> StackExchange.Redis.ConfigurationOptions! +static StackExchange.Redis.ConfigurationOptions.Parse(string! configuration, bool ignoreUnknown) -> StackExchange.Redis.ConfigurationOptions! +static StackExchange.Redis.ConnectionMultiplexer.Connect(StackExchange.Redis.ConfigurationOptions! configuration, System.IO.TextWriter? log = null) -> StackExchange.Redis.ConnectionMultiplexer! +static StackExchange.Redis.ConnectionMultiplexer.Connect(string! configuration, System.Action! configure, System.IO.TextWriter? log = null) -> StackExchange.Redis.ConnectionMultiplexer! +static StackExchange.Redis.ConnectionMultiplexer.Connect(string! configuration, System.IO.TextWriter? log = null) -> StackExchange.Redis.ConnectionMultiplexer! +static StackExchange.Redis.ConnectionMultiplexer.ConnectAsync(StackExchange.Redis.ConfigurationOptions! configuration, System.IO.TextWriter? log = null) -> System.Threading.Tasks.Task! +static StackExchange.Redis.ConnectionMultiplexer.ConnectAsync(string! configuration, System.Action! configure, System.IO.TextWriter? log = null) -> System.Threading.Tasks.Task! +static StackExchange.Redis.ConnectionMultiplexer.ConnectAsync(string! configuration, System.IO.TextWriter? log = null) -> System.Threading.Tasks.Task! +static StackExchange.Redis.ConnectionMultiplexer.Factory.get -> System.Threading.Tasks.TaskFactory! +static StackExchange.Redis.ConnectionMultiplexer.Factory.set -> void +static StackExchange.Redis.ConnectionMultiplexer.GetFeatureFlag(string! flag) -> bool +static StackExchange.Redis.ConnectionMultiplexer.SentinelConnect(StackExchange.Redis.ConfigurationOptions! configuration, System.IO.TextWriter? log = null) -> StackExchange.Redis.ConnectionMultiplexer! +static StackExchange.Redis.ConnectionMultiplexer.SentinelConnect(string! configuration, System.IO.TextWriter? log = null) -> StackExchange.Redis.ConnectionMultiplexer! +static StackExchange.Redis.ConnectionMultiplexer.SentinelConnectAsync(StackExchange.Redis.ConfigurationOptions! configuration, System.IO.TextWriter? log = null) -> System.Threading.Tasks.Task! +static StackExchange.Redis.ConnectionMultiplexer.SentinelConnectAsync(string! configuration, System.IO.TextWriter? log = null) -> System.Threading.Tasks.Task! +static StackExchange.Redis.ConnectionMultiplexer.SetFeatureFlag(string! flag, bool enabled) -> void +static StackExchange.Redis.EndPointCollection.ToString(System.Net.EndPoint? endpoint) -> string! +static StackExchange.Redis.EndPointCollection.TryParse(string! endpoint) -> System.Net.EndPoint? +static StackExchange.Redis.ExtensionMethods.AsStream(this StackExchange.Redis.Lease? bytes, bool ownsLease = true) -> System.IO.Stream? +static StackExchange.Redis.ExtensionMethods.DecodeLease(this StackExchange.Redis.Lease? bytes, System.Text.Encoding? encoding = null) -> StackExchange.Redis.Lease? +static StackExchange.Redis.ExtensionMethods.DecodeString(this StackExchange.Redis.Lease! bytes, System.Text.Encoding? encoding = null) -> string? +static StackExchange.Redis.ExtensionMethods.ToDictionary(this StackExchange.Redis.HashEntry[]? hash) -> System.Collections.Generic.Dictionary? +static StackExchange.Redis.ExtensionMethods.ToDictionary(this StackExchange.Redis.SortedSetEntry[]? sortedSet) -> System.Collections.Generic.Dictionary? +static StackExchange.Redis.ExtensionMethods.ToDictionary(this System.Collections.Generic.KeyValuePair[]? pairs) -> System.Collections.Generic.Dictionary? +static StackExchange.Redis.ExtensionMethods.ToDictionary(this System.Collections.Generic.KeyValuePair[]? pairs) -> System.Collections.Generic.Dictionary? +static StackExchange.Redis.ExtensionMethods.ToRedisValueArray(this string![]? values) -> StackExchange.Redis.RedisValue[]? +static StackExchange.Redis.ExtensionMethods.ToStringArray(this StackExchange.Redis.RedisValue[]? values) -> string?[]? +static StackExchange.Redis.ExtensionMethods.ToStringDictionary(this StackExchange.Redis.HashEntry[]? hash) -> System.Collections.Generic.Dictionary? +static StackExchange.Redis.ExtensionMethods.ToStringDictionary(this StackExchange.Redis.SortedSetEntry[]? sortedSet) -> System.Collections.Generic.Dictionary? +static StackExchange.Redis.ExtensionMethods.ToStringDictionary(this System.Collections.Generic.KeyValuePair[]? pairs) -> System.Collections.Generic.Dictionary? +static StackExchange.Redis.GeoEntry.operator !=(StackExchange.Redis.GeoEntry x, StackExchange.Redis.GeoEntry y) -> bool +static StackExchange.Redis.GeoEntry.operator ==(StackExchange.Redis.GeoEntry x, StackExchange.Redis.GeoEntry y) -> bool +static StackExchange.Redis.GeoPosition.operator !=(StackExchange.Redis.GeoPosition x, StackExchange.Redis.GeoPosition y) -> bool +static StackExchange.Redis.GeoPosition.operator ==(StackExchange.Redis.GeoPosition x, StackExchange.Redis.GeoPosition y) -> bool +static StackExchange.Redis.HashEntry.implicit operator StackExchange.Redis.HashEntry(System.Collections.Generic.KeyValuePair value) -> StackExchange.Redis.HashEntry +static StackExchange.Redis.HashEntry.implicit operator System.Collections.Generic.KeyValuePair(StackExchange.Redis.HashEntry value) -> System.Collections.Generic.KeyValuePair +static StackExchange.Redis.HashEntry.operator !=(StackExchange.Redis.HashEntry x, StackExchange.Redis.HashEntry y) -> bool +static StackExchange.Redis.HashEntry.operator ==(StackExchange.Redis.HashEntry x, StackExchange.Redis.HashEntry y) -> bool +static StackExchange.Redis.KeyspaceIsolation.DatabaseExtensions.WithKeyPrefix(this StackExchange.Redis.IDatabase! database, StackExchange.Redis.RedisKey keyPrefix) -> StackExchange.Redis.IDatabase! +static StackExchange.Redis.Lease.Create(int length, bool clear = true) -> StackExchange.Redis.Lease! +static StackExchange.Redis.Lease.Empty.get -> StackExchange.Redis.Lease! +static StackExchange.Redis.ListPopResult.Null.get -> StackExchange.Redis.ListPopResult +static StackExchange.Redis.LuaScript.GetCachedScriptCount() -> int +static StackExchange.Redis.LuaScript.Prepare(string! script) -> StackExchange.Redis.LuaScript! +static StackExchange.Redis.LuaScript.PurgeCache() -> void +static StackExchange.Redis.NameValueEntry.implicit operator StackExchange.Redis.NameValueEntry(System.Collections.Generic.KeyValuePair value) -> StackExchange.Redis.NameValueEntry +static StackExchange.Redis.NameValueEntry.implicit operator System.Collections.Generic.KeyValuePair(StackExchange.Redis.NameValueEntry value) -> System.Collections.Generic.KeyValuePair +static StackExchange.Redis.NameValueEntry.operator !=(StackExchange.Redis.NameValueEntry x, StackExchange.Redis.NameValueEntry y) -> bool +static StackExchange.Redis.NameValueEntry.operator ==(StackExchange.Redis.NameValueEntry x, StackExchange.Redis.NameValueEntry y) -> bool +static StackExchange.Redis.RedisChannel.implicit operator byte[]?(StackExchange.Redis.RedisChannel key) -> byte[]? +static StackExchange.Redis.RedisChannel.implicit operator StackExchange.Redis.RedisChannel(byte[]? key) -> StackExchange.Redis.RedisChannel +static StackExchange.Redis.RedisChannel.implicit operator StackExchange.Redis.RedisChannel(string! key) -> StackExchange.Redis.RedisChannel +static StackExchange.Redis.RedisChannel.implicit operator string?(StackExchange.Redis.RedisChannel key) -> string? +static StackExchange.Redis.RedisChannel.Literal(byte[]! value) -> StackExchange.Redis.RedisChannel +static StackExchange.Redis.RedisChannel.Literal(string! value) -> StackExchange.Redis.RedisChannel +static StackExchange.Redis.RedisChannel.operator !=(byte[]! x, StackExchange.Redis.RedisChannel y) -> bool +static StackExchange.Redis.RedisChannel.operator !=(StackExchange.Redis.RedisChannel x, byte[]! y) -> bool +static StackExchange.Redis.RedisChannel.operator !=(StackExchange.Redis.RedisChannel x, StackExchange.Redis.RedisChannel y) -> bool +static StackExchange.Redis.RedisChannel.operator !=(StackExchange.Redis.RedisChannel x, string! y) -> bool +static StackExchange.Redis.RedisChannel.operator !=(string! x, StackExchange.Redis.RedisChannel y) -> bool +static StackExchange.Redis.RedisChannel.operator ==(byte[]! x, StackExchange.Redis.RedisChannel y) -> bool +static StackExchange.Redis.RedisChannel.operator ==(StackExchange.Redis.RedisChannel x, byte[]! y) -> bool +static StackExchange.Redis.RedisChannel.operator ==(StackExchange.Redis.RedisChannel x, StackExchange.Redis.RedisChannel y) -> bool +static StackExchange.Redis.RedisChannel.operator ==(StackExchange.Redis.RedisChannel x, string! y) -> bool +static StackExchange.Redis.RedisChannel.operator ==(string! x, StackExchange.Redis.RedisChannel y) -> bool +static StackExchange.Redis.RedisChannel.Pattern(byte[]! value) -> StackExchange.Redis.RedisChannel +static StackExchange.Redis.RedisChannel.Pattern(string! value) -> StackExchange.Redis.RedisChannel +static StackExchange.Redis.RedisChannel.UseImplicitAutoPattern.get -> bool +static StackExchange.Redis.RedisChannel.UseImplicitAutoPattern.set -> void +static StackExchange.Redis.RedisFeatures.operator !=(StackExchange.Redis.RedisFeatures left, StackExchange.Redis.RedisFeatures right) -> bool +static StackExchange.Redis.RedisFeatures.operator ==(StackExchange.Redis.RedisFeatures left, StackExchange.Redis.RedisFeatures right) -> bool +static StackExchange.Redis.RedisKey.implicit operator byte[]?(StackExchange.Redis.RedisKey key) -> byte[]? +static StackExchange.Redis.RedisKey.implicit operator StackExchange.Redis.RedisKey(byte[]? key) -> StackExchange.Redis.RedisKey +static StackExchange.Redis.RedisKey.implicit operator StackExchange.Redis.RedisKey(string? key) -> StackExchange.Redis.RedisKey +static StackExchange.Redis.RedisKey.implicit operator string?(StackExchange.Redis.RedisKey key) -> string? +static StackExchange.Redis.RedisKey.operator !=(byte[]! x, StackExchange.Redis.RedisKey y) -> bool +static StackExchange.Redis.RedisKey.operator !=(StackExchange.Redis.RedisKey x, byte[]! y) -> bool +static StackExchange.Redis.RedisKey.operator !=(StackExchange.Redis.RedisKey x, StackExchange.Redis.RedisKey y) -> bool +static StackExchange.Redis.RedisKey.operator !=(StackExchange.Redis.RedisKey x, string! y) -> bool +static StackExchange.Redis.RedisKey.operator !=(string! x, StackExchange.Redis.RedisKey y) -> bool +static StackExchange.Redis.RedisKey.operator +(StackExchange.Redis.RedisKey x, StackExchange.Redis.RedisKey y) -> StackExchange.Redis.RedisKey +static StackExchange.Redis.RedisKey.operator ==(byte[]! x, StackExchange.Redis.RedisKey y) -> bool +static StackExchange.Redis.RedisKey.operator ==(StackExchange.Redis.RedisKey x, byte[]! y) -> bool +static StackExchange.Redis.RedisKey.operator ==(StackExchange.Redis.RedisKey x, StackExchange.Redis.RedisKey y) -> bool +static StackExchange.Redis.RedisKey.operator ==(StackExchange.Redis.RedisKey x, string! y) -> bool +static StackExchange.Redis.RedisKey.operator ==(string! x, StackExchange.Redis.RedisKey y) -> bool +static StackExchange.Redis.RedisResult.Create(StackExchange.Redis.RedisChannel channel) -> StackExchange.Redis.RedisResult! +static StackExchange.Redis.RedisResult.Create(StackExchange.Redis.RedisKey key) -> StackExchange.Redis.RedisResult! +static StackExchange.Redis.RedisResult.Create(StackExchange.Redis.RedisResult![]! values) -> StackExchange.Redis.RedisResult! +static StackExchange.Redis.RedisResult.Create(StackExchange.Redis.RedisValue value, StackExchange.Redis.ResultType? resultType = null) -> StackExchange.Redis.RedisResult! +static StackExchange.Redis.RedisResult.Create(StackExchange.Redis.RedisValue[]! values) -> StackExchange.Redis.RedisResult! +static StackExchange.Redis.RedisResult.explicit operator bool(StackExchange.Redis.RedisResult! result) -> bool +static StackExchange.Redis.RedisResult.explicit operator bool?(StackExchange.Redis.RedisResult? result) -> bool? +static StackExchange.Redis.RedisResult.explicit operator bool[]?(StackExchange.Redis.RedisResult? result) -> bool[]? +static StackExchange.Redis.RedisResult.explicit operator byte[]?(StackExchange.Redis.RedisResult? result) -> byte[]? +static StackExchange.Redis.RedisResult.explicit operator byte[]?[]?(StackExchange.Redis.RedisResult? result) -> byte[]?[]? +static StackExchange.Redis.RedisResult.explicit operator double(StackExchange.Redis.RedisResult! result) -> double +static StackExchange.Redis.RedisResult.explicit operator double?(StackExchange.Redis.RedisResult? result) -> double? +static StackExchange.Redis.RedisResult.explicit operator double[]?(StackExchange.Redis.RedisResult? result) -> double[]? +static StackExchange.Redis.RedisResult.explicit operator int(StackExchange.Redis.RedisResult! result) -> int +static StackExchange.Redis.RedisResult.explicit operator int?(StackExchange.Redis.RedisResult? result) -> int? +static StackExchange.Redis.RedisResult.explicit operator int[]?(StackExchange.Redis.RedisResult? result) -> int[]? +static StackExchange.Redis.RedisResult.explicit operator long(StackExchange.Redis.RedisResult! result) -> long +static StackExchange.Redis.RedisResult.explicit operator long?(StackExchange.Redis.RedisResult? result) -> long? +static StackExchange.Redis.RedisResult.explicit operator long[]?(StackExchange.Redis.RedisResult? result) -> long[]? +static StackExchange.Redis.RedisResult.explicit operator StackExchange.Redis.RedisKey(StackExchange.Redis.RedisResult? result) -> StackExchange.Redis.RedisKey +static StackExchange.Redis.RedisResult.explicit operator StackExchange.Redis.RedisKey[]?(StackExchange.Redis.RedisResult? result) -> StackExchange.Redis.RedisKey[]? +static StackExchange.Redis.RedisResult.explicit operator StackExchange.Redis.RedisResult![]?(StackExchange.Redis.RedisResult? result) -> StackExchange.Redis.RedisResult![]? +static StackExchange.Redis.RedisResult.explicit operator StackExchange.Redis.RedisValue(StackExchange.Redis.RedisResult? result) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisResult.explicit operator StackExchange.Redis.RedisValue[]?(StackExchange.Redis.RedisResult? result) -> StackExchange.Redis.RedisValue[]? +static StackExchange.Redis.RedisResult.explicit operator string?(StackExchange.Redis.RedisResult? result) -> string? +static StackExchange.Redis.RedisResult.explicit operator string?[]?(StackExchange.Redis.RedisResult? result) -> string?[]? +static StackExchange.Redis.RedisResult.explicit operator ulong(StackExchange.Redis.RedisResult! result) -> ulong +static StackExchange.Redis.RedisResult.explicit operator ulong?(StackExchange.Redis.RedisResult? result) -> ulong? +static StackExchange.Redis.RedisResult.explicit operator ulong[]?(StackExchange.Redis.RedisResult? result) -> ulong[]? +static StackExchange.Redis.RedisValue.CreateFrom(System.IO.MemoryStream! stream) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.EmptyString.get -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.explicit operator bool(StackExchange.Redis.RedisValue value) -> bool +static StackExchange.Redis.RedisValue.explicit operator bool?(StackExchange.Redis.RedisValue value) -> bool? +static StackExchange.Redis.RedisValue.explicit operator decimal(StackExchange.Redis.RedisValue value) -> decimal +static StackExchange.Redis.RedisValue.explicit operator decimal?(StackExchange.Redis.RedisValue value) -> decimal? +static StackExchange.Redis.RedisValue.explicit operator double(StackExchange.Redis.RedisValue value) -> double +static StackExchange.Redis.RedisValue.explicit operator double?(StackExchange.Redis.RedisValue value) -> double? +static StackExchange.Redis.RedisValue.explicit operator float(StackExchange.Redis.RedisValue value) -> float +static StackExchange.Redis.RedisValue.explicit operator float?(StackExchange.Redis.RedisValue value) -> float? +static StackExchange.Redis.RedisValue.explicit operator int(StackExchange.Redis.RedisValue value) -> int +static StackExchange.Redis.RedisValue.explicit operator int?(StackExchange.Redis.RedisValue value) -> int? +static StackExchange.Redis.RedisValue.explicit operator long(StackExchange.Redis.RedisValue value) -> long +static StackExchange.Redis.RedisValue.explicit operator long?(StackExchange.Redis.RedisValue value) -> long? +static StackExchange.Redis.RedisValue.explicit operator uint(StackExchange.Redis.RedisValue value) -> uint +static StackExchange.Redis.RedisValue.explicit operator uint?(StackExchange.Redis.RedisValue value) -> uint? +static StackExchange.Redis.RedisValue.explicit operator ulong(StackExchange.Redis.RedisValue value) -> ulong +static StackExchange.Redis.RedisValue.explicit operator ulong?(StackExchange.Redis.RedisValue value) -> ulong? +static StackExchange.Redis.RedisValue.implicit operator byte[]?(StackExchange.Redis.RedisValue value) -> byte[]? +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(bool value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(bool? value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(byte[]? value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(double value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(double? value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(int value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(int? value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(long value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(long? value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(string? value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(System.Memory value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(System.ReadOnlyMemory value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(uint value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(uint? value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(ulong value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator StackExchange.Redis.RedisValue(ulong? value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.implicit operator string?(StackExchange.Redis.RedisValue value) -> string? +static StackExchange.Redis.RedisValue.implicit operator System.ReadOnlyMemory(StackExchange.Redis.RedisValue value) -> System.ReadOnlyMemory +static StackExchange.Redis.RedisValue.Null.get -> StackExchange.Redis.RedisValue +static StackExchange.Redis.RedisValue.operator !=(StackExchange.Redis.RedisValue x, StackExchange.Redis.RedisValue y) -> bool +static StackExchange.Redis.RedisValue.operator ==(StackExchange.Redis.RedisValue x, StackExchange.Redis.RedisValue y) -> bool +static StackExchange.Redis.RedisValue.Unbox(object? value) -> StackExchange.Redis.RedisValue +static StackExchange.Redis.SlotRange.operator !=(StackExchange.Redis.SlotRange x, StackExchange.Redis.SlotRange y) -> bool +static StackExchange.Redis.SlotRange.operator ==(StackExchange.Redis.SlotRange x, StackExchange.Redis.SlotRange y) -> bool +static StackExchange.Redis.SlotRange.TryParse(string! range, out StackExchange.Redis.SlotRange value) -> bool +static StackExchange.Redis.SocketManager.Shared.get -> StackExchange.Redis.SocketManager! +static StackExchange.Redis.SocketManager.ThreadPool.get -> StackExchange.Redis.SocketManager! +static StackExchange.Redis.SortedSetEntry.implicit operator StackExchange.Redis.SortedSetEntry(System.Collections.Generic.KeyValuePair value) -> StackExchange.Redis.SortedSetEntry +static StackExchange.Redis.SortedSetEntry.implicit operator System.Collections.Generic.KeyValuePair(StackExchange.Redis.SortedSetEntry value) -> System.Collections.Generic.KeyValuePair +static StackExchange.Redis.SortedSetEntry.operator !=(StackExchange.Redis.SortedSetEntry x, StackExchange.Redis.SortedSetEntry y) -> bool +static StackExchange.Redis.SortedSetEntry.operator ==(StackExchange.Redis.SortedSetEntry x, StackExchange.Redis.SortedSetEntry y) -> bool +static StackExchange.Redis.SortedSetPopResult.Null.get -> StackExchange.Redis.SortedSetPopResult +static StackExchange.Redis.StreamAutoClaimIdsOnlyResult.Null.get -> StackExchange.Redis.StreamAutoClaimIdsOnlyResult +static StackExchange.Redis.StreamAutoClaimResult.Null.get -> StackExchange.Redis.StreamAutoClaimResult +static StackExchange.Redis.StreamEntry.Null.get -> StackExchange.Redis.StreamEntry +static StackExchange.Redis.StreamPosition.Beginning.get -> StackExchange.Redis.RedisValue +static StackExchange.Redis.StreamPosition.NewMessages.get -> StackExchange.Redis.RedisValue +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.AbortOnConnectFail.get -> bool +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.AfterConnectAsync(StackExchange.Redis.ConnectionMultiplexer! multiplexer, System.Action! log) -> System.Threading.Tasks.Task! +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.AllowAdmin.get -> bool +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.BacklogPolicy.get -> StackExchange.Redis.BacklogPolicy! +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.CheckCertificateRevocation.get -> bool +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.CommandMap.get -> StackExchange.Redis.CommandMap? +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.ConfigCheckInterval.get -> System.TimeSpan +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.ConfigurationChannel.get -> string! +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.ConnectRetry.get -> int +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.ConnectTimeout.get -> System.TimeSpan? +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.DefaultVersion.get -> System.Version! +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.GetDefaultClientName() -> string! +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.GetDefaultSsl(StackExchange.Redis.EndPointCollection! endPoints) -> bool +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.GetSslHostFromEndpoints(StackExchange.Redis.EndPointCollection! endPoints) -> string? +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.HeartbeatConsistencyChecks.get -> bool +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.HeartbeatInterval.get -> System.TimeSpan +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.HighIntegrity.get -> bool +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.IncludeDetailInExceptions.get -> bool +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.IncludePerformanceCountersInExceptions.get -> bool +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.IsMatch(System.Net.EndPoint! endpoint) -> bool +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.KeepAliveInterval.get -> System.TimeSpan +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.LibraryName.get -> string! +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.LoggerFactory.get -> Microsoft.Extensions.Logging.ILoggerFactory? +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.Password.get -> string? +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.Proxy.get -> StackExchange.Redis.Proxy +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.ReconnectRetryPolicy.get -> StackExchange.Redis.IReconnectRetryPolicy? +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.ResolveDns.get -> bool +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.SetClientLibrary.get -> bool +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.SyncTimeout.get -> System.TimeSpan +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.TieBreaker.get -> string! +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.User.get -> string? +abstract StackExchange.Redis.RedisResult.ToString(out string? type) -> string? +override sealed StackExchange.Redis.RedisResult.ToString() -> string! +override StackExchange.Redis.Role.Master.Replica.ToString() -> string! +StackExchange.Redis.ClientInfo.Protocol.get -> StackExchange.Redis.RedisProtocol? +StackExchange.Redis.ConfigurationOptions.Protocol.get -> StackExchange.Redis.RedisProtocol? +StackExchange.Redis.ConfigurationOptions.Protocol.set -> void +StackExchange.Redis.IServer.Protocol.get -> StackExchange.Redis.RedisProtocol +StackExchange.Redis.RedisFeatures.ClientId.get -> bool +StackExchange.Redis.RedisFeatures.Equals(StackExchange.Redis.RedisFeatures other) -> bool +StackExchange.Redis.RedisFeatures.Resp3.get -> bool +StackExchange.Redis.RedisProtocol +StackExchange.Redis.RedisProtocol.Resp2 = 20000 -> StackExchange.Redis.RedisProtocol +StackExchange.Redis.RedisProtocol.Resp3 = 30000 -> StackExchange.Redis.RedisProtocol +StackExchange.Redis.RedisResult.Resp2Type.get -> StackExchange.Redis.ResultType +StackExchange.Redis.RedisResult.Resp3Type.get -> StackExchange.Redis.ResultType +StackExchange.Redis.RedisResult.Type.get -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.Array = 5 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.Attribute = 29 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.BigInteger = 17 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.BlobError = 10 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.Boolean = 11 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.Double = 9 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.Map = 13 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.Null = 8 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.Push = 37 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.Set = 21 -> StackExchange.Redis.ResultType +StackExchange.Redis.ResultType.VerbatimString = 12 -> StackExchange.Redis.ResultType +static StackExchange.Redis.RedisResult.Create(StackExchange.Redis.RedisResult![]! values, StackExchange.Redis.ResultType resultType) -> StackExchange.Redis.RedisResult! +static StackExchange.Redis.RedisResult.Create(StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.ResultType resultType) -> StackExchange.Redis.RedisResult! +virtual StackExchange.Redis.RedisResult.Length.get -> int +virtual StackExchange.Redis.RedisResult.this[int index].get -> StackExchange.Redis.RedisResult! +StackExchange.Redis.ConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) -> void +StackExchange.Redis.IConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) -> void +StackExchange.Redis.RedisFeatures.ShardedPubSub.get -> bool +static StackExchange.Redis.RedisChannel.Sharded(byte[]? value) -> StackExchange.Redis.RedisChannel +static StackExchange.Redis.RedisChannel.Sharded(string! value) -> StackExchange.Redis.RedisChannel +StackExchange.Redis.ClientInfo.ShardedSubscriptionCount.get -> int +StackExchange.Redis.ConfigurationOptions.SetUserPfxCertificate(string! userCertificatePath, string? password = null) -> void +StackExchange.Redis.Bitwise.AndOr = 6 -> StackExchange.Redis.Bitwise +StackExchange.Redis.Bitwise.Diff = 4 -> StackExchange.Redis.Bitwise +StackExchange.Redis.Bitwise.Diff1 = 5 -> StackExchange.Redis.Bitwise +StackExchange.Redis.Bitwise.One = 7 -> StackExchange.Redis.Bitwise +StackExchange.Redis.StreamTrimMode +StackExchange.Redis.StreamTrimMode.Acknowledged = 2 -> StackExchange.Redis.StreamTrimMode +StackExchange.Redis.StreamTrimMode.DeleteReferences = 1 -> StackExchange.Redis.StreamTrimMode +StackExchange.Redis.StreamTrimMode.KeepReferences = 0 -> StackExchange.Redis.StreamTrimMode +StackExchange.Redis.StreamTrimResult +StackExchange.Redis.StreamTrimResult.Deleted = 1 -> StackExchange.Redis.StreamTrimResult +StackExchange.Redis.StreamTrimResult.NotDeleted = 2 -> StackExchange.Redis.StreamTrimResult +StackExchange.Redis.StreamTrimResult.NotFound = -1 -> StackExchange.Redis.StreamTrimResult +StackExchange.Redis.IDatabase.HashFieldGetAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldGetAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashFieldGetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashFieldGetLeaseAndDelete(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +StackExchange.Redis.IDatabase.HashFieldGetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +StackExchange.Redis.IDatabase.HashFieldGetLeaseAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue field, StackExchange.Redis.RedisValue value, System.DateTime expiry, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue field, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.DateTime expiry, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabase.HashFieldSetAndSetExpiry(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndDeleteAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.DateTime expiry, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +StackExchange.Redis.IDatabaseAsync.HashFieldGetLeaseAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan? expiry = null, bool persist = false, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +StackExchange.Redis.IDatabaseAsync.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue field, StackExchange.Redis.RedisValue value, System.DateTime expiry, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue field, StackExchange.Redis.RedisValue value, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.DateTime expiry, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldSetAndSetExpiryAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.TimeSpan? expiry = null, bool keepTtl = false, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.RedisValue.CopyTo(System.Span destination) -> int +StackExchange.Redis.RedisValue.GetByteCount() -> int +StackExchange.Redis.RedisValue.GetLongByteCount() -> long +static StackExchange.Redis.Condition.SortedSetContainsStarting(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue prefix) -> StackExchange.Redis.Condition! +static StackExchange.Redis.Condition.SortedSetNotContainsStarting(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue prefix) -> StackExchange.Redis.Condition! +StackExchange.Redis.ConnectionMultiplexer.GetServer(StackExchange.Redis.RedisKey key, object? asyncState = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.IServer! +StackExchange.Redis.IConnectionMultiplexer.GetServer(StackExchange.Redis.RedisKey key, object? asyncState = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.IServer! +StackExchange.Redis.IServer.Execute(int? database, string! command, System.Collections.Generic.ICollection! args, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisResult! +StackExchange.Redis.IServer.ExecuteAsync(int? database, string! command, System.Collections.Generic.ICollection! args, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +[SER001]override StackExchange.Redis.VectorSetLink.ToString() -> string! +[SER001]override StackExchange.Redis.VectorSetSimilaritySearchResult.ToString() -> string! +[SER001]StackExchange.Redis.IDatabase.VectorSetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.VectorSetAddRequest! request, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.VectorSetAddRequest! request, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +[SER001]StackExchange.Redis.VectorSetAddRequest +[SER001]StackExchange.Redis.VectorSetAddRequest.BuildExplorationFactor.get -> int? +[SER001]StackExchange.Redis.VectorSetAddRequest.BuildExplorationFactor.set -> void +[SER001]StackExchange.Redis.VectorSetAddRequest.MaxConnections.get -> int? +[SER001]StackExchange.Redis.VectorSetAddRequest.MaxConnections.set -> void +[SER001]StackExchange.Redis.VectorSetAddRequest.Quantization.get -> StackExchange.Redis.VectorSetQuantization +[SER001]StackExchange.Redis.VectorSetAddRequest.Quantization.set -> void +[SER001]StackExchange.Redis.VectorSetAddRequest.ReducedDimensions.get -> int? +[SER001]StackExchange.Redis.VectorSetAddRequest.ReducedDimensions.set -> void +[SER001]StackExchange.Redis.VectorSetAddRequest.UseCheckAndSet.get -> bool +[SER001]StackExchange.Redis.VectorSetAddRequest.UseCheckAndSet.set -> void +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.Count.get -> int? +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.Count.set -> void +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.DisableThreading.get -> bool +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.DisableThreading.set -> void +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.Epsilon.get -> double? +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.Epsilon.set -> void +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.FilterExpression.get -> string? +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.FilterExpression.set -> void +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.MaxFilteringEffort.get -> int? +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.MaxFilteringEffort.set -> void +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.SearchExplorationFactor.get -> int? +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.SearchExplorationFactor.set -> void +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.UseExactSearch.get -> bool +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.UseExactSearch.set -> void +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.WithAttributes.get -> bool +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.WithAttributes.set -> void +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.WithScores.get -> bool +[SER001]StackExchange.Redis.VectorSetSimilaritySearchRequest.WithScores.set -> void +[SER001]StackExchange.Redis.IDatabase.VectorSetContains(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +[SER001]StackExchange.Redis.IDatabase.VectorSetDimension(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> int +[SER001]StackExchange.Redis.IDatabase.VectorSetGetApproximateVector(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +[SER001]StackExchange.Redis.IDatabase.VectorSetGetAttributesJson(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string? +[SER001]StackExchange.Redis.IDatabase.VectorSetGetLinks(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +[SER001]StackExchange.Redis.IDatabase.VectorSetGetLinksWithScores(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +[SER001]StackExchange.Redis.IDatabase.VectorSetInfo(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.VectorSetInfo? +[SER001]StackExchange.Redis.IDatabase.VectorSetLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long +[SER001]StackExchange.Redis.IDatabase.VectorSetRandomMember(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue +[SER001]StackExchange.Redis.IDatabase.VectorSetRandomMembers(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +[SER001]StackExchange.Redis.IDatabase.VectorSetRemove(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +[SER001]StackExchange.Redis.IDatabase.VectorSetSetAttributesJson(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, string! attributesJson, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +[SER001]StackExchange.Redis.IDatabase.VectorSetSimilaritySearch(StackExchange.Redis.RedisKey key, StackExchange.Redis.VectorSetSimilaritySearchRequest! query, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetContainsAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetDimensionAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetGetApproximateVectorAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetGetAttributesJsonAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetGetLinksAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetGetLinksWithScoresAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetInfoAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetRandomMemberAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetRandomMembersAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetRemoveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetSetAttributesJsonAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, string! attributesJson, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +[SER001]StackExchange.Redis.IDatabaseAsync.VectorSetSimilaritySearchAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.VectorSetSimilaritySearchRequest! query, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +[SER001]StackExchange.Redis.VectorSetInfo +[SER001]StackExchange.Redis.VectorSetInfo.Dimension.get -> int +[SER001]StackExchange.Redis.VectorSetInfo.HnswMaxNodeUid.get -> long +[SER001]StackExchange.Redis.VectorSetInfo.Length.get -> long +[SER001]StackExchange.Redis.VectorSetInfo.MaxLevel.get -> int +[SER001]StackExchange.Redis.VectorSetInfo.Quantization.get -> StackExchange.Redis.VectorSetQuantization +[SER001]StackExchange.Redis.VectorSetInfo.QuantizationRaw.get -> string? +[SER001]StackExchange.Redis.VectorSetInfo.VectorSetInfo() -> void +[SER001]StackExchange.Redis.VectorSetInfo.VectorSetInfo(StackExchange.Redis.VectorSetQuantization quantization, string? quantizationRaw, int dimension, long length, int maxLevel, long vectorSetUid, long hnswMaxNodeUid) -> void +[SER001]StackExchange.Redis.VectorSetInfo.VectorSetUid.get -> long +[SER001]StackExchange.Redis.VectorSetLink +[SER001]StackExchange.Redis.VectorSetLink.Member.get -> StackExchange.Redis.RedisValue +[SER001]StackExchange.Redis.VectorSetLink.Score.get -> double +[SER001]StackExchange.Redis.VectorSetLink.VectorSetLink() -> void +[SER001]StackExchange.Redis.VectorSetLink.VectorSetLink(StackExchange.Redis.RedisValue member, double score) -> void +[SER001]StackExchange.Redis.VectorSetQuantization +[SER001]StackExchange.Redis.VectorSetQuantization.Binary = 3 -> StackExchange.Redis.VectorSetQuantization +[SER001]StackExchange.Redis.VectorSetQuantization.Int8 = 2 -> StackExchange.Redis.VectorSetQuantization +[SER001]StackExchange.Redis.VectorSetQuantization.None = 1 -> StackExchange.Redis.VectorSetQuantization +[SER001]StackExchange.Redis.VectorSetQuantization.Unknown = 0 -> StackExchange.Redis.VectorSetQuantization +[SER001]StackExchange.Redis.VectorSetSimilaritySearchResult +[SER001]StackExchange.Redis.VectorSetSimilaritySearchResult.AttributesJson.get -> string? +[SER001]StackExchange.Redis.VectorSetSimilaritySearchResult.Member.get -> StackExchange.Redis.RedisValue +[SER001]StackExchange.Redis.VectorSetSimilaritySearchResult.Score.get -> double +[SER001]StackExchange.Redis.VectorSetSimilaritySearchResult.VectorSetSimilaritySearchResult() -> void +[SER001]StackExchange.Redis.VectorSetSimilaritySearchResult.VectorSetSimilaritySearchResult(StackExchange.Redis.RedisValue member, double score = NaN, string? attributesJson = null) -> void +[SER001]static StackExchange.Redis.VectorSetAddRequest.Member(StackExchange.Redis.RedisValue element, System.ReadOnlyMemory values, string? attributesJson = null) -> StackExchange.Redis.VectorSetAddRequest! +[SER001]static StackExchange.Redis.VectorSetSimilaritySearchRequest.ByMember(StackExchange.Redis.RedisValue member) -> StackExchange.Redis.VectorSetSimilaritySearchRequest! +[SER001]static StackExchange.Redis.VectorSetSimilaritySearchRequest.ByVector(System.ReadOnlyMemory vector) -> StackExchange.Redis.VectorSetSimilaritySearchRequest! +StackExchange.Redis.RedisChannel.WithKeyRouting() -> StackExchange.Redis.RedisChannel diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..ab058de62 --- /dev/null +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/StackExchange.Redis/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/net6.0/PublicAPI.Shipped.txt new file mode 100644 index 000000000..fae4f65ce --- /dev/null +++ b/src/StackExchange.Redis/PublicAPI/net6.0/PublicAPI.Shipped.txt @@ -0,0 +1,4 @@ +StackExchange.Redis.ConfigurationOptions.SslClientAuthenticationOptions.get -> System.Func? +StackExchange.Redis.ConfigurationOptions.SslClientAuthenticationOptions.set -> void +System.Runtime.CompilerServices.IsExternalInit (forwarded, contained in System.Runtime) +StackExchange.Redis.ConfigurationOptions.SetUserPemCertificate(string! userCertificatePath, string? userKeyPath = null) -> void \ No newline at end of file diff --git a/src/StackExchange.Redis/PublicAPI/net8.0/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/net8.0/PublicAPI.Shipped.txt new file mode 100644 index 000000000..fae4f65ce --- /dev/null +++ b/src/StackExchange.Redis/PublicAPI/net8.0/PublicAPI.Shipped.txt @@ -0,0 +1,4 @@ +StackExchange.Redis.ConfigurationOptions.SslClientAuthenticationOptions.get -> System.Func? +StackExchange.Redis.ConfigurationOptions.SslClientAuthenticationOptions.set -> void +System.Runtime.CompilerServices.IsExternalInit (forwarded, contained in System.Runtime) +StackExchange.Redis.ConfigurationOptions.SetUserPemCertificate(string! userCertificatePath, string? userKeyPath = null) -> void \ No newline at end of file diff --git a/src/StackExchange.Redis/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt new file mode 100644 index 000000000..194e1b51b --- /dev/null +++ b/src/StackExchange.Redis/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt @@ -0,0 +1,2 @@ +StackExchange.Redis.ConfigurationOptions.SslClientAuthenticationOptions.get -> System.Func? +StackExchange.Redis.ConfigurationOptions.SslClientAuthenticationOptions.set -> void \ No newline at end of file diff --git a/src/StackExchange.Redis/README.md b/src/StackExchange.Redis/README.md new file mode 100644 index 000000000..9cc0fe157 --- /dev/null +++ b/src/StackExchange.Redis/README.md @@ -0,0 +1,6 @@ +StackExchange.Redis is a high-performance RESP (Redis, etc) client for .NET, available under the MIT license. + +- Release notes: [https://stackexchange.github.io/StackExchange.Redis/ReleaseNotes](https://stackexchange.github.io/StackExchange.Redis/ReleaseNotes) +- NuGet package: [https://www.nuget.org/packages/StackExchange.Redis/](https://www.nuget.org/packages/StackExchange.Redis/) +- General docs: [https://stackexchange.github.io/StackExchange.Redis/](https://stackexchange.github.io/StackExchange.Redis/) +- Code: [https://github.com/StackExchange/StackExchange.Redis/](https://github.com/StackExchange/StackExchange.Redis/) \ No newline at end of file diff --git a/src/StackExchange.Redis/RawResult.cs b/src/StackExchange.Redis/RawResult.cs new file mode 100644 index 000000000..1ac9f081a --- /dev/null +++ b/src/StackExchange.Redis/RawResult.cs @@ -0,0 +1,513 @@ +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Text; +using Pipelines.Sockets.Unofficial.Arenas; + +namespace StackExchange.Redis +{ + internal readonly struct RawResult + { + internal ref RawResult this[int index] => ref GetItems()[index]; + + internal int ItemsCount => (int)_items.Length; + + private readonly ReadOnlySequence _payload; + internal ReadOnlySequence Payload => _payload; + + internal static readonly RawResult Nil = default; + // Note: can't use Memory here - struct recursion breaks runtime + private readonly Sequence _items; + private readonly ResultType _resultType; + private readonly ResultFlags _flags; + + [Flags] + internal enum ResultFlags + { + None = 0, + HasValue = 1 << 0, // simply indicates "not the default" (always set in .ctor) + NonNull = 1 << 1, // defines explicit null; isn't "IsNull" because we want default to be null + Resp3 = 1 << 2, // was the connection in RESP3 mode? + } + + public RawResult(ResultType resultType, in ReadOnlySequence payload, ResultFlags flags) + { + switch (resultType) + { + case ResultType.SimpleString: + case ResultType.Error: + case ResultType.Integer: + case ResultType.BulkString: + case ResultType.Double: + case ResultType.Boolean: + case ResultType.BlobError: + case ResultType.VerbatimString: + case ResultType.BigInteger: + break; + case ResultType.Null: + flags &= ~ResultFlags.NonNull; + break; + default: + ThrowInvalidType(resultType); + break; + } + _resultType = resultType; + _flags = flags | ResultFlags.HasValue; + _payload = payload; + _items = default; + } + + public RawResult(ResultType resultType, Sequence items, ResultFlags flags) + { + switch (resultType) + { + case ResultType.Array: + case ResultType.Map: + case ResultType.Set: + case ResultType.Attribute: + case ResultType.Push: + break; + case ResultType.Null: + flags &= ~ResultFlags.NonNull; + break; + default: + ThrowInvalidType(resultType); + break; + } + _resultType = resultType; + _flags = flags | ResultFlags.HasValue; + _payload = default; + _items = items.Untyped(); + } + + private static void ThrowInvalidType(ResultType resultType) + => throw new ArgumentOutOfRangeException(nameof(resultType), $"Invalid result-type: {resultType}"); + + public bool IsError => _resultType.IsError(); + + public ResultType Resp3Type => _resultType; + + // if null, assume string + public ResultType Resp2TypeBulkString => _resultType == ResultType.Null ? ResultType.BulkString : _resultType.ToResp2(); + // if null, assume array + public ResultType Resp2TypeArray => _resultType == ResultType.Null ? ResultType.Array : _resultType.ToResp2(); + + internal bool IsNull => (_flags & ResultFlags.NonNull) == 0; + + public bool HasValue => (_flags & ResultFlags.HasValue) != 0; + + public bool IsResp3 => (_flags & ResultFlags.Resp3) != 0; + + public override string ToString() + { + if (IsNull) return "(null)"; + + return _resultType.ToResp2() switch + { + ResultType.SimpleString or ResultType.Integer or ResultType.Error => $"{Resp3Type}: {GetString()}", + ResultType.BulkString => $"{Resp3Type}: {Payload.Length} bytes", + ResultType.Array => $"{Resp3Type}: {ItemsCount} items", + _ => $"(unknown: {Resp3Type})", + }; + } + + public Tokenizer GetInlineTokenizer() => new Tokenizer(Payload); + + internal ref struct Tokenizer + { + // tokenizes things according to the inline protocol + // specifically; the line: abc "def ghi" jkl + // is 3 tokens: "abc", "def ghi" and "jkl" + public Tokenizer GetEnumerator() => this; + private BufferReader _value; + + public Tokenizer(scoped in ReadOnlySequence value) + { + _value = new BufferReader(value); + Current = default; + } + + public bool MoveNext() + { + Current = default; + // take any white-space + while (_value.PeekByte() == (byte)' ') { _value.Consume(1); } + + byte terminator = (byte)' '; + var first = _value.PeekByte(); + if (first < 0) return false; // EOF + + switch (first) + { + case (byte)'"': + case (byte)'\'': + // start of string + terminator = (byte)first; + _value.Consume(1); + break; + } + + int end = BufferReader.FindNext(_value, terminator); + if (end < 0) + { + Current = _value.ConsumeToEnd(); + } + else + { + Current = _value.ConsumeAsBuffer(end); + _value.Consume(1); // drop the terminator itself; + } + return true; + } + public ReadOnlySequence Current { get; private set; } + } + internal RedisChannel AsRedisChannel(byte[]? channelPrefix, RedisChannel.RedisChannelOptions options) + { + switch (Resp2TypeBulkString) + { + case ResultType.SimpleString: + case ResultType.BulkString: + if (channelPrefix == null) + { + return new RedisChannel(GetBlob(), options); + } + if (StartsWith(channelPrefix)) + { + byte[] copy = Payload.Slice(channelPrefix.Length).ToArray(); + + return new RedisChannel(copy, options); + } + return default; + default: + throw new InvalidCastException("Cannot convert to RedisChannel: " + Resp3Type); + } + } + + internal RedisKey AsRedisKey() + { + return Resp2TypeBulkString switch + { + ResultType.SimpleString or ResultType.BulkString => (RedisKey)GetBlob(), + _ => throw new InvalidCastException("Cannot convert to RedisKey: " + Resp3Type), + }; + } + + internal RedisValue AsRedisValue() + { + if (IsNull) return RedisValue.Null; + if (Resp3Type == ResultType.Boolean && Payload.Length == 1) + { + switch (Payload.First.Span[0]) + { + case (byte)'t': return (RedisValue)true; + case (byte)'f': return (RedisValue)false; + } + } + switch (Resp2TypeBulkString) + { + case ResultType.Integer: + long i64; + if (TryGetInt64(out i64)) return (RedisValue)i64; + break; + case ResultType.SimpleString: + case ResultType.BulkString: + return (RedisValue)GetBlob(); + } + throw new InvalidCastException("Cannot convert to RedisValue: " + Resp3Type); + } + + internal Lease? AsLease() + { + if (IsNull) return null; + switch (Resp2TypeBulkString) + { + case ResultType.SimpleString: + case ResultType.BulkString: + var payload = Payload; + var lease = Lease.Create(checked((int)payload.Length), false); + payload.CopyTo(lease.Span); + return lease; + } + throw new InvalidCastException("Cannot convert to Lease: " + Resp3Type); + } + + internal bool IsEqual(in CommandBytes expected) + { + if (expected.Length != Payload.Length) return false; + return new CommandBytes(Payload).Equals(expected); + } + + internal bool IsEqual(byte[]? expected) + { + if (expected == null) throw new ArgumentNullException(nameof(expected)); + return IsEqual(new ReadOnlySpan(expected)); + } + + internal bool IsEqual(ReadOnlySpan expected) + { + var rangeToCheck = Payload; + + if (expected.Length != rangeToCheck.Length) return false; + if (rangeToCheck.IsSingleSegment) return rangeToCheck.First.Span.SequenceEqual(expected); + + int offset = 0; + foreach (var segment in rangeToCheck) + { + var from = segment.Span; + var to = expected.Slice(offset, from.Length); + if (!from.SequenceEqual(to)) return false; + + offset += from.Length; + } + return true; + } + + internal bool StartsWith(in CommandBytes expected) + { + var len = expected.Length; + if (len > Payload.Length) return false; + + var rangeToCheck = Payload.Slice(0, len); + return new CommandBytes(rangeToCheck).Equals(expected); + } + internal bool StartsWith(byte[] expected) + { + if (expected == null) throw new ArgumentNullException(nameof(expected)); + if (expected.Length > Payload.Length) return false; + + var rangeToCheck = Payload.Slice(0, expected.Length); + if (rangeToCheck.IsSingleSegment) return rangeToCheck.First.Span.SequenceEqual(expected); + + int offset = 0; + foreach (var segment in rangeToCheck) + { + var from = segment.Span; + var to = new Span(expected, offset, from.Length); + if (!from.SequenceEqual(to)) return false; + + offset += from.Length; + } + return true; + } + + internal byte[]? GetBlob() + { + if (IsNull) return null; + + if (Payload.IsEmpty) return Array.Empty(); + + return Payload.ToArray(); + } + + internal bool GetBoolean() + { + if (Payload.Length != 1) throw new InvalidCastException(); + if (Resp3Type == ResultType.Boolean) + { + return Payload.First.Span[0] switch + { + (byte)'t' => true, + (byte)'f' => false, + _ => throw new InvalidCastException(), + }; + } + return Payload.First.Span[0] switch + { + (byte)'1' => true, + (byte)'0' => false, + _ => throw new InvalidCastException(), + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Sequence GetItems() => _items.Cast(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal double?[]? GetItemsAsDoubles() => this.ToArray((in RawResult x) => x.TryGetDouble(out double val) ? val : null); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal RedisKey[]? GetItemsAsKeys() => this.ToArray((in RawResult x) => x.AsRedisKey()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal RedisValue[]? GetItemsAsValues() => this.ToArray((in RawResult x) => x.AsRedisValue()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal string?[]? GetItemsAsStrings() => this.ToArray((in RawResult x) => (string?)x.AsRedisValue()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal string[]? GetItemsAsStringsNotNullable() => this.ToArray((in RawResult x) => (string)x.AsRedisValue()!); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool[]? GetItemsAsBooleans() => this.ToArray((in RawResult x) => (bool)x.AsRedisValue()); + + internal GeoPosition? GetItemsAsGeoPosition() + { + var items = GetItems(); + if (IsNull || items.Length == 0) + { + return null; + } + + ref RawResult root = ref items[0]; + if (root.IsNull) + { + return null; + } + return AsGeoPosition(root.GetItems()); + } + + internal SortedSetEntry[]? GetItemsAsSortedSetEntryArray() => this.ToArray((in RawResult item) => AsSortedSetEntry(item.GetItems())); + + private static SortedSetEntry AsSortedSetEntry(in Sequence elements) + { + if (elements.IsSingleSegment) + { + var span = elements.FirstSpan; + return new SortedSetEntry(span[0].AsRedisValue(), span[1].TryGetDouble(out double val) ? val : double.NaN); + } + else + { + return new SortedSetEntry(elements[0].AsRedisValue(), elements[1].TryGetDouble(out double val) ? val : double.NaN); + } + } + + private static GeoPosition AsGeoPosition(in Sequence coords) + { + double longitude, latitude; + if (coords.IsSingleSegment) + { + var span = coords.FirstSpan; + longitude = (double)span[0].AsRedisValue(); + latitude = (double)span[1].AsRedisValue(); + } + else + { + longitude = (double)coords[0].AsRedisValue(); + latitude = (double)coords[1].AsRedisValue(); + } + + return new GeoPosition(longitude, latitude); + } + + internal GeoPosition?[]? GetItemsAsGeoPositionArray() + => this.ToArray((in RawResult item) => item.IsNull ? default : AsGeoPosition(item.GetItems())); + + internal unsafe string? GetString() => GetString(out _); + internal unsafe string? GetString(out ReadOnlySpan verbatimPrefix) + { + verbatimPrefix = default; + if (IsNull) return null; + if (Payload.IsEmpty) return ""; + + string s; + if (Payload.IsSingleSegment) + { + s = Format.GetString(Payload.First.Span); + return Resp3Type == ResultType.VerbatimString ? GetVerbatimString(s, out verbatimPrefix) : s; + } +#if NET6_0_OR_GREATER + // use system-provided sequence decoder + return Encoding.UTF8.GetString(in _payload); +#else + var decoder = Encoding.UTF8.GetDecoder(); + int charCount = 0; + foreach (var segment in Payload) + { + var span = segment.Span; + if (span.IsEmpty) continue; + + fixed (byte* bPtr = span) + { + charCount += decoder.GetCharCount(bPtr, span.Length, false); + } + } + + decoder.Reset(); + + s = new string((char)0, charCount); + fixed (char* sPtr = s) + { + char* cPtr = sPtr; + foreach (var segment in Payload) + { + var span = segment.Span; + if (span.IsEmpty) continue; + + fixed (byte* bPtr = span) + { + var written = decoder.GetChars(bPtr, span.Length, cPtr, charCount, false); + if (written < 0 || written > charCount) Throw(); // protect against hypothetical cPtr weirdness + cPtr += written; + charCount -= written; + } + } + } + + return Resp3Type == ResultType.VerbatimString ? GetVerbatimString(s, out verbatimPrefix) : s; + + static void Throw() => throw new InvalidOperationException("Invalid result from GetChars"); +#endif + static string? GetVerbatimString(string? value, out ReadOnlySpan type) + { + // The first three bytes provide information about the format of the following string, which + // can be txt for plain text, or mkd for markdown. The fourth byte is always `:`. + // Then the real string follows. + if (value is not null + && value.Length >= 4 && value[3] == ':') + { + type = value.AsSpan().Slice(0, 3); + value = value.Substring(4); + } + else + { + type = default; + } + return value; + } + } + + internal bool TryGetDouble(out double val) + { + if (IsNull || Payload.IsEmpty) + { + val = 0; + return false; + } + if (TryGetInt64(out long i64)) + { + val = i64; + return true; + } + + if (Payload.IsSingleSegment) return Format.TryParseDouble(Payload.First.Span, out val); + if (Payload.Length < 64) + { + Span span = stackalloc byte[(int)Payload.Length]; + Payload.CopyTo(span); + return Format.TryParseDouble(span, out val); + } + return Format.TryParseDouble(GetString(), out val); + } + + internal bool TryGetInt64(out long value) + { + if (IsNull || Payload.IsEmpty || Payload.Length > Format.MaxInt64TextLen) + { + value = 0; + return false; + } + + if (Payload.IsSingleSegment) return Format.TryParseInt64(Payload.First.Span, out value); + + Span span = stackalloc byte[(int)Payload.Length]; // we already checked the length was <= MaxInt64TextLen + Payload.CopyTo(span); + return Format.TryParseInt64(span, out value); + } + + internal bool Is(char value) + { + var span = Payload.First.Span; + return span.Length == 1 && (char)span[0] == value && Payload.IsSingleSegment; + } + } +} diff --git a/src/StackExchange.Redis/RedisBase.cs b/src/StackExchange.Redis/RedisBase.cs new file mode 100644 index 000000000..095835efd --- /dev/null +++ b/src/StackExchange.Redis/RedisBase.cs @@ -0,0 +1,139 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + internal abstract partial class RedisBase : IRedis + { + internal static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + internal readonly ConnectionMultiplexer multiplexer; + protected readonly object? asyncState; + + internal RedisBase(ConnectionMultiplexer multiplexer, object? asyncState) + { + this.multiplexer = multiplexer; + this.asyncState = asyncState; + } + + IConnectionMultiplexer IRedisAsync.Multiplexer => multiplexer; + + public virtual TimeSpan Ping(CommandFlags flags = CommandFlags.None) + { + var msg = GetTimerMessage(flags); + return ExecuteSync(msg, ResultProcessor.ResponseTimer); + } + + public virtual Task PingAsync(CommandFlags flags = CommandFlags.None) + { + var msg = GetTimerMessage(flags); + return ExecuteAsync(msg, ResultProcessor.ResponseTimer); + } + + public override string ToString() => multiplexer.ToString(); + + public bool TryWait(Task task) => task.Wait(multiplexer.TimeoutMilliseconds); + + public void Wait(Task task) => multiplexer.Wait(task); + + public T Wait(Task task) => multiplexer.Wait(task); + + public void WaitAll(params Task[] tasks) => multiplexer.WaitAll(tasks); + + internal virtual Task ExecuteAsync(Message? message, ResultProcessor? processor, T defaultValue, ServerEndPoint? server = null) + { + if (message is null) return CompletedTask.FromDefault(defaultValue, asyncState); + multiplexer.CheckMessage(message); + return multiplexer.ExecuteAsyncImpl(message, processor, asyncState, server, defaultValue); + } + + internal virtual Task ExecuteAsync(Message? message, ResultProcessor? processor, ServerEndPoint? server = null) + { + if (message is null) return CompletedTask.Default(asyncState); + multiplexer.CheckMessage(message); + return multiplexer.ExecuteAsyncImpl(message, processor, asyncState, server); + } + + [return: NotNullIfNotNull("defaultValue")] + internal virtual T? ExecuteSync(Message? message, ResultProcessor? processor, ServerEndPoint? server = null, T? defaultValue = default) + { + if (message is null) return defaultValue; // no-op + multiplexer.CheckMessage(message); + return multiplexer.ExecuteSyncImpl(message, processor, server, defaultValue); + } + + internal virtual RedisFeatures GetFeatures(in RedisKey key, CommandFlags flags, RedisCommand command, out ServerEndPoint? server) + { + server = multiplexer.SelectServer(command, flags, key); + var version = server == null ? multiplexer.RawConfig.DefaultVersion : server.Version; + return new RedisFeatures(version); + } + + protected static void WhenAlwaysOrExists(When when) + { + switch (when) + { + case When.Always: + case When.Exists: + break; + default: + throw new ArgumentException(when + " is not valid in this context; the permitted values are: Always, Exists"); + } + } + + protected static void WhenAlwaysOrExistsOrNotExists(When when) + { + switch (when) + { + case When.Always: + case When.Exists: + case When.NotExists: + break; + default: + throw new ArgumentException(when + " is not valid in this context; the permitted values are: Always, Exists, NotExists"); + } + } + + protected static void WhenAlwaysOrNotExists(When when) + { + switch (when) + { + case When.Always: + case When.NotExists: + break; + default: + throw new ArgumentException(when + " is not valid in this context; the permitted values are: Always, NotExists"); + } + } + + private ResultProcessor.TimingProcessor.TimerMessage GetTimerMessage(CommandFlags flags) + { + // do the best we can with available commands + var map = multiplexer.CommandMap; + if (map.IsAvailable(RedisCommand.PING)) + return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.PING); + if (map.IsAvailable(RedisCommand.TIME)) + return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.TIME); + if (map.IsAvailable(RedisCommand.ECHO)) + return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.ECHO, RedisLiterals.PING); + // as our fallback, we'll do something odd... we'll treat a key like a value, out of sheer desperation + // note: this usually means: twemproxy/envoyproxy - in which case we're fine anyway, since the proxy does the routing + return ResultProcessor.TimingProcessor.CreateMessage(0, flags, RedisCommand.EXISTS, (RedisValue)multiplexer.UniqueId); + } + + internal static class CursorUtils + { + internal const long Origin = 0; + internal const int + DefaultRedisPageSize = 10, + DefaultLibraryPageSize = 250; + internal static bool IsNil(in RedisValue pattern) + { + if (pattern.IsNullOrEmpty) return true; + if (pattern.IsInteger) return false; + byte[] rawValue = pattern!; + return rawValue.Length == 1 && rawValue[0] == '*'; + } + } + } +} diff --git a/src/StackExchange.Redis/RedisBatch.cs b/src/StackExchange.Redis/RedisBatch.cs new file mode 100644 index 000000000..0ef97f365 --- /dev/null +++ b/src/StackExchange.Redis/RedisBatch.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + internal sealed class RedisBatch : RedisDatabase, IBatch + { + private List? pending; + + public RedisBatch(RedisDatabase wrapped, object? asyncState) : base(wrapped.multiplexer, wrapped.Database, asyncState ?? wrapped.AsyncState) { } + + public void Execute() + { + var snapshot = pending; + pending = null; + if (snapshot == null || snapshot.Count == 0) return; + + // group into per-bridge chunks + var byBridge = new Dictionary>(); + + // optimisation: assume most things are in a single bridge + PhysicalBridge? lastBridge = null; + List? lastList = null; + foreach (var message in snapshot) + { + var server = multiplexer.SelectServer(message); + if (server == null) + { + FailNoServer(multiplexer, snapshot); + throw ExceptionFactory.NoConnectionAvailable(multiplexer, message, server); + } + var bridge = server.GetBridge(message); + if (bridge == null) + { + FailNoServer(multiplexer, snapshot); + throw ExceptionFactory.NoConnectionAvailable(multiplexer, message, server); + } + + // identity a list + List? list; + if (bridge == lastBridge) + { + list = lastList!; + } + else if (!byBridge.TryGetValue(bridge, out list)) + { + list = new List(); + byBridge.Add(bridge, list); + } + lastBridge = bridge; + lastList = list; + + list.Add(message); + } + + foreach (var pair in byBridge) + { + if (!pair.Key.TryEnqueue(pair.Value, pair.Key.ServerEndPoint.IsReplica)) + { + FailNoServer(multiplexer, pair.Value); + } + } + } + + internal override Task ExecuteAsync(Message? message, ResultProcessor? processor, T defaultValue, ServerEndPoint? server = null) + { + if (message == null) return CompletedTask.FromDefault(defaultValue, asyncState); + multiplexer.CheckMessage(message); + + // prepare the inner command as a task + Task task; + if (message.IsFireAndForget) + { + task = CompletedTask.FromDefault(defaultValue, null); // F+F explicitly does not get async-state + } + else + { + var source = TaskResultBox.Create(out var tcs, asyncState); + task = tcs.Task; + message.SetSource(source, processor); + } + + // store it + (pending ??= new List()).Add(message); + return task!; + } + + internal override Task ExecuteAsync(Message? message, ResultProcessor? processor, ServerEndPoint? server = null) where T : default + { + if (message == null) return CompletedTask.Default(asyncState); + multiplexer.CheckMessage(message); + + // prepare the inner command as a task + Task task; + if (message.IsFireAndForget) + { + task = CompletedTask.Default(null); // F+F explicitly does not get async-state + } + else + { + var source = TaskResultBox.Create(out var tcs, asyncState); + task = tcs.Task; + message.SetSource(source!, processor); + } + + // store it + (pending ??= new List()).Add(message); + return task; + } + + internal override T ExecuteSync(Message? message, ResultProcessor? processor, ServerEndPoint? server = null, T? defaultValue = default) where T : default + => throw new NotSupportedException("ExecuteSync cannot be used inside a batch"); + + private static void FailNoServer(ConnectionMultiplexer muxer, List messages) + { + if (messages == null) return; + foreach (var msg in messages) + { + msg.Fail(ConnectionFailureType.UnableToResolvePhysicalConnection, null, "unable to write batch", muxer); + msg.Complete(); + } + } + } +} diff --git a/src/StackExchange.Redis/RedisChannel.cs b/src/StackExchange.Redis/RedisChannel.cs new file mode 100644 index 000000000..d4289f3c6 --- /dev/null +++ b/src/StackExchange.Redis/RedisChannel.cs @@ -0,0 +1,375 @@ +using System; +using System.Text; + +namespace StackExchange.Redis +{ + /// + /// Represents a pub/sub channel name. + /// + public readonly struct RedisChannel : IEquatable + { + internal readonly byte[]? Value; + + internal readonly RedisChannelOptions Options; + + [Flags] + internal enum RedisChannelOptions + { + None = 0, + Pattern = 1 << 0, + Sharded = 1 << 1, + KeyRouted = 1 << 2, + } + + // we don't consider Routed for equality - it's an implementation detail, not a fundamental feature + private const RedisChannelOptions EqualityMask = ~RedisChannelOptions.KeyRouted; + + internal RedisCommand PublishCommand => IsSharded ? RedisCommand.SPUBLISH : RedisCommand.PUBLISH; + + /// + /// Should we use cluster routing for this channel? This applies *either* to sharded (SPUBLISH) scenarios, + /// or to scenarios using . + /// + internal bool IsKeyRouted => (Options & RedisChannelOptions.KeyRouted) != 0; + + /// + /// Indicates whether the channel-name is either null or a zero-length value. + /// + public bool IsNullOrEmpty => Value == null || Value.Length == 0; + + /// + /// Indicates whether this channel represents a wildcard pattern (see PSUBSCRIBE). + /// + public bool IsPattern => (Options & RedisChannelOptions.Pattern) != 0; + + /// + /// Indicates whether this channel represents a shard channel (see SSUBSCRIBE). + /// + public bool IsSharded => (Options & RedisChannelOptions.Sharded) != 0; + + internal bool IsNull => Value == null; + + /// + /// Indicates whether channels should use when no + /// is specified; this is enabled by default, but can be disabled to avoid unexpected wildcard scenarios. + /// + public static bool UseImplicitAutoPattern + { + get => s_DefaultPatternMode == PatternMode.Auto; + set => s_DefaultPatternMode = value ? PatternMode.Auto : PatternMode.Literal; + } + private static PatternMode s_DefaultPatternMode = PatternMode.Auto; + + /// + /// Creates a new that does not act as a wildcard subscription. In cluster + /// environments, this channel will be freely routed to any applicable server - different client nodes + /// will generally connect to different servers; this is suitable for distributing pub/sub in scenarios with + /// very few channels. In non-cluster environments, routing is not a consideration. + /// + public static RedisChannel Literal(string value) => new(value, RedisChannelOptions.None); + + /// + /// Creates a new that does not act as a wildcard subscription. In cluster + /// environments, this channel will be freely routed to any applicable server - different client nodes + /// will generally connect to different servers; this is suitable for distributing pub/sub in scenarios with + /// very few channels. In non-cluster environments, routing is not a consideration. + /// + public static RedisChannel Literal(byte[] value) => new(value, RedisChannelOptions.None); + + /// + /// In cluster environments, this channel will be routed using similar rules to , which is suitable + /// for distributing pub/sub in scenarios with lots of channels. In non-cluster environments, routing is not + /// a consideration. + /// + /// Note that channels from Sharded are always routed. + public RedisChannel WithKeyRouting() => new(Value, Options | RedisChannelOptions.KeyRouted); + + /// + /// Creates a new that acts as a wildcard subscription. In cluster + /// environments, this channel will be freely routed to any applicable server - different client nodes + /// will generally connect to different servers; this is suitable for distributing pub/sub in scenarios with + /// very few channels. In non-cluster environments, routing is not a consideration. + /// + public static RedisChannel Pattern(string value) => new(value, RedisChannelOptions.Pattern); + + /// + /// Creates a new that acts as a wildcard subscription. In cluster + /// environments, this channel will be freely routed to any applicable server - different client nodes + /// will generally connect to different servers; this is suitable for distributing pub/sub in scenarios with + /// very few channels. In non-cluster environments, routing is not a consideration. + /// + public static RedisChannel Pattern(byte[] value) => new(value, RedisChannelOptions.Pattern); + + /// + /// Create a new redis channel from a buffer, explicitly controlling the pattern mode. + /// + /// The name of the channel to create. + /// The mode for name matching. + public RedisChannel(byte[]? value, PatternMode mode) : this(value, DeterminePatternBased(value, mode) ? RedisChannelOptions.Pattern : RedisChannelOptions.None) + { + } + + /// + /// Create a new redis channel from a string, explicitly controlling the pattern mode. + /// + /// The string name of the channel to create. + /// The mode for name matching. + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + public RedisChannel(string value, PatternMode mode) : this(value is null ? null : Encoding.UTF8.GetBytes(value), mode) + { + } + + /// + /// Create a new redis channel from a buffer, representing a sharded channel. In cluster + /// environments, this channel will be routed using similar rules to , which is suitable + /// for distributing pub/sub in scenarios with lots of channels. In non-cluster environments, routing is not + /// a consideration. + /// + /// The name of the channel to create. + /// Note that sharded subscriptions are completely separate to regular subscriptions; subscriptions + /// using sharded channels must also be published with sharded channels (and vice versa). + public static RedisChannel Sharded(byte[]? value) => new(value, RedisChannelOptions.Sharded | RedisChannelOptions.KeyRouted); + + /// + /// Create a new redis channel from a string, representing a sharded channel. In cluster + /// environments, this channel will be routed using similar rules to , which is suitable + /// for distributing pub/sub in scenarios with lots of channels. In non-cluster environments, routing is not + /// a consideration. + /// + /// The string name of the channel to create. + /// Note that sharded subscriptions are completely separate to regular subscriptions; subscriptions + /// using sharded channels must also be published with sharded channels (and vice versa). + public static RedisChannel Sharded(string value) => new(value, RedisChannelOptions.Sharded | RedisChannelOptions.KeyRouted); + + internal RedisChannel(byte[]? value, RedisChannelOptions options) + { + Value = value; + Options = options; + } + + internal RedisChannel(string? value, RedisChannelOptions options) + { + Value = value is null ? null : Encoding.UTF8.GetBytes(value); + Options = options; + } + + private static bool DeterminePatternBased(byte[]? value, PatternMode mode) => mode switch + { + PatternMode.Auto => value != null && Array.IndexOf(value, (byte)'*') >= 0, + PatternMode.Literal => false, + PatternMode.Pattern => true, + _ => throw new ArgumentOutOfRangeException(nameof(mode)), + }; + + /// + /// Indicate whether two channel names are not equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(RedisChannel x, RedisChannel y) => !(x == y); + + /// + /// Indicate whether two channel names are not equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(string x, RedisChannel y) => !(x == y); + + /// + /// Indicate whether two channel names are not equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(byte[] x, RedisChannel y) => !(x == y); + + /// + /// Indicate whether two channel names are not equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(RedisChannel x, string y) => !(x == y); + + /// + /// Indicate whether two channel names are not equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(RedisChannel x, byte[] y) => !(x == y); + + /// + /// Indicate whether two channel names are equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(RedisChannel x, RedisChannel y) => + (x.Options & EqualityMask) == (y.Options & EqualityMask) + && RedisValue.Equals(x.Value, y.Value); + + /// + /// Indicate whether two channel names are equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(string x, RedisChannel y) => + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + RedisValue.Equals(x is null ? null : Encoding.UTF8.GetBytes(x), y.Value); + + /// + /// Indicate whether two channel names are equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(byte[] x, RedisChannel y) => RedisValue.Equals(x, y.Value); + + /// + /// Indicate whether two channel names are equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(RedisChannel x, string y) => + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + RedisValue.Equals(x.Value, y is null ? null : Encoding.UTF8.GetBytes(y)); + + /// + /// Indicate whether two channel names are equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(RedisChannel x, byte[] y) => RedisValue.Equals(x.Value, y); + + /// + /// See . + /// + /// The to compare to. + public override bool Equals(object? obj) => obj switch + { + RedisChannel rcObj => RedisValue.Equals(Value, rcObj.Value), + string sObj => RedisValue.Equals(Value, Encoding.UTF8.GetBytes(sObj)), + byte[] bObj => RedisValue.Equals(Value, bObj), + _ => false, + }; + + /// + /// Indicate whether two channel names are equal. + /// + /// The to compare to. + public bool Equals(RedisChannel other) => (Options & EqualityMask) == (other.Options & EqualityMask) + && RedisValue.Equals(Value, other.Value); + + /// + public override int GetHashCode() => RedisValue.GetHashCode(Value) ^ (int)(Options & EqualityMask); + + /// + /// Obtains a string representation of the channel name. + /// + public override string ToString() => ((string?)this) ?? "(null)"; + + internal static bool AssertStarts(byte[] value, byte[] expected) + { + for (int i = 0; i < expected.Length; i++) + { + if (expected[i] != value[i]) return false; + } + return true; + } + + internal void AssertNotNull() + { + if (IsNull) throw new ArgumentException("A null key is not valid in this context"); + } + + internal RedisChannel Clone() + { + if (Value is null || Value.Length == 0) + { + // no need to duplicate anything + return this; + } + var copy = (byte[])Value.Clone(); // defensive array copy + return new RedisChannel(copy, Options); + } + + /// + /// The matching pattern for this channel. + /// + public enum PatternMode + { + /// + /// Will be treated as a pattern if it includes *. + /// + Auto = 0, + + /// + /// Never a pattern. + /// + Literal = 1, + + /// + /// Always a pattern. + /// + Pattern = 2, + } + + /// + /// Create a channel name from a . + /// + /// The string to get a channel from. + [Obsolete("It is preferable to explicitly specify a " + nameof(PatternMode) + ", or use the " + nameof(Literal) + "/" + nameof(Pattern) + " methods", error: false)] + public static implicit operator RedisChannel(string key) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (key is null) return default; + return new RedisChannel(Encoding.UTF8.GetBytes(key), s_DefaultPatternMode); + } + + /// + /// Create a channel name from a byte[]. + /// + /// The byte array to get a channel from. + [Obsolete("It is preferable to explicitly specify a " + nameof(PatternMode) + ", or use the " + nameof(Literal) + "/" + nameof(Pattern) + " methods", error: false)] + public static implicit operator RedisChannel(byte[]? key) + => key is null ? default : new RedisChannel(key, s_DefaultPatternMode); + + /// + /// Obtain the channel name as a byte[]. + /// + /// The channel to get a byte[] from. + public static implicit operator byte[]?(RedisChannel key) => key.Value; + + /// + /// Obtain the channel name as a . + /// + /// The channel to get a string from. + public static implicit operator string?(RedisChannel key) + { + var arr = key.Value; + if (arr is null) + { + return null; + } + try + { + return Encoding.UTF8.GetString(arr); + } + catch (Exception e) when // Only catch exception throwed by Encoding.UTF8.GetString + (e is DecoderFallbackException or ArgumentException or ArgumentNullException) + { + return BitConverter.ToString(arr); + } + } + +#if DEBUG + // these exist *purely* to ensure that we never add them later *without* + // giving due consideration to the default pattern mode (UseImplicitAutoPattern) + // (since we don't ship them, we don't need them in release) + [Obsolete("Watch for " + nameof(UseImplicitAutoPattern), error: true)] + // ReSharper disable once UnusedMember.Local + // ReSharper disable once UnusedParameter.Local + private RedisChannel(string value) => throw new NotSupportedException(); + [Obsolete("Watch for " + nameof(UseImplicitAutoPattern), error: true)] + // ReSharper disable once UnusedMember.Local + // ReSharper disable once UnusedParameter.Local + private RedisChannel(byte[]? value) => throw new NotSupportedException(); +#endif + } +} diff --git a/src/StackExchange.Redis/RedisDatabase.ExpiryToken.cs b/src/StackExchange.Redis/RedisDatabase.ExpiryToken.cs new file mode 100644 index 000000000..42cfdcb18 --- /dev/null +++ b/src/StackExchange.Redis/RedisDatabase.ExpiryToken.cs @@ -0,0 +1,76 @@ +using System; + +namespace StackExchange.Redis; + +internal partial class RedisDatabase +{ + /// + /// Parses, validates and represents, for example: "EX 10", "KEEPTTL" or "". + /// + internal readonly struct ExpiryToken + { + private static readonly ExpiryToken s_Persist = new(RedisLiterals.PERSIST), s_KeepTtl = new(RedisLiterals.KEEPTTL), s_Null = new(RedisValue.Null); + + public RedisValue Operand { get; } + public long Value { get; } + public int Tokens => Value == long.MinValue ? (Operand.IsNull ? 0 : 1) : 2; + public bool HasValue => Value != long.MinValue; + public bool HasOperand => !Operand.IsNull; + + public static ExpiryToken Persist(TimeSpan? expiry, bool persist) + { + if (expiry.HasValue) + { + if (persist) throw new ArgumentException("Cannot specify both expiry and persist", nameof(persist)); + return new(expiry.GetValueOrDefault()); // EX 10 + } + + return persist ? s_Persist : s_Null; // PERSIST (or nothing) + } + + public static ExpiryToken KeepTtl(TimeSpan? expiry, bool keepTtl) + { + if (expiry.HasValue) + { + if (keepTtl) throw new ArgumentException("Cannot specify both expiry and keepTtl", nameof(keepTtl)); + return new(expiry.GetValueOrDefault()); // EX 10 + } + + return keepTtl ? s_KeepTtl : s_Null; // KEEPTTL (or nothing) + } + + private ExpiryToken(RedisValue operand, long value = long.MinValue) + { + Operand = operand; + Value = value; + } + + public ExpiryToken(TimeSpan expiry) + { + long milliseconds = expiry.Ticks / TimeSpan.TicksPerMillisecond; + var useSeconds = milliseconds % 1000 == 0; + + Operand = useSeconds ? RedisLiterals.EX : RedisLiterals.PX; + Value = useSeconds ? (milliseconds / 1000) : milliseconds; + } + + public ExpiryToken(DateTime expiry) + { + long milliseconds = GetUnixTimeMilliseconds(expiry); + var useSeconds = milliseconds % 1000 == 0; + + Operand = useSeconds ? RedisLiterals.EXAT : RedisLiterals.PXAT; + Value = useSeconds ? (milliseconds / 1000) : milliseconds; + } + + public override string ToString() => Tokens switch + { + 2 => $"{Operand} {Value}", + 1 => Operand.ToString(), + _ => "", + }; + + public override int GetHashCode() => throw new NotSupportedException(); + public override bool Equals(object? obj) => throw new NotSupportedException(); + } +} diff --git a/src/StackExchange.Redis/RedisDatabase.VectorSets.cs b/src/StackExchange.Redis/RedisDatabase.VectorSets.cs new file mode 100644 index 000000000..9b3f1b43b --- /dev/null +++ b/src/StackExchange.Redis/RedisDatabase.VectorSets.cs @@ -0,0 +1,191 @@ +using System; +using System.Threading.Tasks; + +// ReSharper disable once CheckNamespace +namespace StackExchange.Redis; + +internal partial class RedisDatabase +{ + public bool VectorSetAdd( + RedisKey key, + VectorSetAddRequest request, + CommandFlags flags = CommandFlags.None) + { + var msg = request.ToMessage(key, Database, flags); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public long VectorSetLength(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VCARD, key); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public int VectorSetDimension(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VDIM, key); + return ExecuteSync(msg, ResultProcessor.Int32); + } + + public Lease? VectorSetGetApproximateVector(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VEMB, key, member); + return ExecuteSync(msg, ResultProcessor.LeaseFloat32); + } + + public string? VectorSetGetAttributesJson(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VGETATTR, key, member); + return ExecuteSync(msg, ResultProcessor.String); + } + + public VectorSetInfo? VectorSetInfo(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VINFO, key); + return ExecuteSync(msg, ResultProcessor.VectorSetInfo); + } + + public bool VectorSetContains(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VISMEMBER, key, member); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Lease? VectorSetGetLinks(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VLINKS, key, member); + return ExecuteSync(msg, ResultProcessor.VectorSetLinks); + } + + public Lease? VectorSetGetLinksWithScores(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VLINKS, key, member, RedisLiterals.WITHSCORES); + return ExecuteSync(msg, ResultProcessor.VectorSetLinksWithScores); + } + + public RedisValue VectorSetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VRANDMEMBER, key); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public RedisValue[] VectorSetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VRANDMEMBER, key, count); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public bool VectorSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VREM, key, member); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public bool VectorSetSetAttributesJson(RedisKey key, RedisValue member, string attributesJson, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VSETATTR, key, member, attributesJson); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Lease? VectorSetSimilaritySearch( + RedisKey key, + VectorSetSimilaritySearchRequest query, + CommandFlags flags = CommandFlags.None) + { + if (query == null) throw new ArgumentNullException(nameof(query)); + var msg = query.ToMessage(key, Database, flags); + return ExecuteSync(msg, msg.GetResultProcessor()); + } + + // Vector Set async operations + public Task VectorSetAddAsync( + RedisKey key, + VectorSetAddRequest request, + CommandFlags flags = CommandFlags.None) + { + var msg = request.ToMessage(key, Database, flags); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task VectorSetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VCARD, key); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task VectorSetDimensionAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VDIM, key); + return ExecuteAsync(msg, ResultProcessor.Int32); + } + + public Task?> VectorSetGetApproximateVectorAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VEMB, key, member); + return ExecuteAsync(msg, ResultProcessor.LeaseFloat32); + } + + public Task VectorSetGetAttributesJsonAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VGETATTR, key, member); + return ExecuteAsync(msg, ResultProcessor.String); + } + + public Task VectorSetInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VINFO, key); + return ExecuteAsync(msg, ResultProcessor.VectorSetInfo); + } + + public Task VectorSetContainsAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VISMEMBER, key, member); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task?> VectorSetGetLinksAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VLINKS, key, member); + return ExecuteAsync(msg, ResultProcessor.VectorSetLinks); + } + + public Task?> VectorSetGetLinksWithScoresAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VLINKS, key, member, RedisLiterals.WITHSCORES); + return ExecuteAsync(msg, ResultProcessor.VectorSetLinksWithScores); + } + + public Task VectorSetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VRANDMEMBER, key); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task VectorSetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VRANDMEMBER, key, count); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task VectorSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VREM, key, member); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task VectorSetSetAttributesJsonAsync(RedisKey key, RedisValue member, string attributesJson, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.VSETATTR, key, member, attributesJson); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task?> VectorSetSimilaritySearchAsync( + RedisKey key, + VectorSetSimilaritySearchRequest query, + CommandFlags flags = CommandFlags.None) + { + if (query == null) throw new ArgumentNullException(nameof(query)); + var msg = query.ToMessage(key, Database, flags); + return ExecuteAsync(msg, msg.GetResultProcessor()); + } +} diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs new file mode 100644 index 000000000..bcda4146b --- /dev/null +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -0,0 +1,5726 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Threading.Tasks; +using Pipelines.Sockets.Unofficial.Arenas; + +namespace StackExchange.Redis +{ + internal partial class RedisDatabase : RedisBase, IDatabase + { + internal RedisDatabase(ConnectionMultiplexer multiplexer, int db, object? asyncState) + : base(multiplexer, asyncState) + { + Database = db; + } + + public object? AsyncState => asyncState; + + public int Database { get; } + + public IBatch CreateBatch(object? asyncState) + { + if (this is IBatch) throw new NotSupportedException("Nested batches are not supported"); + return new RedisBatch(this, asyncState); + } + + public ITransaction CreateTransaction(object? asyncState) + { + if (this is IBatch) throw new NotSupportedException("Nested transactions are not supported"); + return new RedisTransaction(this, asyncState); + } + + private ITransaction? CreateTransactionIfAvailable(object? asyncState) + { + var map = multiplexer.CommandMap; + if (!map.IsAvailable(RedisCommand.MULTI) || !map.IsAvailable(RedisCommand.EXEC)) + { + return null; + } + return CreateTransaction(asyncState); + } + + public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None) + { + return GeoAdd(key, new GeoEntry(longitude, latitude, member), flags); + } + + public Task GeoAddAsync(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None) + { + return GeoAddAsync(key, new GeoEntry(longitude, latitude, member), flags); + } + + public bool GeoAdd(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, value.Longitude, value.Latitude, value.Member); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task GeoAddAsync(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, value.Longitude, value.Latitude, value.Member); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public long GeoAdd(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, values); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task GeoAddAsync(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, values); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + return SortedSetRemove(key, member, flags); + } + + public Task GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + return SortedSetRemoveAsync(key, member, flags); + } + + public double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GEODIST, key, member1, member2, StackExchange.Redis.GeoPosition.GetRedisUnit(unit)); + return ExecuteSync(msg, ResultProcessor.NullableDouble); + } + + public Task GeoDistanceAsync(RedisKey key, RedisValue value0, RedisValue value1, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GEODIST, key, value0, value1, StackExchange.Redis.GeoPosition.GetRedisUnit(unit)); + return ExecuteAsync(msg, ResultProcessor.NullableDouble); + } + + public string?[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + { + if (members == null) throw new ArgumentNullException(nameof(members)); + var redisValues = new RedisValue[members.Length]; + for (var i = 0; i < members.Length; i++) redisValues[i] = members[i]; + var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues); + return ExecuteSync(msg, ResultProcessor.NullableStringArray, defaultValue: Array.Empty()); + } + + public Task GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + { + if (members == null) throw new ArgumentNullException(nameof(members)); + var redisValues = new RedisValue[members.Length]; + for (var i = 0; i < members.Length; i++) redisValues[i] = members[i]; + var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues); + return ExecuteAsync(msg, ResultProcessor.NullableStringArray, defaultValue: Array.Empty()); + } + + public string? GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, member); + return ExecuteSync(msg, ResultProcessor.String); + } + + public Task GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, member); + return ExecuteAsync(msg, ResultProcessor.String); + } + + public GeoPosition?[] GeoPosition(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + { + if (members == null) throw new ArgumentNullException(nameof(members)); + var redisValues = new RedisValue[members.Length]; + for (var i = 0; i < members.Length; i++) redisValues[i] = members[i]; + var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, redisValues); + return ExecuteSync(msg, ResultProcessor.RedisGeoPositionArray, defaultValue: Array.Empty()); + } + + public Task GeoPositionAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + { + if (members == null) throw new ArgumentNullException(nameof(members)); + var redisValues = new RedisValue[members.Length]; + for (var i = 0; i < members.Length; i++) redisValues[i] = members[i]; + var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, redisValues); + return ExecuteAsync(msg, ResultProcessor.RedisGeoPositionArray, defaultValue: Array.Empty()); + } + + public GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, member); + return ExecuteSync(msg, ResultProcessor.RedisGeoPosition); + } + + public Task GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, member); + return ExecuteAsync(msg, ResultProcessor.RedisGeoPosition); + } + + private Message GetGeoSearchMessage(in RedisKey sourceKey, in RedisKey destinationKey, RedisValue? member, double longitude, double latitude, GeoSearchShape shape, int count, bool demandClosest, bool storeDistances, Order? order, GeoRadiusOptions options, CommandFlags flags) + { + var redisValues = new List(15); + if (member != null) + { + redisValues.Add(RedisLiterals.FROMMEMBER); + redisValues.Add(member.Value); + } + else + { + redisValues.Add(RedisLiterals.FROMLONLAT); + redisValues.Add(longitude); + redisValues.Add(latitude); + } + + shape.AddArgs(redisValues); + + if (order != null) + { + redisValues.Add(order.Value.ToLiteral()); + } + if (count >= 0) + { + redisValues.Add(RedisLiterals.COUNT); + redisValues.Add(count); + } + + if (!demandClosest) + { + if (count < 0) + { + throw new ArgumentException($"{nameof(demandClosest)} must be true if you are not limiting the count for a GEOSEARCH"); + } + redisValues.Add(RedisLiterals.ANY); + } + + options.AddArgs(redisValues); + + if (storeDistances) + { + redisValues.Add(RedisLiterals.STOREDIST); + } + + return destinationKey.IsNull + ? Message.Create(Database, flags, RedisCommand.GEOSEARCH, sourceKey, redisValues.ToArray()) + : Message.Create(Database, flags, RedisCommand.GEOSEARCHSTORE, destinationKey, sourceKey, redisValues.ToArray()); + } + + private Message GetGeoRadiusMessage(in RedisKey key, RedisValue? member, double longitude, double latitude, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags) + { + var redisValues = new List(10); + RedisCommand command; + if (member == null) + { + redisValues.Add(longitude); + redisValues.Add(latitude); + command = RedisCommand.GEORADIUS; + } + else + { + redisValues.Add(member.Value); + command = RedisCommand.GEORADIUSBYMEMBER; + } + + redisValues.Add(radius); + redisValues.Add(Redis.GeoPosition.GetRedisUnit(unit)); + options.AddArgs(redisValues); + + if (count > 0) + { + redisValues.Add(RedisLiterals.COUNT); + redisValues.Add(count); + } + if (order != null) + { + redisValues.Add(order.Value.ToLiteral()); + } + + return Message.Create(Database, flags, command, key, redisValues.ToArray()); + } + + public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags) + { + // This gets confused with the double overload below sometimes...throwing when this occurs. + if (member.Type == RedisValue.StorageType.Double) + { + throw new ArgumentException("Member should not be a double, you likely want the GeoRadius(RedisKey, double, double, ...) overload.", nameof(member)); + } + return ExecuteSync(GetGeoRadiusMessage(key, member, double.NaN, double.NaN, radius, unit, count, order, options, flags), ResultProcessor.GeoRadiusArray(options), defaultValue: Array.Empty()); + } + + public Task GeoRadiusAsync(RedisKey key, RedisValue member, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags) + { + // This gets confused with the double overload below sometimes...throwing when this occurs. + if (member.Type == RedisValue.StorageType.Double) + { + throw new ArgumentException("Member should not be a double, you likely want the GeoRadius(RedisKey, double, double, ...) overload.", nameof(member)); + } + return ExecuteAsync(GetGeoRadiusMessage(key, member, double.NaN, double.NaN, radius, unit, count, order, options, flags), ResultProcessor.GeoRadiusArray(options), defaultValue: Array.Empty()); + } + + public GeoRadiusResult[] GeoRadius(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags) + { + return ExecuteSync(GetGeoRadiusMessage(key, null, longitude, latitude, radius, unit, count, order, options, flags), ResultProcessor.GeoRadiusArray(options), defaultValue: Array.Empty()); + } + + public Task GeoRadiusAsync(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags) + { + return ExecuteAsync(GetGeoRadiusMessage(key, null, longitude, latitude, radius, unit, count, order, options, flags), ResultProcessor.GeoRadiusArray(options), defaultValue: Array.Empty()); + } + + public GeoRadiusResult[] GeoSearch(RedisKey key, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + { + var msg = GetGeoSearchMessage(key, RedisKey.Null, member, double.NaN, double.NaN, shape, count, demandClosest, false, order, options, flags); + return ExecuteSync(msg, ResultProcessor.GeoRadiusArray(options), defaultValue: Array.Empty()); + } + + public GeoRadiusResult[] GeoSearch(RedisKey key, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + { + var msg = GetGeoSearchMessage(key, RedisKey.Null, null, longitude, latitude, shape, count, demandClosest, false, order, options, flags); + return ExecuteSync(msg, ResultProcessor.GeoRadiusArray(options), defaultValue: Array.Empty()); + } + + public Task GeoSearchAsync(RedisKey key, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + { + var msg = GetGeoSearchMessage(key, RedisKey.Null, member, double.NaN, double.NaN, shape, count, demandClosest, false, order, options, flags); + return ExecuteAsync(msg, ResultProcessor.GeoRadiusArray(options), defaultValue: Array.Empty()); + } + + public Task GeoSearchAsync(RedisKey key, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + { + var msg = GetGeoSearchMessage(key, RedisKey.Null, null, longitude, latitude, shape, count, demandClosest, false, order, options, flags); + return ExecuteAsync(msg, ResultProcessor.GeoRadiusArray(options), defaultValue: Array.Empty()); + } + + public long GeoSearchAndStore(RedisKey sourceKey, RedisKey destinationKey, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetGeoSearchMessage(sourceKey, destinationKey, member, double.NaN, double.NaN, shape, count, demandClosest, storeDistances, order, GeoRadiusOptions.None, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public long GeoSearchAndStore(RedisKey sourceKey, RedisKey destinationKey, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetGeoSearchMessage(sourceKey, destinationKey, null, longitude, latitude, shape, count, demandClosest, storeDistances, order, GeoRadiusOptions.None, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task GeoSearchAndStoreAsync(RedisKey sourceKey, RedisKey destinationKey, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetGeoSearchMessage(sourceKey, destinationKey, member, double.NaN, double.NaN, shape, count, demandClosest, storeDistances, order, GeoRadiusOptions.None, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task GeoSearchAndStoreAsync(RedisKey sourceKey, RedisKey destinationKey, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetGeoSearchMessage(sourceKey, destinationKey, null, longitude, latitude, shape, count, demandClosest, storeDistances, order, GeoRadiusOptions.None, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long HashDecrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) + { + return HashIncrement(key, hashField, -value, flags); + } + + public double HashDecrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) + { + return HashIncrement(key, hashField, -value, flags); + } + + public Task HashDecrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) + { + return HashIncrementAsync(key, hashField, -value, flags); + } + + public Task HashDecrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) + { + return HashIncrementAsync(key, hashField, -value, flags); + } + + public bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HDEL, key, hashField); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public long HashDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + var msg = hashFields.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.HDEL, key, hashFields); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HDEL, key, hashField); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task HashDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + + var msg = hashFields.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.HDEL, key, hashFields); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HEXISTS, key, hashField); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HEXISTS, key, hashField); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + { + long milliseconds = expiry.Ticks / TimeSpan.TicksPerMillisecond; + return HashFieldExpireExecute(key, milliseconds, when, PickExpireCommandByPrecision, SyncCustomArrExecutor>, ResultProcessor.ExpireResultArray, flags, hashFields); + } + + public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + { + long milliseconds = GetUnixTimeMilliseconds(expiry); + return HashFieldExpireExecute(key, milliseconds, when, PickExpireAtCommandByPrecision, SyncCustomArrExecutor>, ResultProcessor.ExpireResultArray, flags, hashFields); + } + + public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + { + long milliseconds = expiry.Ticks / TimeSpan.TicksPerMillisecond; + return HashFieldExpireExecute(key, milliseconds, when, PickExpireCommandByPrecision, AsyncCustomArrExecutor>, ResultProcessor.ExpireResultArray, flags, hashFields); + } + + public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + { + long milliseconds = GetUnixTimeMilliseconds(expiry); + return HashFieldExpireExecute(key, milliseconds, when, PickExpireAtCommandByPrecision, AsyncCustomArrExecutor>, ResultProcessor.ExpireResultArray, flags, hashFields); + } + + private T HashFieldExpireExecute(RedisKey key, long milliseconds, ExpireWhen when, Func getCmd, CustomExecutor executor, TProcessor processor, CommandFlags flags, params RedisValue[] hashFields) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + var useSeconds = milliseconds % 1000 == 0; + var cmd = getCmd(useSeconds); + long expiry = useSeconds ? (milliseconds / 1000) : milliseconds; + + var values = when switch + { + ExpireWhen.Always => new List { expiry, RedisLiterals.FIELDS, hashFields.Length }, + _ => new List { expiry, when.ToLiteral(), RedisLiterals.FIELDS, hashFields.Length }, + }; + values.AddRange(hashFields); + var msg = Message.Create(Database, flags, cmd, key, values.ToArray()); + return executor(msg, processor); + } + + private static RedisCommand PickExpireCommandByPrecision(bool useSeconds) => useSeconds ? RedisCommand.HEXPIRE : RedisCommand.HPEXPIRE; + + private static RedisCommand PickExpireAtCommandByPrecision(bool useSeconds) => useSeconds ? RedisCommand.HEXPIREAT : RedisCommand.HPEXPIREAT; + + private T HashFieldExecute(RedisCommand cmd, RedisKey key, CustomExecutor executor, TProcessor processor, CommandFlags flags = CommandFlags.None, params RedisValue[] hashFields) + { + var values = new List { RedisLiterals.FIELDS, hashFields.Length }; + values.AddRange(hashFields); + var msg = Message.Create(Database, flags, cmd, key, values.ToArray()); + return executor(msg, processor); + } + + private delegate T CustomExecutor(Message msg, TProcessor processor); + + private T[] SyncCustomArrExecutor(Message msg, TProcessor processor) where TProcessor : ResultProcessor => ExecuteSync(msg, processor)!; + + private Task AsyncCustomArrExecutor(Message msg, TProcessor processor) where TProcessor : ResultProcessor => ExecuteAsync(msg, processor)!; + + public RedisValue HashFieldGetAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, 1, hashField); + return ExecuteSync(msg, ResultProcessor.RedisValueFromArray); + } + + public Lease? HashFieldGetLeaseAndDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, 1, hashField); + return ExecuteSync(msg, ResultProcessor.LeaseFromArray); + } + + public RedisValue[] HashFieldGetAndDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return Array.Empty(); + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, hashFields.Length, hashFields); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, 1, hashField); + return ExecuteAsync(msg, ResultProcessor.RedisValueFromArray); + } + + public Task?> HashFieldGetLeaseAndDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, 1, hashField); + return ExecuteAsync(msg, ResultProcessor.LeaseFromArray); + } + + public Task HashFieldGetAndDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + var msg = Message.Create(Database, flags, RedisCommand.HGETDEL, key, RedisLiterals.FIELDS, hashFields.Length, hashFields); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + private Message HashFieldGetAndSetExpiryMessage(in RedisKey key, in RedisValue hashField, ExpiryToken expiry, CommandFlags flags) => + expiry.Tokens switch + { + // expiry, for example EX 10 + 2 => Message.Create(Database, flags, RedisCommand.HGETEX, key, expiry.Operand, expiry.Value, RedisLiterals.FIELDS, 1, hashField), + // keyword only, for example PERSIST + 1 => Message.Create(Database, flags, RedisCommand.HGETEX, key, expiry.Operand, RedisLiterals.FIELDS, 1, hashField), + // default case when neither expiry nor persist are set + _ => Message.Create(Database, flags, RedisCommand.HGETEX, key, RedisLiterals.FIELDS, 1, hashField), + }; + + private Message HashFieldGetAndSetExpiryMessage(in RedisKey key, RedisValue[] hashFields, ExpiryToken expiry, CommandFlags flags) + { + if (hashFields is null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 1) + { + return HashFieldGetAndSetExpiryMessage(key, in hashFields[0], expiry, flags); + } + + // precision, time, FIELDS, hashFields.Length + int extraTokens = expiry.Tokens + 2; + + RedisValue[] values = new RedisValue[expiry.Tokens + 2 + hashFields.Length]; + + int index = 0; + // add PERSIST or expiry values + switch (expiry.Tokens) + { + case 2: + values[index++] = expiry.Operand; + values[index++] = expiry.Value; + break; + case 1: + values[index++] = expiry.Operand; + break; + } + // add the fields + values[index++] = RedisLiterals.FIELDS; + values[index++] = hashFields.Length; + // check we've added everything we expected to + Debug.Assert(index == extraTokens + hashFields.Length); + + // Add hash fields to the array + hashFields.AsSpan().CopyTo(values.AsSpan(index)); + + return Message.Create(Database, flags, RedisCommand.HGETEX, key, values); + } + + public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, ExpiryToken.Persist(expiry, persist), flags); + return ExecuteSync(msg, ResultProcessor.RedisValueFromArray); + } + + public RedisValue HashFieldGetAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, new(expiry), flags); + return ExecuteSync(msg, ResultProcessor.RedisValueFromArray); + } + + public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, ExpiryToken.Persist(expiry, persist), flags); + return ExecuteSync(msg, ResultProcessor.LeaseFromArray); + } + + public Lease? HashFieldGetLeaseAndSetExpiry(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, new(expiry), flags); + return ExecuteSync(msg, ResultProcessor.LeaseFromArray); + } + + public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return Array.Empty(); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, ExpiryToken.Persist(expiry, persist), flags); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public RedisValue[] HashFieldGetAndSetExpiry(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return Array.Empty(); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, new(expiry), flags); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, ExpiryToken.Persist(expiry, persist), flags); + return ExecuteAsync(msg, ResultProcessor.RedisValueFromArray); + } + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, new(expiry), flags); + return ExecuteAsync(msg, ResultProcessor.RedisValueFromArray); + } + + public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, ExpiryToken.Persist(expiry, persist), flags); + return ExecuteAsync(msg, ResultProcessor.LeaseFromArray); + } + + public Task?> HashFieldGetLeaseAndSetExpiryAsync(RedisKey key, RedisValue hashField, DateTime expiry, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldGetAndSetExpiryMessage(key, hashField, new(expiry), flags); + return ExecuteAsync(msg, ResultProcessor.LeaseFromArray); + } + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, TimeSpan? expiry = null, bool persist = false, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, ExpiryToken.Persist(expiry, persist), flags); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task HashFieldGetAndSetExpiryAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + var msg = HashFieldGetAndSetExpiryMessage(key, hashFields, new(expiry), flags); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + private Message HashFieldSetAndSetExpiryMessage(in RedisKey key, in RedisValue field, in RedisValue value, ExpiryToken expiry, When when, CommandFlags flags) + { + if (when == When.Always) + { + return expiry.Tokens switch + { + 2 => Message.Create(Database, flags, RedisCommand.HSETEX, key, expiry.Operand, expiry.Value, RedisLiterals.FIELDS, 1, field, value), + 1 => Message.Create(Database, flags, RedisCommand.HSETEX, key, expiry.Operand, RedisLiterals.FIELDS, 1, field, value), + _ => Message.Create(Database, flags, RedisCommand.HSETEX, key, RedisLiterals.FIELDS, 1, field, value), + }; + } + else + { + // we need an extra token + var existance = when switch + { + When.Exists => RedisLiterals.FXX, + When.NotExists => RedisLiterals.FNX, + _ => throw new ArgumentOutOfRangeException(nameof(when)), + }; + + return expiry.Tokens switch + { + 2 => Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, expiry.Operand, expiry.Value, RedisLiterals.FIELDS, 1, field, value), + 1 => Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, expiry.Operand, RedisLiterals.FIELDS, 1, field, value), + _ => Message.Create(Database, flags, RedisCommand.HSETEX, key, existance, RedisLiterals.FIELDS, 1, field, value), + }; + } + } + + private Message HashFieldSetAndSetExpiryMessage(in RedisKey key, HashEntry[] hashFields, ExpiryToken expiry, When when, CommandFlags flags) + { + if (hashFields.Length == 1) + { + var field = hashFields[0]; + return HashFieldSetAndSetExpiryMessage(key, field.Name, field.Value, expiry, when, flags); + } + // Determine the base array size + var extraTokens = expiry.Tokens + (when == When.Always ? 2 : 3); // [FXX|FNX] {expiry} FIELDS {length} + RedisValue[] values = new RedisValue[(hashFields.Length * 2) + extraTokens]; + + int index = 0; + switch (when) + { + case When.Always: + break; + case When.Exists: + values[index++] = RedisLiterals.FXX; + break; + case When.NotExists: + values[index++] = RedisLiterals.FNX; + break; + default: + throw new ArgumentOutOfRangeException(nameof(when)); + } + switch (expiry.Tokens) + { + case 2: + values[index++] = expiry.Operand; + values[index++] = expiry.Value; + break; + case 1: + values[index++] = expiry.Operand; + break; + } + values[index++] = RedisLiterals.FIELDS; + values[index++] = hashFields.Length; + for (int i = 0; i < hashFields.Length; i++) + { + values[index++] = hashFields[i].name; + values[index++] = hashFields[i].value; + } + Debug.Assert(index == values.Length); + return Message.Create(Database, flags, RedisCommand.HSETEX, key, values); + } + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldSetAndSetExpiryMessage(key, field, value, ExpiryToken.KeepTtl(expiry, keepTtl), when, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldSetAndSetExpiryMessage(key, field, value, new(expiry), when, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, ExpiryToken.KeepTtl(expiry, keepTtl), when, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + public RedisValue HashFieldSetAndSetExpiry(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, new(expiry), when, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldSetAndSetExpiryMessage(key, field, value, ExpiryToken.KeepTtl(expiry, keepTtl), when, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, RedisValue field, RedisValue value, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = HashFieldSetAndSetExpiryMessage(key, field, value, new(expiry), when, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, ExpiryToken.KeepTtl(expiry, keepTtl), when, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + public Task HashFieldSetAndSetExpiryAsync(RedisKey key, HashEntry[] hashFields, DateTime expiry, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + var msg = HashFieldSetAndSetExpiryMessage(key, hashFields, new(expiry), when, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + HashFieldExecute(RedisCommand.HPEXPIRETIME, key, SyncCustomArrExecutor>, ResultProcessor.Int64Array, flags, hashFields); + + public Task HashFieldGetExpireDateTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + HashFieldExecute(RedisCommand.HPEXPIRETIME, key, AsyncCustomArrExecutor>, ResultProcessor.Int64Array, flags, hashFields); + + public PersistResult[] HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + HashFieldExecute(RedisCommand.HPERSIST, key, SyncCustomArrExecutor>, ResultProcessor.PersistResultArray, flags, hashFields); + + public Task HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + HashFieldExecute(RedisCommand.HPERSIST, key, AsyncCustomArrExecutor>, ResultProcessor.PersistResultArray, flags, hashFields); + + public long[] HashFieldGetTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + HashFieldExecute(RedisCommand.HPTTL, key, SyncCustomArrExecutor>, ResultProcessor.Int64Array, flags, hashFields); + + public Task HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) => + HashFieldExecute(RedisCommand.HPTTL, key, AsyncCustomArrExecutor>, ResultProcessor.Int64Array, flags, hashFields); + + public RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Lease? HashGetLease(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField); + return ExecuteSync(msg, ResultProcessor.Lease); + } + + public RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return Array.Empty(); + var msg = Message.Create(Database, flags, RedisCommand.HMGET, key, hashFields); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGETALL, key); + return ExecuteSync(msg, ResultProcessor.HashEntryArray, defaultValue: Array.Empty()); + } + + public Task HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGETALL, key); + return ExecuteAsync(msg, ResultProcessor.HashEntryArray, defaultValue: Array.Empty()); + } + + public Task HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task?> HashGetLeaseAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField); + return ExecuteAsync(msg, ResultProcessor.Lease); + } + + public Task HashGetAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + var msg = Message.Create(Database, flags, RedisCommand.HMGET, key, hashFields); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public long HashIncrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) + { + var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 + ? null : Message.Create(Database, flags, RedisCommand.HINCRBY, key, hashField, value); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public double HashIncrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) + { + var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 + ? null : Message.Create(Database, flags, RedisCommand.HINCRBYFLOAT, key, hashField, value); + return ExecuteSync(msg, ResultProcessor.Double); + } + + public Task HashIncrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) + { + var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 + ? null : Message.Create(Database, flags, RedisCommand.HINCRBY, key, hashField, value); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task HashIncrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) + { + var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 + ? null : Message.Create(Database, flags, RedisCommand.HINCRBYFLOAT, key, hashField, value); + return ExecuteAsync(msg, ResultProcessor.Double); + } + + public RedisValue[] HashKeys(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HKEYS, key); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task HashKeysAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HKEYS, key); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HLEN, key); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public RedisValue HashRandomField(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public RedisValue[] HashRandomFields(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public HashEntry[] HashRandomFieldsWithValues(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count, RedisLiterals.WITHVALUES); + return ExecuteSync(msg, ResultProcessor.HashEntryArray, defaultValue: Array.Empty()); + } + + public Task HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HLEN, key); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task HashRandomFieldAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task HashRandomFieldsAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task HashRandomFieldsWithValuesAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count, RedisLiterals.WITHVALUES); + return ExecuteAsync(msg, ResultProcessor.HashEntryArray, defaultValue: Array.Empty()); + } + + IEnumerable IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) + => HashScanAsync(key, pattern, pageSize, CursorUtils.Origin, 0, flags); + + IEnumerable IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => HashScanAsync(key, pattern, pageSize, cursor, pageOffset, flags); + + IAsyncEnumerable IDatabaseAsync.HashScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => HashScanAsync(key, pattern, pageSize, cursor, pageOffset, flags); + + private CursorEnumerable HashScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + { + var scan = TryScan(key, pattern, pageSize, cursor, pageOffset, flags, RedisCommand.HSCAN, HashScanResultProcessor.Default, out var server); + if (scan != null) return scan; + + if (cursor != 0) throw ExceptionFactory.NoCursor(RedisCommand.HGETALL); + + if (pattern.IsNull) return CursorEnumerable.From(this, server, HashGetAllAsync(key, flags), pageOffset); + throw ExceptionFactory.NotSupported(true, RedisCommand.HSCAN); + } + + IEnumerable IDatabase.HashScanNoValues(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => HashScanNoValuesAsync(key, pattern, pageSize, cursor, pageOffset, flags); + + IAsyncEnumerable IDatabaseAsync.HashScanNoValuesAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => HashScanNoValuesAsync(key, pattern, pageSize, cursor, pageOffset, flags); + + private CursorEnumerable HashScanNoValuesAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + { + var scan = TryScan(key, pattern, pageSize, cursor, pageOffset, flags, RedisCommand.HSCAN, SetScanResultProcessor.Default, out var server, true); + if (scan != null) return scan; + + if (cursor != 0) throw ExceptionFactory.NoCursor(RedisCommand.HKEYS); + + if (pattern.IsNull) return CursorEnumerable.From(this, server, HashKeysAsync(key, flags), pageOffset); + throw ExceptionFactory.NotSupported(true, RedisCommand.HSCAN); + } + + public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrNotExists(when); + var msg = value.IsNull + ? Message.Create(Database, flags, RedisCommand.HDEL, key, hashField) + : Message.Create(Database, flags, when == When.Always ? RedisCommand.HSET : RedisCommand.HSETNX, key, hashField, value); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public void HashSet(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None) + { + var msg = GetHashSetMessage(key, hashFields, flags); + if (msg == null) return; + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public long HashStringLength(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HSTRLEN, key, hashField); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task HashSetAsync(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrNotExists(when); + var msg = value.IsNull + ? Message.Create(Database, flags, RedisCommand.HDEL, key, hashField) + : Message.Create(Database, flags, when == When.Always ? RedisCommand.HSET : RedisCommand.HSETNX, key, hashField, value); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task HashStringLengthAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HSTRLEN, key, hashField); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None) + { + var msg = GetHashSetMessage(key, hashFields, flags); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public Task HashSetIfNotExistsAsync(RedisKey key, RedisValue hashField, RedisValue value, CommandFlags flags) + { + var msg = Message.Create(Database, flags, RedisCommand.HSETNX, key, hashField, value); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public RedisValue[] HashValues(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HVALS, key); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task HashValuesAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.HVALS, key); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public bool HyperLogLogAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, value); + return ExecuteSync(cmd, ResultProcessor.Boolean); + } + + public bool HyperLogLogAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + { + var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, values); + return ExecuteSync(cmd, ResultProcessor.Boolean); + } + + public Task HyperLogLogAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, value); + return ExecuteAsync(cmd, ResultProcessor.Boolean); + } + + public Task HyperLogLogAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + { + var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, values); + return ExecuteAsync(cmd, ResultProcessor.Boolean); + } + + public long HyperLogLogLength(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var features = GetFeatures(key, flags, RedisCommand.PFCOUNT, out ServerEndPoint? server); + var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, key); + // technically a write / primary-only command until 2.8.18 + if (server != null && !features.HyperLogLogCountReplicaSafe) cmd.SetPrimaryOnly(); + return ExecuteSync(cmd, ResultProcessor.Int64, server); + } + + public long HyperLogLogLength(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + ServerEndPoint? server = null; + var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, keys); + if (keys.Length != 0) + { + var features = GetFeatures(keys[0], flags, RedisCommand.PFCOUNT, out server); + // technically a write / primary-only command until 2.8.18 + if (server != null && !features.HyperLogLogCountReplicaSafe) cmd.SetPrimaryOnly(); + } + return ExecuteSync(cmd, ResultProcessor.Int64, server); + } + + public Task HyperLogLogLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var features = GetFeatures(key, flags, RedisCommand.PFCOUNT, out ServerEndPoint? server); + var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, key); + // technically a write / primary-only command until 2.8.18 + if (server != null && !features.HyperLogLogCountReplicaSafe) cmd.SetPrimaryOnly(); + return ExecuteAsync(cmd, ResultProcessor.Int64, server); + } + + public Task HyperLogLogLengthAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + ServerEndPoint? server = null; + var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, keys); + if (keys.Length != 0) + { + var features = GetFeatures(keys[0], flags, RedisCommand.PFCOUNT, out server); + // technically a write / primary-only command until 2.8.18 + if (server != null && !features.HyperLogLogCountReplicaSafe) cmd.SetPrimaryOnly(); + } + return ExecuteAsync(cmd, ResultProcessor.Int64, server); + } + + public void HyperLogLogMerge(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + { + var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, first, second); + ExecuteSync(cmd, ResultProcessor.DemandOK); + } + + public void HyperLogLogMerge(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) + { + var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, sourceKeys); + ExecuteSync(cmd, ResultProcessor.DemandOK); + } + + public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + { + var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, first, second); + return ExecuteAsync(cmd, ResultProcessor.DemandOK); + } + + public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) + { + var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, sourceKeys); + return ExecuteAsync(cmd, ResultProcessor.DemandOK); + } + + public EndPoint? IdentifyEndpoint(RedisKey key = default, CommandFlags flags = CommandFlags.None) + { + var msg = key.IsNull ? Message.Create(-1, flags, RedisCommand.PING) : Message.Create(Database, flags, RedisCommand.EXISTS, key); + return ExecuteSync(msg, ResultProcessor.ConnectionIdentity); + } + + public Task IdentifyEndpointAsync(RedisKey key = default, CommandFlags flags = CommandFlags.None) + { + var msg = key.IsNull ? Message.Create(-1, flags, RedisCommand.PING) : Message.Create(Database, flags, RedisCommand.EXISTS, key); + return ExecuteAsync(msg, ResultProcessor.ConnectionIdentity); + } + + public bool IsConnected(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var server = multiplexer.SelectServer(RedisCommand.PING, flags, key); + return server?.IsConnected == true; + } + + public bool KeyCopy(RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase = -1, bool replace = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetCopyMessage(sourceKey, destinationKey, destinationDatabase, replace, flags); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task KeyCopyAsync(RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase = -1, bool replace = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetCopyMessage(sourceKey, destinationKey, destinationDatabase, replace, flags); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public bool KeyDelete(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var cmd = GetDeleteCommand(key, flags, out var server); + var msg = Message.Create(Database, flags, cmd, key); + return ExecuteSync(msg, ResultProcessor.DemandZeroOrOne, server); + } + + public long KeyDelete(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (keys.Length > 0) + { + var cmd = GetDeleteCommand(keys[0], flags, out var server); + var msg = keys.Length == 0 ? null : Message.Create(Database, flags, cmd, keys); + return ExecuteSync(msg, ResultProcessor.Int64, server); + } + return 0; + } + + public Task KeyDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var cmd = GetDeleteCommand(key, flags, out var server); + var msg = Message.Create(Database, flags, cmd, key); + return ExecuteAsync(msg, ResultProcessor.DemandZeroOrOne, server); + } + + public Task KeyDeleteAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (keys.Length > 0) + { + var cmd = GetDeleteCommand(keys[0], flags, out var server); + var msg = keys.Length == 0 ? null : Message.Create(Database, flags, cmd, keys); + return ExecuteAsync(msg, ResultProcessor.Int64, server); + } + return CompletedTask.Default(0); + } + + private RedisCommand GetDeleteCommand(RedisKey key, CommandFlags flags, out ServerEndPoint? server) + { + var features = GetFeatures(key, flags, RedisCommand.UNLINK, out server); + if (server != null && features.Unlink && multiplexer.CommandMap.IsAvailable(RedisCommand.UNLINK)) + { + return RedisCommand.UNLINK; + } + return RedisCommand.DEL; + } + + public byte[]? KeyDump(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.DUMP, key); + return ExecuteSync(msg, ResultProcessor.ByteArray); + } + + public Task KeyDumpAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.DUMP, key); + return ExecuteAsync(msg, ResultProcessor.ByteArray); + } + + public string? KeyEncoding(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.ENCODING, key); + return ExecuteSync(msg, ResultProcessor.String); + } + + public Task KeyEncodingAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.ENCODING, key); + return ExecuteAsync(msg, ResultProcessor.String); + } + + public bool KeyExists(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.EXISTS, key); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public long KeyExists(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.EXISTS, keys); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task KeyExistsAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.EXISTS, key); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task KeyExistsAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.EXISTS, keys); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) => + KeyExpire(key, expiry, ExpireWhen.Always, flags); + + public bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None) => + KeyExpire(key, expiry, ExpireWhen.Always, flags); + + public bool KeyExpire(RedisKey key, TimeSpan? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None) + { + var msg = GetExpiryMessage(key, flags, expiry, when, out ServerEndPoint? server); + return ExecuteSync(msg, ResultProcessor.Boolean, server: server); + } + + public bool KeyExpire(RedisKey key, DateTime? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None) + { + var msg = GetExpiryMessage(key, flags, expiry, when, out ServerEndPoint? server); + return ExecuteSync(msg, ResultProcessor.Boolean, server: server); + } + + public Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) => + KeyExpireAsync(key, expiry, ExpireWhen.Always, flags); + + public Task KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None) => + KeyExpireAsync(key, expiry, ExpireWhen.Always, flags); + + public Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None) + { + var msg = GetExpiryMessage(key, flags, expiry, when, out ServerEndPoint? server); + return ExecuteAsync(msg, ResultProcessor.Boolean, server: server); + } + + public Task KeyExpireAsync(RedisKey key, DateTime? expire, ExpireWhen when, CommandFlags flags = CommandFlags.None) + { + var msg = GetExpiryMessage(key, flags, expire, when, out ServerEndPoint? server); + return ExecuteAsync(msg, ResultProcessor.Boolean, server: server); + } + + public DateTime? KeyExpireTime(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.PEXPIRETIME, key); + return ExecuteSync(msg, ResultProcessor.NullableDateTimeFromMilliseconds); + } + + public Task KeyExpireTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.PEXPIRETIME, key); + return ExecuteAsync(msg, ResultProcessor.NullableDateTimeFromMilliseconds); + } + + public long? KeyFrequency(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.FREQ, key); + return ExecuteSync(msg, ResultProcessor.NullableInt64); + } + + public Task KeyFrequencyAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.FREQ, key); + return ExecuteAsync(msg, ResultProcessor.NullableInt64); + } + + public TimeSpan? KeyIdleTime(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.IDLETIME, key); + return ExecuteSync(msg, ResultProcessor.TimeSpanFromSeconds); + } + + public Task KeyIdleTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.IDLETIME, key); + return ExecuteAsync(msg, ResultProcessor.TimeSpanFromSeconds); + } + + public void KeyMigrate(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) + { + if (timeoutMilliseconds <= 0) timeoutMilliseconds = multiplexer.TimeoutMilliseconds; + var msg = new KeyMigrateCommandMessage(Database, key, toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task KeyMigrateAsync(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) + { + if (timeoutMilliseconds <= 0) timeoutMilliseconds = multiplexer.TimeoutMilliseconds; + var msg = new KeyMigrateCommandMessage(Database, key, toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + private sealed class KeyMigrateCommandMessage : Message.CommandKeyBase // MIGRATE is atypical + { + private readonly MigrateOptions migrateOptions; + private readonly int timeoutMilliseconds; + private readonly int toDatabase; + private readonly RedisValue toHost, toPort; + + public KeyMigrateCommandMessage(int db, RedisKey key, EndPoint toServer, int toDatabase, int timeoutMilliseconds, MigrateOptions migrateOptions, CommandFlags flags) + : base(db, flags, RedisCommand.MIGRATE, key) + { + if (toServer == null) throw new ArgumentNullException(nameof(toServer)); + if (!Format.TryGetHostPort(toServer, out string? toHost, out int? toPort)) throw new ArgumentException($"Couldn't get host and port from {toServer}", nameof(toServer)); + this.toHost = toHost; + this.toPort = toPort; + if (toDatabase < 0) throw new ArgumentOutOfRangeException(nameof(toDatabase)); + this.toDatabase = toDatabase; + this.timeoutMilliseconds = timeoutMilliseconds; + this.migrateOptions = migrateOptions; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + bool isCopy = (migrateOptions & MigrateOptions.Copy) != 0; + bool isReplace = (migrateOptions & MigrateOptions.Replace) != 0; + physical.WriteHeader(Command, 5 + (isCopy ? 1 : 0) + (isReplace ? 1 : 0)); + physical.WriteBulkString(toHost); + physical.WriteBulkString(toPort); + physical.Write(Key); + physical.WriteBulkString(toDatabase); + physical.WriteBulkString(timeoutMilliseconds); + if (isCopy) physical.WriteBulkString(RedisLiterals.COPY); + if (isReplace) physical.WriteBulkString(RedisLiterals.REPLACE); + } + + public override int ArgCount + { + get + { + bool isCopy = (migrateOptions & MigrateOptions.Copy) != 0; + bool isReplace = (migrateOptions & MigrateOptions.Replace) != 0; + return 5 + (isCopy ? 1 : 0) + (isReplace ? 1 : 0); + } + } + } + + public bool KeyMove(RedisKey key, int database, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.MOVE, key, database); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task KeyMoveAsync(RedisKey key, int database, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.MOVE, key, database); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public bool KeyPersist(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.PERSIST, key); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.PERSIST, key); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public RedisKey KeyRandom(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.RANDOMKEY); + return ExecuteSync(msg, ResultProcessor.RedisKey); + } + + public Task KeyRandomAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.RANDOMKEY); + return ExecuteAsync(msg, ResultProcessor.RedisKey); + } + + public long? KeyRefCount(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.REFCOUNT, key); + return ExecuteSync(msg, ResultProcessor.NullableInt64); + } + + public Task KeyRefCountAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.REFCOUNT, key); + return ExecuteAsync(msg, ResultProcessor.NullableInt64); + } + + public bool KeyRename(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrNotExists(when); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RENAME : RedisCommand.RENAMENX, key, newKey); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task KeyRenameAsync(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrNotExists(when); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RENAME : RedisCommand.RENAMENX, key, newKey); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public void KeyRestore(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetRestoreMessage(key, value, expiry, flags); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetRestoreMessage(key, value, expiry, flags); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public TimeSpan? KeyTimeToLive(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var features = GetFeatures(key, flags, RedisCommand.TTL, out ServerEndPoint? server); + Message msg; + if (server != null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(RedisCommand.PTTL)) + { + msg = Message.Create(Database, flags, RedisCommand.PTTL, key); + return ExecuteSync(msg, ResultProcessor.TimeSpanFromMilliseconds, server); + } + msg = Message.Create(Database, flags, RedisCommand.TTL, key); + return ExecuteSync(msg, ResultProcessor.TimeSpanFromSeconds); + } + + public Task KeyTimeToLiveAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var features = GetFeatures(key, flags, RedisCommand.TTL, out ServerEndPoint? server); + Message msg; + if (server != null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(RedisCommand.PTTL)) + { + msg = Message.Create(Database, flags, RedisCommand.PTTL, key); + return ExecuteAsync(msg, ResultProcessor.TimeSpanFromMilliseconds, server); + } + msg = Message.Create(Database, flags, RedisCommand.TTL, key); + return ExecuteAsync(msg, ResultProcessor.TimeSpanFromSeconds); + } + + public RedisType KeyType(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.TYPE, key); + return ExecuteSync(msg, ResultProcessor.RedisType); + } + + public Task KeyTypeAsync(RedisKey key, CommandFlags flags) + { + var msg = Message.Create(Database, flags, RedisCommand.TYPE, key); + return ExecuteAsync(msg, ResultProcessor.RedisType); + } + + public RedisValue ListGetByIndex(RedisKey key, long index, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LINDEX, key, index); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task ListGetByIndexAsync(RedisKey key, long index, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LINDEX, key, index); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public long ListInsertAfter(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.AFTER, pivot, value); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task ListInsertAfterAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.AFTER, pivot, value); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long ListInsertBefore(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.BEFORE, pivot, value); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task ListInsertBeforeAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.BEFORE, pivot, value); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public RedisValue ListLeftPop(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LPOP, key); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public RedisValue[] ListLeftPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LPOP, key, count); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public ListPopResult ListLeftPop(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) + { + var msg = GetListMultiPopMessage(keys, RedisLiterals.LEFT, count, flags); + return ExecuteSync(msg, ResultProcessor.ListPopResult, defaultValue: ListPopResult.Null); + } + + public long ListPosition(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) + { + var msg = CreateListPositionMessage(Database, flags, key, element, rank, maxLength); + return ExecuteSync(msg, ResultProcessor.Int64DefaultNegativeOne, defaultValue: -1); + } + + public long[] ListPositions(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) + { + var msg = CreateListPositionMessage(Database, flags, key, element, rank, maxLength, count); + return ExecuteSync(msg, ResultProcessor.Int64Array, defaultValue: Array.Empty()); + } + + public Task ListLeftPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LPOP, key); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task ListLeftPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LPOP, key, count); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task ListLeftPopAsync(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) + { + var msg = GetListMultiPopMessage(keys, RedisLiterals.LEFT, count, flags); + return ExecuteAsync(msg, ResultProcessor.ListPopResult, defaultValue: ListPopResult.Null); + } + + public Task ListPositionAsync(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) + { + var msg = CreateListPositionMessage(Database, flags, key, element, rank, maxLength); + return ExecuteAsync(msg, ResultProcessor.Int64DefaultNegativeOne, defaultValue: -1); + } + + public Task ListPositionsAsync(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) + { + var msg = CreateListPositionMessage(Database, flags, key, element, rank, maxLength, count); + return ExecuteAsync(msg, ResultProcessor.Int64Array, defaultValue: Array.Empty()); + } + + public long ListLeftPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrExists(when); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX, key, value); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public long ListLeftPush(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrExists(when); + if (values == null) throw new ArgumentNullException(nameof(values)); + var command = when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX; + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, command, key, values); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public long ListLeftPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + { + if (values == null) throw new ArgumentNullException(nameof(values)); + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, RedisCommand.LPUSH, key, values); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task ListLeftPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrExists(when); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX, key, value); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task ListLeftPushAsync(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrExists(when); + if (values == null) throw new ArgumentNullException(nameof(values)); + var command = when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX; + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, command, key, values); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task ListLeftPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + { + if (values == null) throw new ArgumentNullException(nameof(values)); + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, RedisCommand.LPUSH, key, values); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LLEN, key); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LLEN, key); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public RedisValue ListMove(RedisKey sourceKey, RedisKey destinationKey, ListSide sourceSide, ListSide destinationSide, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LMOVE, sourceKey, destinationKey, sourceSide.ToLiteral(), destinationSide.ToLiteral()); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task ListMoveAsync(RedisKey sourceKey, RedisKey destinationKey, ListSide sourceSide, ListSide destinationSide, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LMOVE, sourceKey, destinationKey, sourceSide.ToLiteral(), destinationSide.ToLiteral()); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LRANGE, key, start, stop); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task ListRangeAsync(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LRANGE, key, start, stop); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public long ListRemove(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LREM, key, count, value); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task ListRemoveAsync(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LREM, key, count, value); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public RedisValue ListRightPop(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.RPOP, key); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public RedisValue[] ListRightPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.RPOP, key, count); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public ListPopResult ListRightPop(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) + { + var msg = GetListMultiPopMessage(keys, RedisLiterals.RIGHT, count, flags); + return ExecuteSync(msg, ResultProcessor.ListPopResult, defaultValue: ListPopResult.Null); + } + + public Task ListRightPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.RPOP, key); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task ListRightPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.RPOP, key, count); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task ListRightPopAsync(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) + { + var msg = GetListMultiPopMessage(keys, RedisLiterals.RIGHT, count, flags); + return ExecuteAsync(msg, ResultProcessor.ListPopResult, defaultValue: ListPopResult.Null); + } + + public RedisValue ListRightPopLeftPush(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.RPOPLPUSH, source, destination); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task ListRightPopLeftPushAsync(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.RPOPLPUSH, source, destination); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public long ListRightPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrExists(when); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX, key, value); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public long ListRightPush(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrExists(when); + if (values == null) throw new ArgumentNullException(nameof(values)); + var command = when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX; + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, command, key, values); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public long ListRightPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + { + if (values == null) throw new ArgumentNullException(nameof(values)); + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, RedisCommand.RPUSH, key, values); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task ListRightPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrExists(when); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX, key, value); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task ListRightPushAsync(RedisKey key, RedisValue[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrExists(when); + if (values == null) throw new ArgumentNullException(nameof(values)); + var command = when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX; + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, command, key, values); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task ListRightPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + { + if (values == null) throw new ArgumentNullException(nameof(values)); + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, RedisCommand.RPUSH, key, values); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public void ListSetByIndex(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LSET, key, index, value); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task ListSetByIndexAsync(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LSET, key, index, value); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public void ListTrim(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LTRIM, key, start, stop); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task ListTrimAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LTRIM, key, start, stop); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public bool LockExtend(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) + { + if (value.IsNull) throw new ArgumentNullException(nameof(value)); + var tran = GetLockExtendTransaction(key, value, expiry); + + if (tran != null) return tran.Execute(flags); + + // without transactions (twemproxy etc), we can't enforce the "value" part + return KeyExpire(key, expiry, flags); + } + + public Task LockExtendAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) + { + if (value.IsNull) throw new ArgumentNullException(nameof(value)); + var tran = GetLockExtendTransaction(key, value, expiry); + if (tran != null) return tran.ExecuteAsync(flags); + + // without transactions (twemproxy etc), we can't enforce the "value" part + return KeyExpireAsync(key, expiry, flags); + } + + public RedisValue LockQuery(RedisKey key, CommandFlags flags = CommandFlags.None) + { + return StringGet(key, flags); + } + + public Task LockQueryAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + return StringGetAsync(key, flags); + } + + public bool LockRelease(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + if (value.IsNull) throw new ArgumentNullException(nameof(value)); + var tran = GetLockReleaseTransaction(key, value); + if (tran != null) return tran.Execute(flags); + + // without transactions (twemproxy etc), we can't enforce the "value" part + return KeyDelete(key, flags); + } + + public Task LockReleaseAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + if (value.IsNull) throw new ArgumentNullException(nameof(value)); + var tran = GetLockReleaseTransaction(key, value); + if (tran != null) return tran.ExecuteAsync(flags); + + // without transactions (twemproxy etc), we can't enforce the "value" part + return KeyDeleteAsync(key, flags); + } + + public bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) + { + if (value.IsNull) throw new ArgumentNullException(nameof(value)); + return StringSet(key, value, expiry, When.NotExists, flags); + } + + public Task LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) + { + if (value.IsNull) throw new ArgumentNullException(nameof(value)); + return StringSetAsync(key, value, expiry, When.NotExists, flags); + } + + public string? StringLongestCommonSubsequence(RedisKey key1, RedisKey key2, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2); + return ExecuteSync(msg, ResultProcessor.String); + } + + public Task StringLongestCommonSubsequenceAsync(RedisKey key1, RedisKey key2, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2); + return ExecuteAsync(msg, ResultProcessor.String); + } + + public long StringLongestCommonSubsequenceLength(RedisKey key1, RedisKey key2, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.LEN); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StringLongestCommonSubsequenceLengthAsync(RedisKey key1, RedisKey key2, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.LEN); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public LCSMatchResult StringLongestCommonSubsequenceWithMatches(RedisKey key1, RedisKey key2, long minSubMatchLength = 0, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.IDX, RedisLiterals.MINMATCHLEN, minSubMatchLength, RedisLiterals.WITHMATCHLEN); + return ExecuteSync(msg, ResultProcessor.LCSMatchResult); + } + + public Task StringLongestCommonSubsequenceWithMatchesAsync(RedisKey key1, RedisKey key2, long minSubMatchLength = 0, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.IDX, RedisLiterals.MINMATCHLEN, minSubMatchLength, RedisLiterals.WITHMATCHLEN); + return ExecuteAsync(msg, ResultProcessor.LCSMatchResult); + } + + public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) + { + if (channel.IsNullOrEmpty) throw new ArgumentNullException(nameof(channel)); + var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message); + // if we're actively subscribed: send via that connection (otherwise, follow normal rules) + return ExecuteSync(msg, ResultProcessor.Int64, server: multiplexer.GetSubscribedServer(channel)); + } + + public Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) + { + if (channel.IsNullOrEmpty) throw new ArgumentNullException(nameof(channel)); + var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message); + // if we're actively subscribed: send via that connection (otherwise, follow normal rules) + return ExecuteAsync(msg, ResultProcessor.Int64, server: multiplexer.GetSubscribedServer(channel)); + } + + public RedisResult Execute(string command, params object[] args) + => Execute(command, args, CommandFlags.None); + + public RedisResult Execute(string command, ICollection args, CommandFlags flags = CommandFlags.None) + { + var msg = new ExecuteMessage(multiplexer?.CommandMap, Database, flags, command, args); + return ExecuteSync(msg, ResultProcessor.ScriptResult)!; + } + + public Task ExecuteAsync(string command, params object[] args) + => ExecuteAsync(command, args, CommandFlags.None); + + public Task ExecuteAsync(string command, ICollection? args, CommandFlags flags = CommandFlags.None) + { + var msg = new ExecuteMessage(multiplexer?.CommandMap, Database, flags, command, args); + return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + + public RedisResult ScriptEvaluate(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL; + var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); + try + { + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + catch (RedisServerException) when (msg.IsScriptUnavailable) + { + // could be a NOSCRIPT; for a sync call, we can re-issue that without problem + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + } + + public RedisResult ScriptEvaluate(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA, hash, keys, values); + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + + public RedisResult ScriptEvaluate(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) + { + return script.Evaluate(this, parameters, null, flags); + } + + public RedisResult ScriptEvaluate(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) + { + return script.Evaluate(this, parameters, withKeyPrefix: null, flags); + } + + public async Task ScriptEvaluateAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL; + var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); + + try + { + return await ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle).ForAwait(); + } + catch (RedisServerException) when (msg.IsScriptUnavailable) + { + // could be a NOSCRIPT; for a sync call, we can re-issue that without problem + return await ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle).ForAwait(); + } + } + + public Task ScriptEvaluateAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA, hash, keys, values); + return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + + public Task ScriptEvaluateAsync(LuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) + { + return script.EvaluateAsync(this, parameters, null, flags); + } + + public Task ScriptEvaluateAsync(LoadedLuaScript script, object? parameters = null, CommandFlags flags = CommandFlags.None) + { + return script.EvaluateAsync(this, parameters, withKeyPrefix: null, flags); + } + + public RedisResult ScriptEvaluateReadOnly(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA_RO : RedisCommand.EVAL_RO; + var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); + try + { + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + catch (RedisServerException) when (msg.IsScriptUnavailable) + { + // could be a NOSCRIPT; for a sync call, we can re-issue that without problem + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + } + + public RedisResult ScriptEvaluateReadOnly(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA_RO, hash, keys, values); + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + + public Task ScriptEvaluateReadOnlyAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA_RO : RedisCommand.EVAL_RO; + var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); + return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + + public Task ScriptEvaluateReadOnlyAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) + { + var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA_RO, hash, keys, values); + return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + + public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SADD, key, value); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + { + if (values.Length == 0) return 0; + var msg = Message.Create(Database, flags, RedisCommand.SADD, key, values); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SetAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SADD, key, value); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task SetAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + { + if (values.Length == 0) return Task.FromResult(0); + var msg = Message.Create(Database, flags, RedisCommand.SADD, key, values); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public RedisValue[] SetCombine(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), first, second); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public RedisValue[] SetCombine(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), keys); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, first, second); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, keys); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, first, second); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, keys); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task SetCombineAsync(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), first, second); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task SetCombineAsync(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), keys); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SISMEMBER, key, value); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SISMEMBER, key, value); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SMISMEMBER, key, values); + return ExecuteSync(msg, ResultProcessor.BooleanArray, defaultValue: Array.Empty()); + } + + public Task SetContainsAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SMISMEMBER, key, values); + return ExecuteAsync(msg, ResultProcessor.BooleanArray, defaultValue: Array.Empty()); + } + + public long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + { + var msg = GetSetIntersectionLengthMessage(keys, limit, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + { + var msg = GetSetIntersectionLengthMessage(keys, limit, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SCARD, key); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SCARD, key); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public RedisValue[] SetMembers(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SMEMBERS, key); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task SetMembersAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SMEMBERS, key); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public bool SetMove(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SMOVE, source, destination, value); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task SetMoveAsync(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SMOVE, source, destination, value); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public RedisValue SetPop(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SPOP, key); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task SetPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SPOP, key); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public RedisValue[] SetPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + if (count == 0) return Array.Empty(); + var msg = count == 1 + ? Message.Create(Database, flags, RedisCommand.SPOP, key) + : Message.Create(Database, flags, RedisCommand.SPOP, key, count); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task SetPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + if (count == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + var msg = count == 1 + ? Message.Create(Database, flags, RedisCommand.SPOP, key) + : Message.Create(Database, flags, RedisCommand.SPOP, key, count); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public RedisValue SetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task SetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public RedisValue[] SetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key, count); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task SetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key, count); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public bool SetRemove(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SREM, key, value); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public long SetRemove(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + { + if (values == null) throw new ArgumentNullException(nameof(values)); + if (values.Length == 0) return 0; + var msg = Message.Create(Database, flags, RedisCommand.SREM, key, values); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SetRemoveAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SREM, key, value); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + { + if (values == null) throw new ArgumentNullException(nameof(values)); + if (values.Length == 0) return CompletedTask.FromResult(0, asyncState); + var msg = Message.Create(Database, flags, RedisCommand.SREM, key, values); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + IEnumerable IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) + => SetScanAsync(key, pattern, pageSize, CursorUtils.Origin, 0, flags); + + IEnumerable IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => SetScanAsync(key, pattern, pageSize, cursor, pageOffset, flags); + + IAsyncEnumerable IDatabaseAsync.SetScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => SetScanAsync(key, pattern, pageSize, cursor, pageOffset, flags); + + private CursorEnumerable SetScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + { + var scan = TryScan(key, pattern, pageSize, cursor, pageOffset, flags, RedisCommand.SSCAN, SetScanResultProcessor.Default, out var server); + if (scan != null) return scan; + + if (cursor != 0) throw ExceptionFactory.NoCursor(RedisCommand.SMEMBERS); + if (pattern.IsNull) return CursorEnumerable.From(this, server, SetMembersAsync(key, flags), pageOffset); + throw ExceptionFactory.NotSupported(true, RedisCommand.SSCAN); + } + + public RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortMessage(RedisKey.Null, key, skip, take, order, sortType, by, get, flags, out var server); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, server: server, defaultValue: Array.Empty()); + } + + public long SortAndStore(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortMessage(destination, key, skip, take, order, sortType, by, get, flags, out var server); + return ExecuteSync(msg, ResultProcessor.Int64, server); + } + + public Task SortAndStoreAsync(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortMessage(destination, key, skip, take, order, sortType, by, get, flags, out var server); + return ExecuteAsync(msg, ResultProcessor.Int64, server); + } + + public Task SortAsync(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortMessage(RedisKey.Null, key, skip, take, order, sortType, by, get, flags, out var server); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty(), server: server); + } + + public bool SortedSetAdd(RedisKey key, RedisValue member, double score, CommandFlags flags) => + SortedSetAdd(key, member, score, SortedSetWhen.Always, flags); + + public bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None) => + SortedSetAdd(key, member, score, SortedSetWhenExtensions.Parse(when), flags); + + public bool SortedSetAdd(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetAddMessage(key, member, score, when, false, flags); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public bool SortedSetUpdate(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetAddMessage(key, member, score, when, true, flags); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, CommandFlags flags) => + SortedSetAdd(key, values, SortedSetWhen.Always, flags); + + public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) => + SortedSetAdd(key, values, SortedSetWhenExtensions.Parse(when), flags); + + public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetAddMessage(key, values, when, false, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public long SortedSetUpdate(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetAddMessage(key, values, when, true, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, CommandFlags flags) => + SortedSetAddAsync(key, member, score, SortedSetWhen.Always, flags); + + public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None) => + SortedSetAddAsync(key, member, score, SortedSetWhenExtensions.Parse(when), flags); + + public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetAddMessage(key, member, score, when, false, flags); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task SortedSetUpdateAsync(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetAddMessage(key, member, score, when, true, flags); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, CommandFlags flags) => + SortedSetAddAsync(key, values, SortedSetWhen.Always, flags); + + public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) => + SortedSetAddAsync(key, values, SortedSetWhenExtensions.Parse(when), flags); + + public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetAddMessage(key, values, when, false, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task SortedSetUpdateAsync(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetAddMessage(key, values, when, true, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public RedisValue[] SortedSetCombine(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetCombineCommandMessage(operation, keys, weights, aggregate, withScores: false, flags); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task SortedSetCombineAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetCombineCommandMessage(operation, keys, weights, aggregate, withScores: false, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public SortedSetEntry[] SortedSetCombineWithScores(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetCombineCommandMessage(operation, keys, weights, aggregate, withScores: true, flags); + return ExecuteSync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); + } + + public Task SortedSetCombineWithScoresAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetCombineCommandMessage(operation, keys, weights, aggregate, withScores: true, flags); + return ExecuteAsync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); + } + + public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetCombineAndStoreCommandMessage(operation, destination, new[] { first, second }, null, aggregate, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetCombineAndStoreCommandMessage(operation, destination, keys, weights, aggregate, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetCombineAndStoreCommandMessage(operation, destination, new[] { first, second }, null, aggregate, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetCombineAndStoreCommandMessage(operation, destination, keys, weights, aggregate, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public double SortedSetDecrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) + { + return SortedSetIncrement(key, member, -value, flags); + } + + public Task SortedSetDecrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) + { + return SortedSetIncrementAsync(key, member, -value, flags); + } + + public double SortedSetIncrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZINCRBY, key, value, member); + return ExecuteSync(msg, ResultProcessor.Double); + } + + public Task SortedSetIncrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZINCRBY, key, value, member); + return ExecuteAsync(msg, ResultProcessor.Double); + } + + public long SortedSetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetIntersectionLengthMessage(keys, limit, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SortedSetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetIntersectionLengthMessage(keys, limit, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long SortedSetLength(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetLengthMessage(key, min, max, exclude, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SortedSetLengthAsync(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetLengthMessage(key, min, max, exclude, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public RedisValue SortedSetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public RedisValue[] SortedSetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, count); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public SortedSetEntry[] SortedSetRandomMembersWithScores(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, count, RedisLiterals.WITHSCORES); + return ExecuteSync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); + } + + public Task SortedSetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task SortedSetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, count); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task SortedSetRandomMembersWithScoresAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, count, RedisLiterals.WITHSCORES); + return ExecuteAsync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); + } + + public RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public long SortedSetRangeAndStore( + RedisKey sourceKey, + RedisKey destinationKey, + RedisValue start, + RedisValue stop, + SortedSetOrder sortedSetOrder = SortedSetOrder.ByRank, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long? take = null, + CommandFlags flags = CommandFlags.None) + { + var msg = CreateSortedSetRangeStoreMessage(Database, flags, sourceKey, destinationKey, start, stop, sortedSetOrder, order, exclude, skip, take); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SortedSetRangeByRankAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task SortedSetRangeAndStoreAsync( + RedisKey sourceKey, + RedisKey destinationKey, + RedisValue start, + RedisValue stop, + SortedSetOrder sortedSetOrder = SortedSetOrder.ByRank, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long? take = null, + CommandFlags flags = CommandFlags.None) + { + var msg = CreateSortedSetRangeStoreMessage(Database, flags, sourceKey, destinationKey, start, stop, sortedSetOrder, order, exclude, skip, take); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop, RedisLiterals.WITHSCORES); + return ExecuteSync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); + } + + public Task SortedSetRangeByRankWithScoresAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop, RedisLiterals.WITHSCORES); + return ExecuteAsync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); + } + + public RedisValue[] SortedSetRangeByScore(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetRangeByScoreMessage(key, start, stop, exclude, order, skip, take, flags, false); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task SortedSetRangeByScoreAsync(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetRangeByScoreMessage(key, start, stop, exclude, order, skip, take, flags, false); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public SortedSetEntry[] SortedSetRangeByScoreWithScores(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetRangeByScoreMessage(key, start, stop, exclude, order, skip, take, flags, true); + return ExecuteSync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); + } + + public Task SortedSetRangeByScoreWithScoresAsync(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetRangeByScoreMessage(key, start, stop, exclude, order, skip, take, flags, true); + return ExecuteAsync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); + } + + public long? SortedSetRank(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANK : RedisCommand.ZRANK, key, member); + return ExecuteSync(msg, ResultProcessor.NullableInt64); + } + + public Task SortedSetRankAsync(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANK : RedisCommand.ZRANK, key, member); + return ExecuteAsync(msg, ResultProcessor.NullableInt64); + } + + public bool SortedSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, member); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public long SortedSetRemove(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, members); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SortedSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, member); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task SortedSetRemoveAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, members); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long SortedSetRemoveRangeByRank(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZREMRANGEBYRANK, key, start, stop); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SortedSetRemoveRangeByRankAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZREMRANGEBYRANK, key, start, stop); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long SortedSetRemoveRangeByScore(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetRemoveRangeByScoreMessage(key, start, stop, exclude, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetRemoveRangeByScoreMessage(key, start, stop, exclude, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + IEnumerable IDatabase.SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) + => SortedSetScanAsync(key, pattern, pageSize, CursorUtils.Origin, 0, flags); + + IEnumerable IDatabase.SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => SortedSetScanAsync(key, pattern, pageSize, cursor, pageOffset, flags); + + IAsyncEnumerable IDatabaseAsync.SortedSetScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => SortedSetScanAsync(key, pattern, pageSize, cursor, pageOffset, flags); + + private CursorEnumerable SortedSetScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + { + var scan = TryScan(key, pattern, pageSize, cursor, pageOffset, flags, RedisCommand.ZSCAN, SortedSetScanResultProcessor.Default, out var server); + if (scan != null) return scan; + + if (cursor != 0) throw ExceptionFactory.NoCursor(RedisCommand.ZRANGE); + if (pattern.IsNull) return CursorEnumerable.From(this, server, SortedSetRangeByRankWithScoresAsync(key, flags: flags), pageOffset); + throw ExceptionFactory.NotSupported(true, RedisCommand.ZSCAN); + } + + public double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZSCORE, key, member); + return ExecuteSync(msg, ResultProcessor.NullableDouble); + } + + public double?[] SortedSetScores(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZMSCORE, key, members); + return ExecuteSync(msg, ResultProcessor.NullableDoubleArray, defaultValue: Array.Empty()); + } + + public Task SortedSetScoreAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZSCORE, key, member); + return ExecuteAsync(msg, ResultProcessor.NullableDouble); + } + + public Task SortedSetScoresAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.ZMSCORE, key, members); + return ExecuteAsync(msg, ResultProcessor.NullableDoubleArray, defaultValue: Array.Empty()); + } + + public SortedSetEntry? SortedSetPop(RedisKey key, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key); + return ExecuteSync(msg, ResultProcessor.SortedSetEntry); + } + + public Task SortedSetPopAsync(RedisKey key, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key); + return ExecuteAsync(msg, ResultProcessor.SortedSetEntry); + } + + public SortedSetEntry[] SortedSetPop(RedisKey key, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + if (count == 0) return Array.Empty(); + var msg = count == 1 + ? Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key) + : Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key, count); + return ExecuteSync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); + } + + public SortedSetPopResult SortedSetPop(RedisKey[] keys, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetMultiPopMessage(keys, order, count, flags); + return ExecuteSync(msg, ResultProcessor.SortedSetPopResult, defaultValue: SortedSetPopResult.Null); + } + + public Task SortedSetPopAsync(RedisKey key, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + if (count == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + var msg = count == 1 + ? Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key) + : Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key, count); + return ExecuteAsync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); + } + + public Task SortedSetPopAsync(RedisKey[] keys, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + var msg = GetSortedSetMultiPopMessage(keys, order, count, flags); + return ExecuteAsync(msg, ResultProcessor.SortedSetPopResult, defaultValue: SortedSetPopResult.Null); + } + + public long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAcknowledgeMessage(key, groupName, messageId, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAcknowledgeMessage(key, groupName, messageId, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long StreamAcknowledge(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAcknowledgeMessage(key, groupName, messageIds, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StreamAcknowledgeAsync(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAcknowledgeMessage(key, groupName, messageIds, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public StreamTrimResult StreamAcknowledgeAndDelete(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue messageId, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAcknowledgeAndDeleteMessage(key, groupName, mode, messageId, flags); + return ExecuteSync(msg, ResultProcessor.StreamTrimResult); + } + + public Task StreamAcknowledgeAndDeleteAsync(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue messageId, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAcknowledgeAndDeleteMessage(key, groupName, mode, messageId, flags); + return ExecuteAsync(msg, ResultProcessor.StreamTrimResult); + } + + public StreamTrimResult[] StreamAcknowledgeAndDelete(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAcknowledgeAndDeleteMessage(key, groupName, mode, messageIds, flags); + return ExecuteSync(msg, ResultProcessor.StreamTrimResultArray)!; + } + + public Task StreamAcknowledgeAndDeleteAsync(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAcknowledgeAndDeleteMessage(key, groupName, mode, messageIds, flags); + return ExecuteAsync(msg, ResultProcessor.StreamTrimResultArray)!; + } + + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) + => StreamAdd(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); + + public RedisValue StreamAdd(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAddMessage( + key, + messageId ?? StreamConstants.AutoGeneratedId, + maxLength, + useApproximateMaxLength, + new NameValueEntry(streamField, streamValue), + limit, + mode, + flags); + + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) + => StreamAddAsync(key, streamField, streamValue, messageId, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); + + public Task StreamAddAsync(RedisKey key, RedisValue streamField, RedisValue streamValue, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAddMessage( + key, + messageId ?? StreamConstants.AutoGeneratedId, + maxLength, + useApproximateMaxLength, + new NameValueEntry(streamField, streamValue), + limit, + mode, + flags); + + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) + => StreamAdd(key, streamPairs, messageId, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); + + public RedisValue StreamAdd(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAddMessage( + key, + messageId ?? StreamConstants.AutoGeneratedId, + maxLength, + useApproximateMaxLength, + streamPairs, + limit, + mode, + flags); + + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId, int? maxLength, bool useApproximateMaxLength, CommandFlags flags) + => StreamAddAsync(key, streamPairs, messageId, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); + + public Task StreamAddAsync(RedisKey key, NameValueEntry[] streamPairs, RedisValue? messageId = null, long? maxLength = null, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAddMessage( + key, + messageId ?? StreamConstants.AutoGeneratedId, + maxLength, + useApproximateMaxLength, + streamPairs, + limit, + mode, + flags); + + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public StreamAutoClaimResult StreamAutoClaim(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAutoClaimMessage(key, consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, idsOnly: false, flags); + return ExecuteSync(msg, ResultProcessor.StreamAutoClaim, defaultValue: StreamAutoClaimResult.Null); + } + + public Task StreamAutoClaimAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAutoClaimMessage(key, consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, idsOnly: false, flags); + return ExecuteAsync(msg, ResultProcessor.StreamAutoClaim, defaultValue: StreamAutoClaimResult.Null); + } + + public StreamAutoClaimIdsOnlyResult StreamAutoClaimIdsOnly(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAutoClaimMessage(key, consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, idsOnly: true, flags); + return ExecuteSync(msg, ResultProcessor.StreamAutoClaimIdsOnly, defaultValue: StreamAutoClaimIdsOnlyResult.Null); + } + + public Task StreamAutoClaimIdsOnlyAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamAutoClaimMessage(key, consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, idsOnly: true, flags); + return ExecuteAsync(msg, ResultProcessor.StreamAutoClaimIdsOnly, defaultValue: StreamAutoClaimIdsOnlyResult.Null); + } + + public StreamEntry[] StreamClaim(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamClaimMessage( + key, + consumerGroup, + claimingConsumer, + minIdleTimeInMs, + messageIds, + returnJustIds: false, + flags: flags); + + return ExecuteSync(msg, ResultProcessor.SingleStream, defaultValue: Array.Empty()); + } + + public Task StreamClaimAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamClaimMessage( + key, + consumerGroup, + claimingConsumer, + minIdleTimeInMs, + messageIds, + returnJustIds: false, + flags: flags); + + return ExecuteAsync(msg, ResultProcessor.SingleStream, defaultValue: Array.Empty()); + } + + public RedisValue[] StreamClaimIdsOnly(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamClaimMessage( + key, + consumerGroup, + claimingConsumer, + minIdleTimeInMs, + messageIds, + returnJustIds: true, + flags: flags); + + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task StreamClaimIdsOnlyAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamClaimMessage( + key, + consumerGroup, + claimingConsumer, + minIdleTimeInMs, + messageIds, + returnJustIds: true, + flags: flags); + + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public bool StreamConsumerGroupSetPosition(RedisKey key, RedisValue groupName, RedisValue position, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create( + Database, + flags, + RedisCommand.XGROUP, + new RedisValue[] + { + StreamConstants.SetId, + key.AsRedisValue(), + groupName, + StreamPosition.Resolve(position, RedisCommand.XGROUP), + }); + + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task StreamConsumerGroupSetPositionAsync(RedisKey key, RedisValue groupName, RedisValue position, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create( + Database, + flags, + RedisCommand.XGROUP, + new RedisValue[] + { + StreamConstants.SetId, + key.AsRedisValue(), + groupName, + StreamPosition.Resolve(position, RedisCommand.XGROUP), + }); + + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue? position, CommandFlags flags) + { + return StreamCreateConsumerGroup( + key, + groupName, + position, + true, + flags); + } + + public bool StreamCreateConsumerGroup(RedisKey key, RedisValue groupName, RedisValue? position = null, bool createStream = true, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamCreateConsumerGroupMessage( + key, + groupName, + position, + createStream, + flags); + + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue? position, CommandFlags flags) + { + return StreamCreateConsumerGroupAsync( + key, + groupName, + position, + true, + flags); + } + + public Task StreamCreateConsumerGroupAsync(RedisKey key, RedisValue groupName, RedisValue? position = null, bool createStream = true, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamCreateConsumerGroupMessage( + key, + groupName, + position, + createStream, + flags); + + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public StreamConsumerInfo[] StreamConsumerInfo(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create( + Database, + flags, + RedisCommand.XINFO, + new RedisValue[] + { + StreamConstants.Consumers, + key.AsRedisValue(), + groupName, + }); + + return ExecuteSync(msg, ResultProcessor.StreamConsumerInfo, defaultValue: Array.Empty()); + } + + public Task StreamConsumerInfoAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create( + Database, + flags, + RedisCommand.XINFO, + new RedisValue[] + { + StreamConstants.Consumers, + key.AsRedisValue(), + groupName, + }); + + return ExecuteAsync(msg, ResultProcessor.StreamConsumerInfo, defaultValue: Array.Empty()); + } + + public StreamGroupInfo[] StreamGroupInfo(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Groups, key); + return ExecuteSync(msg, ResultProcessor.StreamGroupInfo, defaultValue: Array.Empty()); + } + + public Task StreamGroupInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Groups, key); + return ExecuteAsync(msg, ResultProcessor.StreamGroupInfo, defaultValue: Array.Empty()); + } + + public StreamInfo StreamInfo(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Stream, key); + return ExecuteSync(msg, ResultProcessor.StreamInfo); + } + + public Task StreamInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Stream, key); + return ExecuteAsync(msg, ResultProcessor.StreamInfo); + } + + public long StreamLength(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XLEN, key); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XLEN, key); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long StreamDelete(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XDEL, key, messageIds); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public StreamTrimResult[] StreamDelete(RedisKey key, RedisValue[] messageIds, StreamTrimMode mode, CommandFlags flags) + { + var msg = GetStreamDeleteExMessage(key, messageIds, mode, flags); + return ExecuteSync(msg, ResultProcessor.StreamTrimResultArray)!; + } + + private Message GetStreamDeleteExMessage(RedisKey key, RedisValue[] messageIds, StreamTrimMode mode, CommandFlags flags) + { + if (messageIds == null) throw new ArgumentNullException(nameof(messageIds)); + if (messageIds.Length == 0) throw new ArgumentOutOfRangeException(nameof(messageIds), "messageIds must contain at least one item."); + + // avoid array for single message case + if (messageIds.Length == 1) + { + return Message.Create(Database, flags, RedisCommand.XDELEX, key, StreamConstants.GetMode(mode), StreamConstants.Ids, 1, messageIds[0]); + } + + var values = new RedisValue[messageIds.Length + 3]; + + var offset = 0; + values[offset++] = StreamConstants.GetMode(mode); + values[offset++] = StreamConstants.Ids; + values[offset++] = messageIds.Length; + messageIds.AsSpan().CopyTo(values.AsSpan(offset)); + Debug.Assert(offset + messageIds.Length == values.Length); + return Message.Create(Database, flags, RedisCommand.XDELEX, key, values); + } + + public Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XDEL, key, messageIds); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, StreamTrimMode mode, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamDeleteExMessage(key, messageIds, mode, flags); + return ExecuteAsync(msg, ResultProcessor.StreamTrimResultArray)!; + } + + public long StreamDeleteConsumer(RedisKey key, RedisValue groupName, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create( + Database, + flags, + RedisCommand.XGROUP, + new RedisValue[] + { + StreamConstants.DeleteConsumer, + key.AsRedisValue(), + groupName, + consumerName, + }); + + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StreamDeleteConsumerAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create( + Database, + flags, + RedisCommand.XGROUP, + new RedisValue[] + { + StreamConstants.DeleteConsumer, + key.AsRedisValue(), + groupName, + consumerName, + }); + + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public bool StreamDeleteConsumerGroup(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create( + Database, + flags, + RedisCommand.XGROUP, + new RedisValue[] + { + StreamConstants.Destroy, + key.AsRedisValue(), + groupName, + }); + + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task StreamDeleteConsumerGroupAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create( + Database, + flags, + RedisCommand.XGROUP, + new RedisValue[] + { + StreamConstants.Destroy, + key.AsRedisValue(), + groupName, + }); + + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public StreamPendingInfo StreamPending(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XPENDING, key, groupName); + return ExecuteSync(msg, ResultProcessor.StreamPendingInfo); + } + + public Task StreamPendingAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.XPENDING, key, groupName); + return ExecuteAsync(msg, ResultProcessor.StreamPendingInfo); + } + + public StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, CommandFlags flags = CommandFlags.None) => + StreamPendingMessages(key, groupName, count, consumerName, minId, maxId, null, flags); + + public StreamPendingMessageInfo[] StreamPendingMessages(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, long? minIdleTimeInMs = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamPendingMessagesMessage( + key, + groupName, + minId, + maxId, + count, + consumerName, + minIdleTimeInMs, + flags); + + return ExecuteSync(msg, ResultProcessor.StreamPendingMessages, defaultValue: Array.Empty()); + } + + public Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, CommandFlags flags = CommandFlags.None) => + StreamPendingMessagesAsync(key, groupName, count, consumerName, minId, maxId, null, flags); + + public Task StreamPendingMessagesAsync(RedisKey key, RedisValue groupName, int count, RedisValue consumerName, RedisValue? minId = null, RedisValue? maxId = null, long? minIdleTimeInMs = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamPendingMessagesMessage( + key, + groupName, + minId, + maxId, + count, + consumerName, + minIdleTimeInMs, + flags); + + return ExecuteAsync(msg, ResultProcessor.StreamPendingMessages, defaultValue: Array.Empty()); + } + + public StreamEntry[] StreamRange(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamRangeMessage( + key, + minId, + maxId, + count, + messageOrder, + flags); + + return ExecuteSync(msg, ResultProcessor.SingleStream, defaultValue: Array.Empty()); + } + + public Task StreamRangeAsync(RedisKey key, RedisValue? minId = null, RedisValue? maxId = null, int? count = null, Order messageOrder = Order.Ascending, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamRangeMessage( + key, + minId, + maxId, + count, + messageOrder, + flags); + + return ExecuteAsync(msg, ResultProcessor.SingleStream, defaultValue: Array.Empty()); + } + + public StreamEntry[] StreamRead(RedisKey key, RedisValue position, int? count = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetSingleStreamReadMessage( + key, + StreamPosition.Resolve(position, RedisCommand.XREAD), + count, + flags); + + return ExecuteSync(msg, ResultProcessor.SingleStreamWithNameSkip, defaultValue: Array.Empty()); + } + + public Task StreamReadAsync(RedisKey key, RedisValue position, int? count = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetSingleStreamReadMessage( + key, + StreamPosition.Resolve(position, RedisCommand.XREAD), + count, + flags); + + return ExecuteAsync(msg, ResultProcessor.SingleStreamWithNameSkip, defaultValue: Array.Empty()); + } + + public RedisStream[] StreamRead(StreamPosition[] streamPositions, int? countPerStream = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetMultiStreamReadMessage(streamPositions, countPerStream, flags); + return ExecuteSync(msg, ResultProcessor.MultiStream, defaultValue: Array.Empty()); + } + + public Task StreamReadAsync(StreamPosition[] streamPositions, int? countPerStream = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetMultiStreamReadMessage(streamPositions, countPerStream, flags); + return ExecuteAsync(msg, ResultProcessor.MultiStream, defaultValue: Array.Empty()); + } + + public StreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position, int? count, CommandFlags flags) + { + return StreamReadGroup( + key, + groupName, + consumerName, + position, + count, + false, + flags); + } + + public StreamEntry[] StreamReadGroup(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position = null, int? count = null, bool noAck = false, CommandFlags flags = CommandFlags.None) + { + var actualPosition = position ?? StreamPosition.NewMessages; + + var msg = GetStreamReadGroupMessage( + key, + groupName, + consumerName, + StreamPosition.Resolve(actualPosition, RedisCommand.XREADGROUP), + count, + noAck, + flags); + + return ExecuteSync(msg, ResultProcessor.SingleStreamWithNameSkip, defaultValue: Array.Empty()); + } + + public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position, int? count, CommandFlags flags) + { + return StreamReadGroupAsync( + key, + groupName, + consumerName, + position, + count, + false, + flags); + } + + public Task StreamReadGroupAsync(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue? position = null, int? count = null, bool noAck = false, CommandFlags flags = CommandFlags.None) + { + var actualPosition = position ?? StreamPosition.NewMessages; + + var msg = GetStreamReadGroupMessage( + key, + groupName, + consumerName, + StreamPosition.Resolve(actualPosition, RedisCommand.XREADGROUP), + count, + noAck, + flags); + + return ExecuteAsync(msg, ResultProcessor.SingleStreamWithNameSkip, defaultValue: Array.Empty()); + } + + public RedisStream[] StreamReadGroup(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream, CommandFlags flags) + { + return StreamReadGroup( + streamPositions, + groupName, + consumerName, + countPerStream, + false, + flags); + } + + public RedisStream[] StreamReadGroup(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream = null, bool noAck = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetMultiStreamReadGroupMessage( + streamPositions, + groupName, + consumerName, + countPerStream, + noAck, + flags); + + return ExecuteSync(msg, ResultProcessor.MultiStream, defaultValue: Array.Empty()); + } + + public Task StreamReadGroupAsync(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream, CommandFlags flags) + { + return StreamReadGroupAsync( + streamPositions, + groupName, + consumerName, + countPerStream, + false, + flags); + } + + public Task StreamReadGroupAsync(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream = null, bool noAck = false, CommandFlags flags = CommandFlags.None) + { + var msg = GetMultiStreamReadGroupMessage( + streamPositions, + groupName, + consumerName, + countPerStream, + noAck, + flags); + + return ExecuteAsync(msg, ResultProcessor.MultiStream, defaultValue: Array.Empty()); + } + + public long StreamTrim(RedisKey key, int maxLength, bool useApproximateMaxLength, CommandFlags flags) + => StreamTrim(key, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); + + public long StreamTrim(RedisKey key, long maxLength, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamTrimMessage(true, key, maxLength, useApproximateMaxLength, limit, mode, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproximateMaxLength, CommandFlags flags) + => StreamTrimAsync(key, maxLength, useApproximateMaxLength, null, StreamTrimMode.KeepReferences, flags); + + public Task StreamTrimAsync(RedisKey key, long maxLength, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamTrimMessage(true, key, maxLength, useApproximateMaxLength, limit, mode, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long StreamTrimByMinId(RedisKey key, RedisValue minId, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamTrimMessage(false, key, minId, useApproximateMaxLength, limit, mode, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StreamTrimByMinIdAsync(RedisKey key, RedisValue minId, bool useApproximateMaxLength = false, long? limit = null, StreamTrimMode mode = StreamTrimMode.KeepReferences, CommandFlags flags = CommandFlags.None) + { + var msg = GetStreamTrimMessage(false, key, minId, useApproximateMaxLength, limit, mode, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.APPEND, key, value); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StringAppendAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.APPEND, key, value); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long StringBitCount(RedisKey key, long start, long end, CommandFlags flags) => + StringBitCount(key, start, end, StringIndexType.Byte, flags); + + public long StringBitCount(RedisKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) + { + var msg = indexType switch + { + StringIndexType.Byte => Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end), + _ => Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end, indexType.ToLiteral()), + }; + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StringBitCountAsync(RedisKey key, long start, long end, CommandFlags flags) => + StringBitCountAsync(key, start, end, StringIndexType.Byte, flags); + + public Task StringBitCountAsync(RedisKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) + { + var msg = indexType switch + { + StringIndexType.Byte => Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end), + _ => Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end, indexType.ToLiteral()), + }; + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringBitOperationMessage(operation, destination, first, second, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringBitOperationMessage(operation, destination, keys, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringBitOperationMessage(operation, destination, first, second, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringBitOperationMessage(operation, destination, keys, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long StringBitPosition(RedisKey key, bool bit, long start, long end, CommandFlags flags) => + StringBitPosition(key, bit, start, end, StringIndexType.Byte, flags); + + public long StringBitPosition(RedisKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) + { + var msg = indexType switch + { + StringIndexType.Byte => Message.Create(Database, flags, RedisCommand.BITPOS, key, bit, start, end), + _ => Message.Create(Database, flags, RedisCommand.BITPOS, key, bit, start, end, indexType.ToLiteral()), + }; + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StringBitPositionAsync(RedisKey key, bool bit, long start, long end, CommandFlags flags) => + StringBitPositionAsync(key, bit, start, end, StringIndexType.Byte, flags); + + public Task StringBitPositionAsync(RedisKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) + { + var msg = indexType switch + { + StringIndexType.Byte => Message.Create(Database, flags, RedisCommand.BITPOS, key, bit, start, end), + _ => Message.Create(Database, flags, RedisCommand.BITPOS, key, bit, start, end, indexType.ToLiteral()), + }; + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long StringDecrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) + { + return StringIncrement(key, -value, flags); + } + + public double StringDecrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) + { + return StringIncrement(key, -value, flags); + } + + public Task StringDecrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) + { + return StringIncrementAsync(key, -value, flags); + } + + public Task StringDecrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) + { + return StringIncrementAsync(key, -value, flags); + } + + public RedisValue StringGet(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GET, key); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public RedisValue StringGetSetExpiry(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringGetExMessage(key, expiry, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public RedisValue StringGetSetExpiry(RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringGetExMessage(key, expiry, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task StringGetSetExpiryAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringGetExMessage(key, expiry, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task StringGetSetExpiryAsync(RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringGetExMessage(key, expiry, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public RedisValue[] StringGet(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (keys.Length == 0) return Array.Empty(); + var msg = Message.Create(Database, flags, RedisCommand.MGET, keys); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Lease? StringGetLease(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GET, key); + return ExecuteSync(msg, ResultProcessor.Lease); + } + + public Task StringGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GET, key); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public Task?> StringGetLeaseAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GET, key); + return ExecuteAsync(msg, ResultProcessor.Lease); + } + + public Task StringGetAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (keys.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + var msg = Message.Create(Database, flags, RedisCommand.MGET, keys); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public bool StringGetBit(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GETBIT, key, offset); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task StringGetBitAsync(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GETBIT, key, offset); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public RedisValue StringGetRange(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GETRANGE, key, start, end); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task StringGetRangeAsync(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GETRANGE, key, start, end); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public RedisValue StringGetSet(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GETSET, key, value); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task StringGetSetAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GETSET, key, value); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public RedisValue StringGetDelete(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GETDEL, key); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task StringGetDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.GETDEL, key); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public RedisValueWithExpiry StringGetWithExpiry(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringGetWithExpiryMessage(key, flags, out ResultProcessor processor, out ServerEndPoint? server); + return ExecuteSync(msg, processor, server); + } + + public Task StringGetWithExpiryAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringGetWithExpiryMessage(key, flags, out ResultProcessor processor, out ServerEndPoint? server); + return ExecuteAsync(msg, processor, server); + } + + public long StringIncrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) + { + var msg = IncrMessage(key, value, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public double StringIncrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) + { + var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 + ? null : Message.Create(Database, flags, RedisCommand.INCRBYFLOAT, key, value); + return ExecuteSync(msg, ResultProcessor.Double); + } + + public Task StringIncrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) + { + var msg = IncrMessage(key, value, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task StringIncrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) + { + var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 + ? null : Message.Create(Database, flags, RedisCommand.INCRBYFLOAT, key, value); + return ExecuteAsync(msg, ResultProcessor.Double); + } + + public long StringLength(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.STRLEN, key); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.STRLEN, key); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + // Backwards compatibility overloads: + public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, When when) => + StringSet(key, value, expiry, false, when, CommandFlags.None); + public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags) => + StringSet(key, value, expiry, false, when, flags); + + public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringSetMessage(key, value, expiry, keepTtl, when, flags); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public bool StringSet(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringSetMessage(values, when, flags); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + // Backwards compatibility overloads: + public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when) => + StringSetAsync(key, value, expiry, false, when); + public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags) => + StringSetAsync(key, value, expiry, false, when, flags); + + public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringSetMessage(key, value, expiry, keepTtl, when, flags); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task StringSetAsync(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringSetMessage(values, when, flags); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public RedisValue StringSetAndGet(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags) => + StringSetAndGet(key, value, expiry, false, when, flags); + + public RedisValue StringSetAndGet(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringSetAndGetMessage(key, value, expiry, keepTtl, when, flags); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task StringSetAndGetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags) => + StringSetAndGetAsync(key, value, expiry, false, when, flags); + + public Task StringSetAndGetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, bool keepTtl = false, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + var msg = GetStringSetAndGetMessage(key, value, expiry, keepTtl, when, flags); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public bool StringSetBit(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SETBIT, key, offset, bit); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task StringSetBitAsync(RedisKey key, long offset, bool value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SETBIT, key, offset, value); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SETRANGE, key, offset, value); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public bool KeyTouch(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.TOUCH, key); + return ExecuteSync(msg, ResultProcessor.DemandZeroOrOne); + } + + public long KeyTouch(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (keys.Length > 0) + { + var msg = keys.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.TOUCH, keys); + return ExecuteSync(msg, ResultProcessor.Int64); + } + return 0; + } + + public Task KeyTouchAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.TOUCH, key); + return ExecuteAsync(msg, ResultProcessor.DemandZeroOrOne); + } + + public Task KeyTouchAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (keys.Length > 0) + { + var msg = keys.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.TOUCH, keys); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + return CompletedTask.Default(0); + } + + public Task StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(Database, flags, RedisCommand.SETRANGE, key, offset, value); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + private static long GetUnixTimeMilliseconds(DateTime when) => when.Kind switch + { + DateTimeKind.Local or DateTimeKind.Utc => (when.ToUniversalTime() - RedisBase.UnixEpoch).Ticks / TimeSpan.TicksPerMillisecond, + _ => throw new ArgumentException("Expiry time must be either Utc or Local", nameof(when)), + }; + + private Message GetCopyMessage(in RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase, bool replace, CommandFlags flags) => + destinationDatabase switch + { + < -1 => throw new ArgumentOutOfRangeException(nameof(destinationDatabase)), + -1 when replace => Message.Create(Database, flags, RedisCommand.COPY, sourceKey, destinationKey, RedisLiterals.REPLACE), + -1 => Message.Create(Database, flags, RedisCommand.COPY, sourceKey, destinationKey), + _ when replace => Message.Create(Database, flags, RedisCommand.COPY, sourceKey, destinationKey, RedisLiterals.DB, destinationDatabase, RedisLiterals.REPLACE), + _ => Message.Create(Database, flags, RedisCommand.COPY, sourceKey, destinationKey, RedisLiterals.DB, destinationDatabase), + }; + + private Message GetExpiryMessage(in RedisKey key, CommandFlags flags, TimeSpan? expiry, ExpireWhen when, out ServerEndPoint? server) + { + if (expiry is null || expiry.Value == TimeSpan.MaxValue) + { + server = null; + return when switch + { + ExpireWhen.Always => Message.Create(Database, flags, RedisCommand.PERSIST, key), + _ => throw new ArgumentException("PERSIST cannot be used with when."), + }; + } + + long milliseconds = expiry.Value.Ticks / TimeSpan.TicksPerMillisecond; + return GetExpiryMessage(key, RedisCommand.PEXPIRE, RedisCommand.EXPIRE, milliseconds, when, flags, out server); + } + + private Message GetExpiryMessage(in RedisKey key, CommandFlags flags, DateTime? expiry, ExpireWhen when, out ServerEndPoint? server) + { + if (expiry is null || expiry == DateTime.MaxValue) + { + server = null; + return when switch + { + ExpireWhen.Always => Message.Create(Database, flags, RedisCommand.PERSIST, key), + _ => throw new ArgumentException("PERSIST cannot be used with when."), + }; + } + + long milliseconds = GetUnixTimeMilliseconds(expiry.Value); + return GetExpiryMessage(key, RedisCommand.PEXPIREAT, RedisCommand.EXPIREAT, milliseconds, when, flags, out server); + } + + private Message GetExpiryMessage( + in RedisKey key, + RedisCommand millisecondsCommand, + RedisCommand secondsCommand, + long milliseconds, + ExpireWhen when, + CommandFlags flags, + out ServerEndPoint? server) + { + server = null; + if ((milliseconds % 1000) != 0) + { + var features = GetFeatures(key, flags, RedisCommand.PEXPIRE, out server); + if (server is not null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(millisecondsCommand)) + { + return when switch + { + ExpireWhen.Always => Message.Create(Database, flags, millisecondsCommand, key, milliseconds), + _ => Message.Create(Database, flags, millisecondsCommand, key, milliseconds, when.ToLiteral()), + }; + } + server = null; + } + + long seconds = milliseconds / 1000; + return when switch + { + ExpireWhen.Always => Message.Create(Database, flags, secondsCommand, key, seconds), + _ => Message.Create(Database, flags, secondsCommand, key, seconds, when.ToLiteral()), + }; + } + + private Message GetListMultiPopMessage(RedisKey[] keys, RedisValue side, long count, CommandFlags flags) + { + if (keys is null || keys.Length == 0) + { + throw new ArgumentOutOfRangeException(nameof(keys), "keys must have a size of at least 1"); + } + + var slot = multiplexer.ServerSelectionStrategy.HashSlot(keys[0]); + + var args = new RedisValue[2 + keys.Length + (count == 1 ? 0 : 2)]; + var i = 0; + args[i++] = keys.Length; + foreach (var key in keys) + { + args[i++] = key.AsRedisValue(); + } + + args[i++] = side; + + if (count != 1) + { + args[i++] = RedisLiterals.COUNT; + args[i++] = count; + } + + return Message.CreateInSlot(Database, slot, flags, RedisCommand.LMPOP, args); + } + + private Message GetSortedSetMultiPopMessage(RedisKey[] keys, Order order, long count, CommandFlags flags) + { + if (keys is null || keys.Length == 0) + { + throw new ArgumentOutOfRangeException(nameof(keys), "keys must have a size of at least 1"); + } + + var slot = multiplexer.ServerSelectionStrategy.HashSlot(keys[0]); + + var args = new RedisValue[2 + keys.Length + (count == 1 ? 0 : 2)]; + var i = 0; + args[i++] = keys.Length; + foreach (var key in keys) + { + args[i++] = key.AsRedisValue(); + } + + args[i++] = order == Order.Ascending ? RedisLiterals.MIN : RedisLiterals.MAX; + + if (count != 1) + { + args[i++] = RedisLiterals.COUNT; + args[i++] = count; + } + + return Message.CreateInSlot(Database, slot, flags, RedisCommand.ZMPOP, args); + } + + private Message? GetHashSetMessage(RedisKey key, HashEntry[] hashFields, CommandFlags flags) + { + if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); + switch (hashFields.Length) + { + case 0: return null; + case 1: + return Message.Create( + Database, + flags, + RedisCommand.HMSET, + key, + hashFields[0].name, + hashFields[0].value); + case 2: + return Message.Create( + Database, + flags, + RedisCommand.HMSET, + key, + hashFields[0].name, + hashFields[0].value, + hashFields[1].name, + hashFields[1].value); + default: + var arr = new RedisValue[hashFields.Length * 2]; + int offset = 0; + for (int i = 0; i < hashFields.Length; i++) + { + arr[offset++] = hashFields[i].name; + arr[offset++] = hashFields[i].value; + } + return Message.Create(Database, flags, RedisCommand.HMSET, key, arr); + } + } + + private ITransaction? GetLockExtendTransaction(RedisKey key, RedisValue value, TimeSpan expiry) + { + var tran = CreateTransactionIfAvailable(asyncState); + if (tran is not null) + { + tran.AddCondition(Condition.StringEqual(key, value)); + tran.KeyExpireAsync(key, expiry, CommandFlags.FireAndForget); + } + return tran; + } + + private ITransaction? GetLockReleaseTransaction(RedisKey key, RedisValue value) + { + var tran = CreateTransactionIfAvailable(asyncState); + if (tran is not null) + { + tran.AddCondition(Condition.StringEqual(key, value)); + tran.KeyDeleteAsync(key, CommandFlags.FireAndForget); + } + return tran; + } + + internal static RedisValue GetLexRange(RedisValue value, Exclude exclude, bool isStart, Order order) + { + if (value.IsNull) // open search + { + if (order == Order.Ascending) return isStart ? RedisLiterals.MinusSymbol : RedisLiterals.PlusSymbol; + + return isStart ? RedisLiterals.PlusSymbol : RedisLiterals.MinusSymbol; // when descending order: Plus and Minus have to be reversed + } + + var srcLength = value.GetByteCount(); + Debug.Assert(srcLength >= 0); + + byte[] result = new byte[srcLength + 1]; + // no defaults here; must always explicitly specify [ / ( + result[0] = (exclude & (isStart ? Exclude.Start : Exclude.Stop)) == 0 ? (byte)'[' : (byte)'('; + int written = value.CopyTo(result.AsSpan(1)); + Debug.Assert(written == srcLength, "predicted/actual length mismatch"); + return result; + } + + private Message GetMultiStreamReadGroupMessage(StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream, bool noAck, CommandFlags flags) => + new MultiStreamReadGroupCommandMessage( + Database, + flags, + streamPositions, + groupName, + consumerName, + countPerStream, + noAck); + + private sealed class MultiStreamReadGroupCommandMessage : Message // XREADGROUP with multiple stream. Example: XREADGROUP GROUP groupName consumerName COUNT countPerStream STREAMS stream1 stream2 id1 id2 + { + private readonly StreamPosition[] streamPositions; + private readonly RedisValue groupName; + private readonly RedisValue consumerName; + private readonly int? countPerStream; + private readonly bool noAck; + private readonly int argCount; + + public MultiStreamReadGroupCommandMessage(int db, CommandFlags flags, StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream, bool noAck) + : base(db, flags, RedisCommand.XREADGROUP) + { + if (streamPositions == null) throw new ArgumentNullException(nameof(streamPositions)); + if (streamPositions.Length == 0) throw new ArgumentOutOfRangeException(nameof(streamPositions), "streamOffsetPairs must contain at least one item."); + for (int i = 0; i < streamPositions.Length; i++) + { + streamPositions[i].Key.AssertNotNull(); + } + + if (countPerStream.HasValue && countPerStream <= 0) + { + throw new ArgumentOutOfRangeException(nameof(countPerStream), "countPerStream must be greater than 0."); + } + + groupName.AssertNotNull(); + consumerName.AssertNotNull(); + + this.streamPositions = streamPositions; + this.groupName = groupName; + this.consumerName = consumerName; + this.countPerStream = countPerStream; + this.noAck = noAck; + + argCount = 4 // Room for GROUP groupName consumerName & STREAMS + + (streamPositions.Length * 2) // Enough room for the stream keys and associated IDs. + + (countPerStream.HasValue ? 2 : 0) // Room for "COUNT num" or 0 if countPerStream is null. + + (noAck ? 1 : 0); // Allow for the NOACK subcommand. + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + { + int slot = ServerSelectionStrategy.NoSlot; + for (int i = 0; i < streamPositions.Length; i++) + { + slot = serverSelectionStrategy.CombineSlot(slot, streamPositions[i].Key); + } + return slot; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, argCount); + physical.WriteBulkString(StreamConstants.Group); + physical.WriteBulkString(groupName); + physical.WriteBulkString(consumerName); + + if (countPerStream.HasValue) + { + physical.WriteBulkString(StreamConstants.Count); + physical.WriteBulkString(countPerStream.Value); + } + + if (noAck) + { + physical.WriteBulkString(StreamConstants.NoAck); + } + + physical.WriteBulkString(StreamConstants.Streams); + for (int i = 0; i < streamPositions.Length; i++) + { + physical.Write(streamPositions[i].Key); + } + for (int i = 0; i < streamPositions.Length; i++) + { + physical.WriteBulkString(StreamPosition.Resolve(streamPositions[i].Position, RedisCommand.XREADGROUP)); + } + } + + public override int ArgCount => argCount; + } + + private Message GetMultiStreamReadMessage(StreamPosition[] streamPositions, int? countPerStream, CommandFlags flags) => + new MultiStreamReadCommandMessage(Database, flags, streamPositions, countPerStream); + + private sealed class MultiStreamReadCommandMessage : Message // XREAD with multiple stream. Example: XREAD COUNT 2 STREAMS mystream writers 0-0 0-0 + { + private readonly StreamPosition[] streamPositions; + private readonly int? countPerStream; + private readonly int argCount; + + public MultiStreamReadCommandMessage(int db, CommandFlags flags, StreamPosition[] streamPositions, int? countPerStream) + : base(db, flags, RedisCommand.XREAD) + { + if (streamPositions == null) throw new ArgumentNullException(nameof(streamPositions)); + if (streamPositions.Length == 0) throw new ArgumentOutOfRangeException(nameof(streamPositions), "streamOffsetPairs must contain at least one item."); + for (int i = 0; i < streamPositions.Length; i++) + { + streamPositions[i].Key.AssertNotNull(); + } + + if (countPerStream.HasValue && countPerStream <= 0) + { + throw new ArgumentOutOfRangeException(nameof(countPerStream), "countPerStream must be greater than 0."); + } + + this.streamPositions = streamPositions; + this.countPerStream = countPerStream; + + argCount = 1 // Streams keyword. + + (countPerStream.HasValue ? 2 : 0) // Room for "COUNT num" or 0 if countPerStream is null. + + (streamPositions.Length * 2); // Room for the stream names and the ID after which to begin reading. + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + { + int slot = ServerSelectionStrategy.NoSlot; + for (int i = 0; i < streamPositions.Length; i++) + { + slot = serverSelectionStrategy.CombineSlot(slot, streamPositions[i].Key); + } + return slot; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, argCount); + + if (countPerStream.HasValue) + { + physical.WriteBulkString(StreamConstants.Count); + physical.WriteBulkString(countPerStream.Value); + } + + physical.WriteBulkString(StreamConstants.Streams); + for (int i = 0; i < streamPositions.Length; i++) + { + physical.Write(streamPositions[i].Key); + } + for (int i = 0; i < streamPositions.Length; i++) + { + physical.WriteBulkString(StreamPosition.Resolve(streamPositions[i].Position, RedisCommand.XREADGROUP)); + } + } + + public override int ArgCount => argCount; + } + + private static RedisValue GetRange(double value, Exclude exclude, bool isStart) + { + if (isStart) + { + if ((exclude & Exclude.Start) == 0) return value; // inclusive is default + } + else + { + if ((exclude & Exclude.Stop) == 0) return value; // inclusive is default + } + return "(" + Format.ToString(value); // '(' prefix means exclusive + } + + private Message GetRestoreMessage(RedisKey key, byte[] value, TimeSpan? expiry, CommandFlags flags) + { + long pttl = (expiry == null || expiry.Value == TimeSpan.MaxValue) ? 0 : (expiry.Value.Ticks / TimeSpan.TicksPerMillisecond); + return Message.Create(Database, flags, RedisCommand.RESTORE, key, pttl, value); + } + + private Message GetSetIntersectionLengthMessage(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + + var values = new RedisValue[1 + keys.Length + (limit > 0 ? 2 : 0)]; + int i = 0; + values[i++] = keys.Length; + for (var j = 0; j < keys.Length; j++) + { + values[i++] = keys[j].AsRedisValue(); + } + if (limit > 0) + { + values[i++] = RedisLiterals.LIMIT; + values[i] = limit; + } + + return Message.Create(Database, flags, RedisCommand.SINTERCARD, values); + } + + private Message GetSortedSetAddMessage(RedisKey key, RedisValue member, double score, SortedSetWhen when, bool change, CommandFlags flags) + { + RedisValue[] arr = new RedisValue[2 + when.CountBits() + (change ? 1 : 0)]; + int index = 0; + if ((when & SortedSetWhen.NotExists) != 0) + { + arr[index++] = RedisLiterals.NX; + } + if ((when & SortedSetWhen.Exists) != 0) + { + arr[index++] = RedisLiterals.XX; + } + if ((when & SortedSetWhen.GreaterThan) != 0) + { + arr[index++] = RedisLiterals.GT; + } + if ((when & SortedSetWhen.LessThan) != 0) + { + arr[index++] = RedisLiterals.LT; + } + if (change) + { + arr[index++] = RedisLiterals.CH; + } + arr[index++] = score; + arr[index++] = member; + return Message.Create(Database, flags, RedisCommand.ZADD, key, arr); + } + + private Message? GetSortedSetAddMessage(RedisKey key, SortedSetEntry[] values, SortedSetWhen when, bool change, CommandFlags flags) + { + if (values == null) throw new ArgumentNullException(nameof(values)); + switch (values.Length) + { + case 0: return null; + case 1: + return GetSortedSetAddMessage(key, values[0].element, values[0].score, when, change, flags); + default: + RedisValue[] arr = new RedisValue[(values.Length * 2) + when.CountBits() + (change ? 1 : 0)]; + int index = 0; + if ((when & SortedSetWhen.NotExists) != 0) + { + arr[index++] = RedisLiterals.NX; + } + if ((when & SortedSetWhen.Exists) != 0) + { + arr[index++] = RedisLiterals.XX; + } + if ((when & SortedSetWhen.GreaterThan) != 0) + { + arr[index++] = RedisLiterals.GT; + } + if ((when & SortedSetWhen.LessThan) != 0) + { + arr[index++] = RedisLiterals.LT; + } + if (change) + { + arr[index++] = RedisLiterals.CH; + } + + for (int i = 0; i < values.Length; i++) + { + arr[index++] = values[i].score; + arr[index++] = values[i].element; + } + return Message.Create(Database, flags, RedisCommand.ZADD, key, arr); + } + } + + private Message GetSortMessage(RedisKey destination, RedisKey key, long skip, long take, Order order, SortType sortType, RedisValue by, RedisValue[]? get, CommandFlags flags, out ServerEndPoint? server) + { + server = null; + var command = destination.IsNull && GetFeatures(key, flags, RedisCommand.SORT_RO, out server).ReadOnlySort + ? RedisCommand.SORT_RO + : RedisCommand.SORT; + + // If SORT_RO is not available, we cannot issue the command to a read-only replica + if (command == RedisCommand.SORT) + { + server = null; + } + + // most common cases; no "get", no "by", no "destination", no "skip", no "take" + if (destination.IsNull && skip == 0 && take == -1 && by.IsNull && (get == null || get.Length == 0)) + { + return order switch + { + Order.Ascending when sortType == SortType.Numeric => Message.Create(Database, flags, command, key), + Order.Ascending when sortType == SortType.Alphabetic => Message.Create(Database, flags, command, key, RedisLiterals.ALPHA), + Order.Descending when sortType == SortType.Numeric => Message.Create(Database, flags, command, key, RedisLiterals.DESC), + Order.Descending when sortType == SortType.Alphabetic => Message.Create(Database, flags, command, key, RedisLiterals.DESC, RedisLiterals.ALPHA), + Order.Ascending or Order.Descending => throw new ArgumentOutOfRangeException(nameof(sortType)), + _ => throw new ArgumentOutOfRangeException(nameof(order)), + }; + } + + // and now: more complicated scenarios... + var values = new List(); + if (!by.IsNull) + { + values.Add(RedisLiterals.BY); + values.Add(by); + } + // these are our defaults that mean "everything"; anything else needs to be sent explicitly + if (skip != 0 || take != -1) + { + values.Add(RedisLiterals.LIMIT); + values.Add(skip); + values.Add(take); + } + switch (order) + { + case Order.Ascending: + break; // default + case Order.Descending: + values.Add(RedisLiterals.DESC); + break; + default: + throw new ArgumentOutOfRangeException(nameof(order)); + } + switch (sortType) + { + case SortType.Numeric: + break; // default + case SortType.Alphabetic: + values.Add(RedisLiterals.ALPHA); + break; + default: + throw new ArgumentOutOfRangeException(nameof(sortType)); + } + if (get != null && get.Length != 0) + { + foreach (var item in get) + { + values.Add(RedisLiterals.GET); + values.Add(item); + } + } + if (destination.IsNull) return Message.Create(Database, flags, command, key, values.ToArray()); + + // Because we are using STORE, we need to push this to a primary + if (Message.GetPrimaryReplicaFlags(flags) == CommandFlags.DemandReplica) + { + throw ExceptionFactory.PrimaryOnly(multiplexer.RawConfig.IncludeDetailInExceptions, RedisCommand.SORT, null, null); + } + flags = Message.SetPrimaryReplicaFlags(flags, CommandFlags.DemandMaster); + values.Add(RedisLiterals.STORE); + return Message.Create(Database, flags, RedisCommand.SORT, key, values.ToArray(), destination); + } + + private Message GetSortedSetCombineAndStoreCommandMessage(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights, Aggregate aggregate, CommandFlags flags) + { + var command = operation.ToCommand(store: true); + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (command == RedisCommand.ZDIFFSTORE && (weights != null || aggregate != Aggregate.Sum)) + { + throw new ArgumentException("ZDIFFSTORE cannot be used with weights or aggregation."); + } + if (weights != null && keys.Length != weights.Length) + { + throw new ArgumentException("Keys and weights should have the same number of elements.", nameof(weights)); + } + + RedisValue[] values = RedisValue.EmptyArray; + + var argsLength = (weights?.Length > 0 ? 1 + weights.Length : 0) + (aggregate != Aggregate.Sum ? 2 : 0); + if (argsLength > 0) + { + values = new RedisValue[argsLength]; + AddWeightsAggregationAndScore(values, weights, aggregate); + } + return new SortedSetCombineAndStoreCommandMessage(Database, flags, command, destination, keys, values); + } + + private Message GetSortedSetCombineCommandMessage(SetOperation operation, RedisKey[] keys, double[]? weights, Aggregate aggregate, bool withScores, CommandFlags flags) + { + var command = operation.ToCommand(store: false); + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (command == RedisCommand.ZDIFF && (weights != null || aggregate != Aggregate.Sum)) + { + throw new ArgumentException("ZDIFF cannot be used with weights or aggregation."); + } + if (weights != null && keys.Length != weights.Length) + { + throw new ArgumentException("Keys and weights should have the same number of elements.", nameof(weights)); + } + + var i = 0; + var values = new RedisValue[1 + keys.Length + + (weights?.Length > 0 ? 1 + weights.Length : 0) + + (aggregate != Aggregate.Sum ? 2 : 0) + + (withScores ? 1 : 0)]; + values[i++] = keys.Length; + foreach (var key in keys) + { + values[i++] = key.AsRedisValue(); + } + AddWeightsAggregationAndScore(values.AsSpan(i), weights, aggregate, withScores: withScores); + return Message.Create(Database, flags, command, values ?? RedisValue.EmptyArray); + } + + private void AddWeightsAggregationAndScore(Span values, double[]? weights, Aggregate aggregate, bool withScores = false) + { + int i = 0; + if (weights?.Length > 0) + { + values[i++] = RedisLiterals.WEIGHTS; + foreach (var weight in weights) + { + values[i++] = weight; + } + } + switch (aggregate) + { + case Aggregate.Sum: + break; // add nothing - Redis default + case Aggregate.Min: + values[i++] = RedisLiterals.AGGREGATE; + values[i++] = RedisLiterals.MIN; + break; + case Aggregate.Max: + values[i++] = RedisLiterals.AGGREGATE; + values[i++] = RedisLiterals.MAX; + break; + default: + throw new ArgumentOutOfRangeException(nameof(aggregate)); + } + if (withScores) + { + values[i++] = RedisLiterals.WITHSCORES; + } + } + + private Message GetSortedSetLengthMessage(RedisKey key, double min, double max, Exclude exclude, CommandFlags flags) + { + if (double.IsNegativeInfinity(min) && double.IsPositiveInfinity(max)) + return Message.Create(Database, flags, RedisCommand.ZCARD, key); + + var from = GetRange(min, exclude, true); + var to = GetRange(max, exclude, false); + return Message.Create(Database, flags, RedisCommand.ZCOUNT, key, from, to); + } + + private Message GetSortedSetIntersectionLengthMessage(RedisKey[] keys, long limit, CommandFlags flags) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + + var i = 0; + var values = new RedisValue[1 + keys.Length + (limit > 0 ? 2 : 0)]; + values[i++] = keys.Length; + foreach (var key in keys) + { + values[i++] = key.AsRedisValue(); + } + if (limit > 0) + { + values[i++] = RedisLiterals.LIMIT; + values[i++] = limit; + } + return Message.Create(Database, flags, RedisCommand.ZINTERCARD, values); + } + + private Message GetSortedSetRangeByScoreMessage(RedisKey key, double start, double stop, Exclude exclude, Order order, long skip, long take, CommandFlags flags, bool withScores) + { + // usage: {ZRANGEBYSCORE|ZREVRANGEBYSCORE} key from to [WITHSCORES] [LIMIT offset count] + // there's basically only 4 layouts; with/without each of scores/limit + var command = order == Order.Descending ? RedisCommand.ZREVRANGEBYSCORE : RedisCommand.ZRANGEBYSCORE; + bool unlimited = skip == 0 && take == -1; // these are our defaults that mean "everything"; anything else needs to be sent explicitly + + bool reverseLimits = (order == Order.Ascending) == (start > stop); + if (reverseLimits) + { + var tmp = start; + start = stop; + stop = tmp; + switch (exclude) + { + case Exclude.Start: exclude = Exclude.Stop; break; + case Exclude.Stop: exclude = Exclude.Start; break; + } + } + + RedisValue from = GetRange(start, exclude, true), to = GetRange(stop, exclude, false); + if (withScores) + { + return unlimited ? Message.Create(Database, flags, command, key, from, to, RedisLiterals.WITHSCORES) + : Message.Create(Database, flags, command, key, new[] { from, to, RedisLiterals.WITHSCORES, RedisLiterals.LIMIT, skip, take }); + } + else + { + return unlimited ? Message.Create(Database, flags, command, key, from, to) + : Message.Create(Database, flags, command, key, new[] { from, to, RedisLiterals.LIMIT, skip, take }); + } + } + + private Message GetSortedSetRemoveRangeByScoreMessage(RedisKey key, double start, double stop, Exclude exclude, CommandFlags flags) + { + return Message.Create( + Database, + flags, + RedisCommand.ZREMRANGEBYSCORE, + key, + GetRange(start, exclude, true), + GetRange(stop, exclude, false)); + } + + private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags) + { + return Message.Create(Database, flags, RedisCommand.XACK, key, groupName, messageId); + } + + private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags) + { + if (messageIds == null) throw new ArgumentNullException(nameof(messageIds)); + if (messageIds.Length == 0) throw new ArgumentOutOfRangeException(nameof(messageIds), "messageIds must contain at least one item."); + + var values = new RedisValue[messageIds.Length + 1]; + values[0] = groupName; + messageIds.AsSpan().CopyTo(values.AsSpan(1)); + + return Message.Create(Database, flags, RedisCommand.XACK, key, values); + } + + private Message GetStreamAcknowledgeAndDeleteMessage(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue messageId, CommandFlags flags) + { + return Message.Create(Database, flags, RedisCommand.XACKDEL, key, groupName, StreamConstants.GetMode(mode), StreamConstants.Ids, 1, messageId); + } + + private Message GetStreamAcknowledgeAndDeleteMessage(RedisKey key, RedisValue groupName, StreamTrimMode mode, RedisValue[] messageIds, CommandFlags flags) + { + if (messageIds == null) throw new ArgumentNullException(nameof(messageIds)); + if (messageIds.Length == 0) throw new ArgumentOutOfRangeException(nameof(messageIds), "messageIds must contain at least one item."); + + var values = new RedisValue[messageIds.Length + 4]; + + var offset = 0; + values[offset++] = groupName; + values[offset++] = StreamConstants.GetMode(mode); + values[offset++] = StreamConstants.Ids; + values[offset++] = messageIds.Length; + messageIds.AsSpan().CopyTo(values.AsSpan(offset)); + Debug.Assert(offset + messageIds.Length == values.Length); + + return Message.Create(Database, flags, RedisCommand.XACKDEL, key, values); + } + + private Message GetStreamAddMessage(RedisKey key, RedisValue messageId, long? maxLength, bool useApproximateMaxLength, NameValueEntry streamPair, long? limit, StreamTrimMode mode, CommandFlags flags) + { + // Calculate the correct number of arguments: + // 3 array elements for Entry ID & NameValueEntry.Name & NameValueEntry.Value. + // 2 elements if using MAXLEN (keyword & value), otherwise 0. + // 1 element if using Approximate Length (~), otherwise 0. + var totalLength = 3 + (maxLength.HasValue ? 2 : 0) + + (maxLength.HasValue && useApproximateMaxLength ? 1 : 0) + + (limit.HasValue ? 2 : 0) + + (mode != StreamTrimMode.KeepReferences ? 1 : 0); + + var values = new RedisValue[totalLength]; + var offset = 0; + + if (maxLength.HasValue) + { + values[offset++] = StreamConstants.MaxLen; + + if (useApproximateMaxLength) + { + values[offset++] = StreamConstants.ApproximateMaxLen; + } + + values[offset++] = maxLength.Value; + } + + if (limit.HasValue) + { + values[offset++] = RedisLiterals.LIMIT; + values[offset++] = limit.Value; + } + + if (mode != StreamTrimMode.KeepReferences) + { + values[offset++] = StreamConstants.GetMode(mode); + } + + values[offset++] = messageId; + + values[offset++] = streamPair.Name; + values[offset++] = streamPair.Value; + + Debug.Assert(offset == totalLength); + return Message.Create(Database, flags, RedisCommand.XADD, key, values); + } + + /// + /// Gets message for . + /// + private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, long? maxLength, bool useApproximateMaxLength, NameValueEntry[] streamPairs, long? limit, StreamTrimMode mode, CommandFlags flags) + { + if (streamPairs == null) throw new ArgumentNullException(nameof(streamPairs)); + if (streamPairs.Length == 0) throw new ArgumentOutOfRangeException(nameof(streamPairs), "streamPairs must contain at least one item."); + + if (maxLength.HasValue && maxLength <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxLength), "maxLength must be greater than 0."); + } + + var totalLength = (streamPairs.Length * 2) // Room for the name/value pairs + + 1 // The stream entry ID + + (maxLength.HasValue ? 2 : 0) // MAXLEN N + + (maxLength.HasValue && useApproximateMaxLength ? 1 : 0) // ~ + + (mode == StreamTrimMode.KeepReferences ? 0 : 1) // relevant trim-mode keyword + + (limit.HasValue ? 2 : 0); // LIMIT N + + var values = new RedisValue[totalLength]; + + var offset = 0; + + if (maxLength.HasValue) + { + values[offset++] = StreamConstants.MaxLen; + + if (useApproximateMaxLength) + { + values[offset++] = StreamConstants.ApproximateMaxLen; + } + + values[offset++] = maxLength.Value; + } + + if (limit.HasValue) + { + values[offset++] = RedisLiterals.LIMIT; + values[offset++] = limit.Value; + } + + if (mode != StreamTrimMode.KeepReferences) + { + values[offset++] = StreamConstants.GetMode(mode); + } + + values[offset++] = entryId; + + for (var i = 0; i < streamPairs.Length; i++) + { + values[offset++] = streamPairs[i].Name; + values[offset++] = streamPairs[i].Value; + } + + Debug.Assert(offset == totalLength); + return Message.Create(Database, flags, RedisCommand.XADD, key, values); + } + + private Message GetStreamAutoClaimMessage(RedisKey key, RedisValue consumerGroup, RedisValue assignToConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count, bool idsOnly, CommandFlags flags) + { + // XAUTOCLAIM [COUNT count] [JUSTID] + var values = new RedisValue[4 + (count is null ? 0 : 2) + (idsOnly ? 1 : 0)]; + + var offset = 0; + + values[offset++] = consumerGroup; + values[offset++] = assignToConsumer; + values[offset++] = minIdleTimeInMs; + values[offset++] = startAtId; + + if (count is not null) + { + values[offset++] = StreamConstants.Count; + values[offset++] = count.Value; + } + + if (idsOnly) + { + values[offset++] = StreamConstants.JustId; + } + + return Message.Create(Database, flags, RedisCommand.XAUTOCLAIM, key, values); + } + + private Message GetStreamClaimMessage(RedisKey key, RedisValue consumerGroup, RedisValue assignToConsumer, long minIdleTimeInMs, RedisValue[] messageIds, bool returnJustIds, CommandFlags flags) + { + if (messageIds == null) throw new ArgumentNullException(nameof(messageIds)); + if (messageIds.Length == 0) throw new ArgumentOutOfRangeException(nameof(messageIds), "messageIds must contain at least one item."); + + // XCLAIM ... + var values = new RedisValue[3 + messageIds.Length + (returnJustIds ? 1 : 0)]; + + var offset = 0; + + values[offset++] = consumerGroup; + values[offset++] = assignToConsumer; + values[offset++] = minIdleTimeInMs; + + for (var i = 0; i < messageIds.Length; i++) + { + values[offset++] = messageIds[i]; + } + + if (returnJustIds) + { + values[offset] = StreamConstants.JustId; + } + + return Message.Create(Database, flags, RedisCommand.XCLAIM, key, values); + } + + private Message GetStreamCreateConsumerGroupMessage(RedisKey key, RedisValue groupName, RedisValue? position = null, bool createStream = true, CommandFlags flags = CommandFlags.None) + { + var actualPosition = position ?? StreamConstants.NewMessages; + + var values = new RedisValue[createStream ? 5 : 4]; + + values[0] = StreamConstants.Create; + values[1] = key.AsRedisValue(); + values[2] = groupName; + values[3] = StreamPosition.Resolve(actualPosition, RedisCommand.XGROUP); + + if (createStream) + { + values[4] = StreamConstants.MkStream; + } + + return Message.Create( + Database, + flags, + RedisCommand.XGROUP, + values); + } + + /// + /// Gets a message for . + /// + /// + private Message GetStreamPendingMessagesMessage(RedisKey key, RedisValue groupName, RedisValue? minId, RedisValue? maxId, int count, RedisValue consumerName, long? minIdleTimeInMs, CommandFlags flags) + { + // > XPENDING mystream mygroup [IDLE min-idle-time] - + 10 [consumer name] + // 1) 1) 1526569498055 - 0 + // 2) "Bob" + // 3) (integer)74170458 + // 4) (integer)1 + // 2) 1) 1526569506935 - 0 + // 2) "Bob" + // 3) (integer)74170458 + // 4) (integer)1 + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "count must be greater than 0."); + } + + var valuesLength = 4; + if (consumerName != RedisValue.Null) + { + valuesLength++; + } + + if (minIdleTimeInMs is not null) + { + valuesLength += 2; + } + var values = new RedisValue[valuesLength]; + + var offset = 0; + + values[offset++] = groupName; + if (minIdleTimeInMs is not null) + { + values[offset++] = "IDLE"; + values[offset++] = minIdleTimeInMs; + } + values[offset++] = minId ?? StreamConstants.ReadMinValue; + values[offset++] = maxId ?? StreamConstants.ReadMaxValue; + values[offset++] = count; + + if (consumerName != RedisValue.Null) + { + values[offset++] = consumerName; + } + + return Message.Create( + Database, + flags, + RedisCommand.XPENDING, + key, + values); + } + + private Message GetStreamRangeMessage(RedisKey key, RedisValue? minId, RedisValue? maxId, int? count, Order messageOrder, CommandFlags flags) + { + if (count.HasValue && count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "count must be greater than 0."); + } + + var actualMin = minId ?? StreamConstants.ReadMinValue; + var actualMax = maxId ?? StreamConstants.ReadMaxValue; + + var values = new RedisValue[2 + (count.HasValue ? 2 : 0)]; + + values[0] = messageOrder == Order.Ascending ? actualMin : actualMax; + values[1] = messageOrder == Order.Ascending ? actualMax : actualMin; + + if (count.HasValue) + { + values[2] = StreamConstants.Count; + values[3] = count.Value; + } + + return Message.Create( + Database, + flags, + messageOrder == Order.Ascending ? RedisCommand.XRANGE : RedisCommand.XREVRANGE, + key, + values); + } + + private Message GetStreamReadGroupMessage(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue afterId, int? count, bool noAck, CommandFlags flags) => + new SingleStreamReadGroupCommandMessage(Database, flags, key, groupName, consumerName, afterId, count, noAck); + + private sealed class SingleStreamReadGroupCommandMessage : Message.CommandKeyBase // XREADGROUP with single stream. eg XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream > + { + private readonly RedisValue groupName; + private readonly RedisValue consumerName; + private readonly RedisValue afterId; + private readonly int? count; + private readonly bool noAck; + private readonly int argCount; + + public SingleStreamReadGroupCommandMessage(int db, CommandFlags flags, RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue afterId, int? count, bool noAck) + : base(db, flags, RedisCommand.XREADGROUP, key) + { + if (count.HasValue && count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "count must be greater than 0."); + } + + groupName.AssertNotNull(); + consumerName.AssertNotNull(); + afterId.AssertNotNull(); + + this.groupName = groupName; + this.consumerName = consumerName; + this.afterId = afterId; + this.count = count; + this.noAck = noAck; + argCount = 6 + (count.HasValue ? 2 : 0) + (noAck ? 1 : 0); + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, argCount); + physical.WriteBulkString(StreamConstants.Group); + physical.WriteBulkString(groupName); + physical.WriteBulkString(consumerName); + + if (count.HasValue) + { + physical.WriteBulkString(StreamConstants.Count); + physical.WriteBulkString(count.Value); + } + + if (noAck) + { + physical.WriteBulkString(StreamConstants.NoAck); + } + + physical.WriteBulkString(StreamConstants.Streams); + physical.Write(Key); + physical.WriteBulkString(afterId); + } + + public override int ArgCount => argCount; + } + + private Message GetSingleStreamReadMessage(RedisKey key, RedisValue afterId, int? count, CommandFlags flags) => + new SingleStreamReadCommandMessage(Database, flags, key, afterId, count); + + private sealed class SingleStreamReadCommandMessage : Message.CommandKeyBase // XREAD with a single stream. Example: XREAD COUNT 2 STREAMS mystream 0-0 + { + private readonly RedisValue afterId; + private readonly int? count; + private readonly int argCount; + + public SingleStreamReadCommandMessage(int db, CommandFlags flags, RedisKey key, RedisValue afterId, int? count) + : base(db, flags, RedisCommand.XREAD, key) + { + if (count.HasValue && count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "count must be greater than 0."); + } + + afterId.AssertNotNull(); + + this.afterId = afterId; + this.count = count; + argCount = count.HasValue ? 5 : 3; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, argCount); + + if (count.HasValue) + { + physical.WriteBulkString(StreamConstants.Count); + physical.WriteBulkString(count.Value); + } + + physical.WriteBulkString(StreamConstants.Streams); + physical.Write(Key); + physical.WriteBulkString(afterId); + } + + public override int ArgCount => argCount; + } + + private Message GetStreamTrimMessage(bool maxLen, RedisKey key, RedisValue threshold, bool useApproximateMaxLength, long? limit, StreamTrimMode mode, CommandFlags flags) + { + if (limit.HasValue && limit.GetValueOrDefault() <= 0) + { + throw new ArgumentOutOfRangeException(nameof(limit), "limit must be greater than 0 when specified."); + } + + if (limit is null && !useApproximateMaxLength && mode == StreamTrimMode.KeepReferences) + { + // avoid array alloc in simple case + return Message.Create(Database, flags, RedisCommand.XTRIM, key, maxLen ? StreamConstants.MaxLen : StreamConstants.MinId, threshold); + } + + var values = new RedisValue[2 + (useApproximateMaxLength ? 1 : 0) + (limit.HasValue ? 2 : 0) + (mode == StreamTrimMode.KeepReferences ? 0 : 1)]; + + var offset = 0; + + values[offset++] = maxLen ? StreamConstants.MaxLen : StreamConstants.MinId; + + if (useApproximateMaxLength) + { + values[offset++] = StreamConstants.ApproximateMaxLen; + } + + values[offset++] = threshold; + + if (limit.HasValue) + { + values[offset++] = RedisLiterals.LIMIT; + values[offset++] = limit.GetValueOrDefault(); + } + + if (mode != StreamTrimMode.KeepReferences) // omit when not needed, for back-compat + { + values[offset++] = StreamConstants.GetMode(mode); + } + + Debug.Assert(offset == values.Length); + + return Message.Create( + Database, + flags, + RedisCommand.XTRIM, + key, + values); + } + + private Message? GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (keys.Length == 0) return null; + + // these ones are too bespoke to warrant custom Message implementations + var serverSelectionStrategy = multiplexer.ServerSelectionStrategy; + int slot = serverSelectionStrategy.HashSlot(destination); + var values = new RedisValue[keys.Length + 2]; + values[0] = RedisLiterals.Get(operation); + values[1] = destination.AsRedisValue(); + for (int i = 0; i < keys.Length; i++) + { + values[i + 2] = keys[i].AsRedisValue(); + slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); + } + return Message.CreateInSlot(Database, slot, flags, RedisCommand.BITOP, values); + } + + private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags) + { + // these ones are too bespoke to warrant custom Message implementations + var op = RedisLiterals.Get(operation); + var serverSelectionStrategy = multiplexer.ServerSelectionStrategy; + int slot = serverSelectionStrategy.HashSlot(destination); + slot = serverSelectionStrategy.CombineSlot(slot, first); + if (second.IsNull || operation == Bitwise.Not) + { + // unary + return Message.CreateInSlot(Database, slot, flags, RedisCommand.BITOP, new[] { op, destination.AsRedisValue(), first.AsRedisValue() }); + } + // binary + slot = serverSelectionStrategy.CombineSlot(slot, second); + return Message.CreateInSlot(Database, slot, flags, RedisCommand.BITOP, new[] { op, destination.AsRedisValue(), first.AsRedisValue(), second.AsRedisValue() }); + } + + private Message GetStringGetExMessage(in RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) => expiry switch + { + null => Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PERSIST), + _ => Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PX, (long)expiry.Value.TotalMilliseconds), + }; + + private Message GetStringGetExMessage(in RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None) => expiry == DateTime.MaxValue + ? Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PERSIST) + : Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PXAT, GetUnixTimeMilliseconds(expiry)); + + private Message GetStringGetWithExpiryMessage(RedisKey key, CommandFlags flags, out ResultProcessor processor, out ServerEndPoint? server) + { + if (this is IBatch) + { + throw new NotSupportedException("This operation is not possible inside a transaction or batch; please issue separate GetString and KeyTimeToLive requests"); + } + var features = GetFeatures(key, flags, RedisCommand.PTTL, out server); + processor = StringGetWithExpiryProcessor.Default; + if (server != null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(RedisCommand.PTTL)) + { + return new StringGetWithExpiryMessage(Database, flags, RedisCommand.PTTL, key); + } + // if we use TTL, it doesn't matter which server + server = null; + return new StringGetWithExpiryMessage(Database, flags, RedisCommand.TTL, key); + } + + private Message? GetStringSetMessage(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) + { + if (values == null) throw new ArgumentNullException(nameof(values)); + switch (values.Length) + { + case 0: return null; + case 1: return GetStringSetMessage(values[0].Key, values[0].Value, null, false, when, flags); + default: + WhenAlwaysOrNotExists(when); + int slot = ServerSelectionStrategy.NoSlot, offset = 0; + var args = new RedisValue[values.Length * 2]; + var serverSelectionStrategy = multiplexer.ServerSelectionStrategy; + for (int i = 0; i < values.Length; i++) + { + args[offset++] = values[i].Key.AsRedisValue(); + args[offset++] = values[i].Value; + slot = serverSelectionStrategy.CombineSlot(slot, values[i].Key); + } + return Message.CreateInSlot(Database, slot, flags, when == When.NotExists ? RedisCommand.MSETNX : RedisCommand.MSET, args); + } + } + + private Message GetStringSetMessage( + RedisKey key, + RedisValue value, + TimeSpan? expiry = null, + bool keepTtl = false, + When when = When.Always, + CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrExistsOrNotExists(when); + if (value.IsNull) return Message.Create(Database, flags, RedisCommand.DEL, key); + + if (expiry == null || expiry.Value == TimeSpan.MaxValue) + { + // no expiry + return when switch + { + When.Always when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value), + When.Always when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.KEEPTTL), + When.NotExists when !keepTtl => Message.Create(Database, flags, RedisCommand.SETNX, key, value), + When.NotExists when keepTtl => Message.Create(Database, flags, RedisCommand.SETNX, key, value, RedisLiterals.KEEPTTL), + When.Exists when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX), + When.Exists when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX, RedisLiterals.KEEPTTL), + _ => throw new ArgumentOutOfRangeException(nameof(when)), + }; + } + long milliseconds = expiry.Value.Ticks / TimeSpan.TicksPerMillisecond; + + if ((milliseconds % 1000) == 0) + { + // a nice round number of seconds + long seconds = milliseconds / 1000; + return when switch + { + When.Always => Message.Create(Database, flags, RedisCommand.SETEX, key, seconds, value), + When.Exists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.XX), + When.NotExists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.NX), + _ => throw new ArgumentOutOfRangeException(nameof(when)), + }; + } + + return when switch + { + When.Always => Message.Create(Database, flags, RedisCommand.PSETEX, key, milliseconds, value), + When.Exists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.XX), + When.NotExists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.NX), + _ => throw new ArgumentOutOfRangeException(nameof(when)), + }; + } + + private Message GetStringSetAndGetMessage( + RedisKey key, + RedisValue value, + TimeSpan? expiry = null, + bool keepTtl = false, + When when = When.Always, + CommandFlags flags = CommandFlags.None) + { + WhenAlwaysOrExistsOrNotExists(when); + if (value.IsNull) return Message.Create(Database, flags, RedisCommand.GETDEL, key); + + if (expiry == null || expiry.Value == TimeSpan.MaxValue) + { + // no expiry + return when switch + { + When.Always when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.GET), + When.Always when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.GET, RedisLiterals.KEEPTTL), + When.Exists when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX, RedisLiterals.GET), + When.Exists when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX, RedisLiterals.GET, RedisLiterals.KEEPTTL), + When.NotExists when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.NX, RedisLiterals.GET), + When.NotExists when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.NX, RedisLiterals.GET, RedisLiterals.KEEPTTL), + _ => throw new ArgumentOutOfRangeException(nameof(when)), + }; + } + long milliseconds = expiry.Value.Ticks / TimeSpan.TicksPerMillisecond; + + if ((milliseconds % 1000) == 0) + { + // a nice round number of seconds + long seconds = milliseconds / 1000; + return when switch + { + When.Always => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.GET), + When.Exists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.XX, RedisLiterals.GET), + When.NotExists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.NX, RedisLiterals.GET), + _ => throw new ArgumentOutOfRangeException(nameof(when)), + }; + } + + return when switch + { + When.Always => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.GET), + When.Exists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.XX, RedisLiterals.GET), + When.NotExists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.NX, RedisLiterals.GET), + _ => throw new ArgumentOutOfRangeException(nameof(when)), + }; + } + + private Message? IncrMessage(RedisKey key, long value, CommandFlags flags) => value switch + { + 0 => ((flags & CommandFlags.FireAndForget) != 0) + ? null + : Message.Create(Database, flags, RedisCommand.INCRBY, key, value), + 1 => Message.Create(Database, flags, RedisCommand.INCR, key), + -1 => Message.Create(Database, flags, RedisCommand.DECR, key), + > 0 => Message.Create(Database, flags, RedisCommand.INCRBY, key, value), + _ => Message.Create(Database, flags, RedisCommand.DECRBY, key, -value), + }; + + private static RedisCommand SetOperationCommand(SetOperation operation, bool store) => operation switch + { + SetOperation.Difference => store ? RedisCommand.SDIFFSTORE : RedisCommand.SDIFF, + SetOperation.Intersect => store ? RedisCommand.SINTERSTORE : RedisCommand.SINTER, + SetOperation.Union => store ? RedisCommand.SUNIONSTORE : RedisCommand.SUNION, + _ => throw new ArgumentOutOfRangeException(nameof(operation)), + }; + + private CursorEnumerable? TryScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags, RedisCommand command, ResultProcessor.ScanResult> processor, out ServerEndPoint? server, bool noValues = false) + { + server = null; + if (pageSize <= 0) + throw new ArgumentOutOfRangeException(nameof(pageSize)); + if (!multiplexer.CommandMap.IsAvailable(command)) return null; + + var features = GetFeatures(key, flags, RedisCommand.SCAN, out server); + if (!features.Scan) return null; + + if (CursorUtils.IsNil(pattern)) pattern = (byte[]?)null; + return new ScanEnumerable(this, server, key, pattern, pageSize, cursor, pageOffset, flags, command, processor, noValues); + } + + private Message GetLexMessage(RedisCommand command, RedisKey key, RedisValue min, RedisValue max, Exclude exclude, long skip, long take, CommandFlags flags, Order order) + { + RedisValue start = GetLexRange(min, exclude, true, order), stop = GetLexRange(max, exclude, false, order); + + if (skip == 0 && take == -1) + return Message.Create(Database, flags, command, key, start, stop); + + return Message.Create(Database, flags, command, key, new[] { start, stop, RedisLiterals.LIMIT, skip, take }); + } + + public long SortedSetLengthByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + { + var msg = GetLexMessage(RedisCommand.ZLEXCOUNT, key, min, max, exclude, 0, -1, flags, Order.Ascending); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public RedisValue[] SortedSetRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude, long skip, long take, CommandFlags flags) + => SortedSetRangeByValue(key, min, max, exclude, Order.Ascending, skip, take, flags); + + private static void ReverseLimits(Order order, ref Exclude exclude, ref RedisValue start, ref RedisValue stop) + { + bool reverseLimits = (order == Order.Ascending) == (stop != default && start.CompareTo(stop) > 0); + if (reverseLimits) + { + var tmp = start; + start = stop; + stop = tmp; + switch (exclude) + { + case Exclude.Start: exclude = Exclude.Stop; break; + case Exclude.Stop: exclude = Exclude.Start; break; + } + } + } + public RedisValue[] SortedSetRangeByValue( + RedisKey key, + RedisValue min = default, + RedisValue max = default, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long take = -1, + CommandFlags flags = CommandFlags.None) + { + ReverseLimits(order, ref exclude, ref min, ref max); + var msg = GetLexMessage(order == Order.Ascending ? RedisCommand.ZRANGEBYLEX : RedisCommand.ZREVRANGEBYLEX, key, min, max, exclude, skip, take, flags, order); + return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + { + var msg = GetLexMessage(RedisCommand.ZREMRANGEBYLEX, key, min, max, exclude, 0, -1, flags, Order.Ascending); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SortedSetLengthByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + { + var msg = GetLexMessage(RedisCommand.ZLEXCOUNT, key, min, max, exclude, 0, -1, flags, Order.Ascending); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public Task SortedSetRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude, long skip, long take, CommandFlags flags) + => SortedSetRangeByValueAsync(key, min, max, exclude, Order.Ascending, skip, take, flags); + + public Task SortedSetRangeByValueAsync( + RedisKey key, + RedisValue min = default, + RedisValue max = default, + Exclude exclude = Exclude.None, + Order order = Order.Ascending, + long skip = 0, + long take = -1, + CommandFlags flags = CommandFlags.None) + { + ReverseLimits(order, ref exclude, ref min, ref max); + var msg = GetLexMessage(order == Order.Ascending ? RedisCommand.ZRANGEBYLEX : RedisCommand.ZREVRANGEBYLEX, key, min, max, exclude, skip, take, flags, order); + return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); + } + + public Task SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) + { + var msg = GetLexMessage(RedisCommand.ZREMRANGEBYLEX, key, min, max, exclude, 0, -1, flags, Order.Ascending); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + internal sealed class ScanEnumerable : CursorEnumerable + { + private readonly RedisKey key; + private readonly RedisValue pattern; + private readonly RedisCommand command; + private readonly bool noValues; + + public ScanEnumerable( + RedisDatabase database, + ServerEndPoint? server, + RedisKey key, + in RedisValue pattern, + int pageSize, + in RedisValue cursor, + int pageOffset, + CommandFlags flags, + RedisCommand command, + ResultProcessor processor, + bool noValues) + : base(database, server, database.Database, pageSize, cursor, pageOffset, flags) + { + this.key = key; + this.pattern = pattern; + this.command = command; + Processor = processor; + this.noValues = noValues; + } + + private protected override ResultProcessor.ScanResult> Processor { get; } + + private protected override Message CreateMessage(in RedisValue cursor) + { + if (noValues) + { + if (CursorUtils.IsNil(pattern) && pageSize == CursorUtils.DefaultRedisPageSize) return Message.Create(db, flags, command, key, cursor, RedisLiterals.NOVALUES); + if (CursorUtils.IsNil(pattern)) return Message.Create(db, flags, command, key, cursor, RedisLiterals.COUNT, pageSize, RedisLiterals.NOVALUES); + return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize, RedisLiterals.NOVALUES); + } + + if (CursorUtils.IsNil(pattern)) + { + if (pageSize == CursorUtils.DefaultRedisPageSize) + { + return Message.Create(db, flags, command, key, cursor); + } + else + { + return Message.Create(db, flags, command, key, cursor, RedisLiterals.COUNT, pageSize); + } + } + else + { + if (pageSize == CursorUtils.DefaultRedisPageSize) + { + return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern); + } + else + { + return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize); + } + } + } + } + + internal sealed class ScriptLoadMessage : Message + { + internal readonly string Script; + public ScriptLoadMessage(CommandFlags flags, string script) + : base(-1, flags, RedisCommand.SCRIPT) + { + Script = script ?? throw new ArgumentNullException(nameof(script)); + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 2); + physical.WriteBulkString(RedisLiterals.LOAD); + physical.WriteBulkString((RedisValue)Script); + } + public override int ArgCount => 2; + } + + private sealed class HashScanResultProcessor : ScanResultProcessor + { + public static readonly ResultProcessor.ScanResult> Default = new HashScanResultProcessor(); + private HashScanResultProcessor() { } + protected override HashEntry[]? Parse(in RawResult result, out int count) + => HashEntryArray.TryParse(result, out HashEntry[]? pairs, true, out count) ? pairs : null; + } + + private abstract class ScanResultProcessor : ResultProcessor.ScanResult> + { + protected abstract T[]? Parse(in RawResult result, out int count); + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var arr = result.GetItems(); + if (arr.Length == 2) + { + ref RawResult inner = ref arr[1]; + if (inner.Resp2TypeArray == ResultType.Array && arr[0].TryGetInt64(out var i64)) + { + T[]? oversized = Parse(inner, out int count); + var sscanResult = new ScanEnumerable.ScanResult(i64, oversized, count, true); + SetResult(message, sscanResult); + return true; + } + } + break; + } + return false; + } + } + + internal sealed class ExecuteMessage : Message + { + private readonly ICollection _args; + public new CommandBytes Command { get; } + + public ExecuteMessage(CommandMap? map, int db, CommandFlags flags, string command, ICollection? args) : base(db, flags, RedisCommand.UNKNOWN) + { + if (args != null && args.Count >= PhysicalConnection.REDIS_MAX_ARGS) // using >= here because we will be adding 1 for the command itself (which is an arg for the purposes of the multi-bulk protocol) + { + throw ExceptionFactory.TooManyArgs(command, args.Count); + } + Command = map?.GetBytes(command) ?? default; + if (Command.IsEmpty) throw ExceptionFactory.CommandDisabled(command); + _args = args ?? Array.Empty(); + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(RedisCommand.UNKNOWN, _args.Count, Command); + foreach (object arg in _args) + { + if (arg is RedisKey key) + { + physical.Write(key); + } + else if (arg is RedisChannel channel) + { + physical.Write(channel); + } + else + { // recognises well-known types + var val = RedisValue.TryParse(arg, out var valid); + if (!valid) throw new InvalidCastException($"Unable to parse value: '{arg}'"); + physical.WriteBulkString(val); + } + } + } + + public override string CommandString => Command.ToString(); + public override string CommandAndKey => Command.ToString(); + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + { + int slot = ServerSelectionStrategy.NoSlot; + foreach (object arg in _args) + { + if (arg is RedisKey key) + { + slot = serverSelectionStrategy.CombineSlot(slot, key); + } + } + return slot; + } + public override int ArgCount => _args.Count; + } + + private sealed class ScriptEvalMessage : Message, IMultiMessage + { + private readonly RedisKey[] keys; + private readonly string? script; + private readonly RedisValue[] values; + private byte[]? asciiHash; + private readonly byte[]? hexHash; + + public ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string script, RedisKey[]? keys, RedisValue[]? values) + : this(db, flags, command, script, null, keys, values) + { + if (script == null) throw new ArgumentNullException(nameof(script)); + } + + public ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, byte[] hash, RedisKey[]? keys, RedisValue[]? values) + : this(db, flags, command, null, hash, keys, values) + { + if (hash == null) throw new ArgumentNullException(nameof(hash)); + if (hash.Length != ResultProcessor.ScriptLoadProcessor.Sha1HashLength) throw new ArgumentOutOfRangeException(nameof(hash), "Invalid hash length"); + } + + private ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string? script, byte[]? hexHash, RedisKey[]? keys, RedisValue[]? values) + : base(db, flags, command) + { + this.script = script; + this.hexHash = hexHash; + + if (keys == null) keys = Array.Empty(); + if (values == null) values = Array.Empty(); + for (int i = 0; i < keys.Length; i++) + keys[i].AssertNotNull(); + this.keys = keys; + for (int i = 0; i < values.Length; i++) + values[i].AssertNotNull(); + this.values = values; + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + { + int slot = ServerSelectionStrategy.NoSlot; + for (int i = 0; i < keys.Length; i++) + slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); + return slot; + } + + public IEnumerable GetMessages(PhysicalConnection connection) + { + PhysicalBridge? bridge; + if (script != null && (bridge = connection.BridgeCouldBeNull) != null + && bridge.Multiplexer.CommandMap.IsAvailable(RedisCommand.SCRIPT) + && (Flags & CommandFlags.NoScriptCache) == 0) + { + // a script was provided (rather than a hash); check it is known and supported + asciiHash = bridge.ServerEndPoint.GetScriptHash(script, command); + + if (asciiHash == null) + { + var msg = new ScriptLoadMessage(Flags, script); + msg.SetInternalCall(); + msg.SetSource(ResultProcessor.ScriptLoad, null); + yield return msg; + } + } + yield return this; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + if (hexHash != null) + { + physical.WriteHeader(RedisCommand.EVALSHA, 2 + keys.Length + values.Length); + physical.WriteSha1AsHex(hexHash); + } + else if (asciiHash != null) + { + physical.WriteHeader(RedisCommand.EVALSHA, 2 + keys.Length + values.Length); + physical.WriteBulkString((RedisValue)asciiHash); + } + else + { + physical.WriteHeader(RedisCommand.EVAL, 2 + keys.Length + values.Length); + physical.WriteBulkString((RedisValue)script); + } + physical.WriteBulkString(keys.Length); + for (int i = 0; i < keys.Length; i++) + physical.Write(keys[i]); + for (int i = 0; i < values.Length; i++) + physical.WriteBulkString(values[i]); + } + public override int ArgCount => 2 + keys.Length + values.Length; + } + + private sealed class SetScanResultProcessor : ScanResultProcessor + { + public static readonly ResultProcessor.ScanResult> Default = new SetScanResultProcessor(); + private SetScanResultProcessor() { } + protected override RedisValue[] Parse(in RawResult result, out int count) + { + var items = result.GetItems(); + if (items.IsEmpty) + { + count = 0; + return Array.Empty(); + } + count = (int)items.Length; + RedisValue[] arr = ArrayPool.Shared.Rent(count); + items.CopyTo(arr, (in RawResult r) => r.AsRedisValue()); + return arr; + } + } + + private static Message CreateListPositionMessage(int db, CommandFlags flags, RedisKey key, RedisValue element, long rank, long maxLen, long? count = null) => + count != null + ? Message.Create(db, flags, RedisCommand.LPOS, key, element, RedisLiterals.RANK, rank, RedisLiterals.MAXLEN, maxLen, RedisLiterals.COUNT, count) + : Message.Create(db, flags, RedisCommand.LPOS, key, element, RedisLiterals.RANK, rank, RedisLiterals.MAXLEN, maxLen); + + private static Message CreateSortedSetRangeStoreMessage( + int db, + CommandFlags flags, + RedisKey sourceKey, + RedisKey destinationKey, + RedisValue start, + RedisValue stop, + SortedSetOrder sortedSetOrder, + Order order, + Exclude exclude, + long skip, + long? take) + { + if (sortedSetOrder == SortedSetOrder.ByRank) + { + if (take > 0) + { + throw new ArgumentException("take argument is not valid when sortedSetOrder is ByRank you may want to try setting the SortedSetOrder to ByLex or ByScore", nameof(take)); + } + if (exclude != Exclude.None) + { + throw new ArgumentException("exclude argument is not valid when sortedSetOrder is ByRank, you may want to try setting the sortedSetOrder to ByLex or ByScore", nameof(exclude)); + } + + return order switch + { + Order.Ascending => Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, start, stop), + Order.Descending => Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, start, stop, RedisLiterals.REV), + _ => throw new ArgumentOutOfRangeException(nameof(order)), + }; + } + + RedisValue formattedStart = exclude switch + { + Exclude.Both or Exclude.Start => $"({start}", + _ when sortedSetOrder == SortedSetOrder.ByLex => $"[{start}", + _ => start, + }; + + RedisValue formattedStop = exclude switch + { + Exclude.Both or Exclude.Stop => $"({stop}", + _ when sortedSetOrder == SortedSetOrder.ByLex => $"[{stop}", + _ => stop, + }; + + return order switch + { + Order.Ascending when take != null && take > 0 => + Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, formattedStart, formattedStop, sortedSetOrder.GetLiteral(), RedisLiterals.LIMIT, skip, take), + Order.Ascending => + Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, formattedStart, formattedStop, sortedSetOrder.GetLiteral()), + Order.Descending when take != null && take > 0 => + Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, formattedStart, formattedStop, sortedSetOrder.GetLiteral(), RedisLiterals.REV, RedisLiterals.LIMIT, skip, take), + Order.Descending => + Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, formattedStart, formattedStop, sortedSetOrder.GetLiteral(), RedisLiterals.REV), + _ => throw new ArgumentOutOfRangeException(nameof(order)), + }; + } + + private sealed class SortedSetCombineAndStoreCommandMessage : Message.CommandKeyBase // ZINTERSTORE and ZUNIONSTORE have a very unusual signature + { + private readonly RedisKey[] keys; + private readonly RedisValue[] values; + public SortedSetCombineAndStoreCommandMessage(int db, CommandFlags flags, RedisCommand command, RedisKey destination, RedisKey[] keys, RedisValue[] values) + : base(db, flags, command, destination) + { + for (int i = 0; i < keys.Length; i++) + keys[i].AssertNotNull(); + this.keys = keys; + for (int i = 0; i < values.Length; i++) + values[i].AssertNotNull(); + this.values = values; + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + { + int slot = base.GetHashSlot(serverSelectionStrategy); + for (int i = 0; i < keys.Length; i++) + slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); + return slot; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(Command, 2 + keys.Length + values.Length); + physical.Write(Key); + physical.WriteBulkString(keys.Length); + for (int i = 0; i < keys.Length; i++) + physical.Write(keys[i]); + for (int i = 0; i < values.Length; i++) + physical.WriteBulkString(values[i]); + } + public override int ArgCount => 2 + keys.Length + values.Length; + } + + private sealed class SortedSetScanResultProcessor : ScanResultProcessor + { + public static readonly ResultProcessor.ScanResult> Default = new SortedSetScanResultProcessor(); + private SortedSetScanResultProcessor() { } + protected override SortedSetEntry[]? Parse(in RawResult result, out int count) + => SortedSetWithScores.TryParse(result, out SortedSetEntry[]? pairs, true, out count) ? pairs : null; + } + + private sealed class StringGetWithExpiryMessage : Message.CommandKeyBase, IMultiMessage + { + private readonly RedisCommand ttlCommand; + private IResultBox? box; + + public StringGetWithExpiryMessage(int db, CommandFlags flags, RedisCommand ttlCommand, in RedisKey key) + : base(db, flags, RedisCommand.GET, key) + { + this.ttlCommand = ttlCommand; + } + + public override string CommandAndKey => ttlCommand + "+" + RedisCommand.GET + " " + (string?)Key; + + public IEnumerable GetMessages(PhysicalConnection connection) + { + box = SimpleResultBox.Create(); + var ttl = Message.Create(Db, Flags, ttlCommand, Key); + var proc = ttlCommand == RedisCommand.PTTL ? ResultProcessor.TimeSpanFromMilliseconds : ResultProcessor.TimeSpanFromSeconds; + ttl.SetSource(proc, box); + yield return ttl; + yield return this; + } + + public bool UnwrapValue(out TimeSpan? value, out Exception? ex) + { + if (box != null) + { + value = box.GetResult(out ex); + box = null; + return ex == null; + } + value = null; + ex = null; + return false; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + physical.WriteHeader(command, 1); + physical.Write(Key); + } + public override int ArgCount => 1; + } + + private sealed class StringGetWithExpiryProcessor : ResultProcessor + { + public static readonly ResultProcessor Default = new StringGetWithExpiryProcessor(); + private StringGetWithExpiryProcessor() { } + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + RedisValue value = result.AsRedisValue(); + if (message is StringGetWithExpiryMessage sgwem && sgwem.UnwrapValue(out var expiry, out var ex)) + { + if (ex == null) + { + SetResult(message, new RedisValueWithExpiry(value, expiry)); + } + else + { + SetException(message, ex); + } + return true; + } + break; + } + return false; + } + } + } +} diff --git a/src/StackExchange.Redis/RedisErrorEventArgs.cs b/src/StackExchange.Redis/RedisErrorEventArgs.cs new file mode 100644 index 000000000..2213baf1c --- /dev/null +++ b/src/StackExchange.Redis/RedisErrorEventArgs.cs @@ -0,0 +1,51 @@ +using System; +using System.Net; +using System.Text; + +namespace StackExchange.Redis +{ + /// + /// Notification of errors from the redis server. + /// + public class RedisErrorEventArgs : EventArgs, ICompletable + { + private readonly EventHandler? handler; + private readonly object sender; + internal RedisErrorEventArgs( + EventHandler? handler, + object sender, + EndPoint endpoint, + string message) + { + this.handler = handler; + this.sender = sender; + Message = message; + EndPoint = endpoint; + } + + /// + /// This constructor is only for testing purposes. + /// + /// The source of the event. + /// Redis endpoint. + /// Error message. + public RedisErrorEventArgs(object sender, EndPoint endpoint, string message) + : this(null, sender, endpoint, message) + { + } + + /// + /// The origin of the message. + /// + public EndPoint EndPoint { get; } + + /// + /// The message from the server. + /// + public string Message { get; } + + void ICompletable.AppendStormLog(StringBuilder sb) => sb.Append("event, error: ").Append(Message); + + bool ICompletable.TryComplete(bool isAsync) => ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync); + } +} diff --git a/src/StackExchange.Redis/RedisFeatures.cs b/src/StackExchange.Redis/RedisFeatures.cs new file mode 100644 index 000000000..87bcbf20c --- /dev/null +++ b/src/StackExchange.Redis/RedisFeatures.cs @@ -0,0 +1,383 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace StackExchange.Redis +{ + /// + /// Provides basic information about the features available on a particular version of Redis. + /// + public readonly struct RedisFeatures : IEquatable + { +#pragma warning disable SA1310 // Field names should not contain underscore +#pragma warning disable SA1311 // Static readonly fields should begin with upper-case letter + internal static readonly Version v2_0_0 = new Version(2, 0, 0), + v2_1_0 = new Version(2, 1, 0), + v2_1_1 = new Version(2, 1, 1), + v2_1_2 = new Version(2, 1, 2), + v2_1_3 = new Version(2, 1, 3), + v2_1_8 = new Version(2, 1, 8), + v2_2_0 = new Version(2, 2, 0), + v2_4_0 = new Version(2, 4, 0), + v2_5_7 = new Version(2, 5, 7), + v2_5_10 = new Version(2, 5, 10), + v2_5_14 = new Version(2, 5, 14), + v2_6_0 = new Version(2, 6, 0), + v2_6_5 = new Version(2, 6, 5), + v2_6_9 = new Version(2, 6, 9), + v2_6_12 = new Version(2, 6, 12), + v2_8_0 = new Version(2, 8, 0), + v2_8_12 = new Version(2, 8, 12), + v2_8_18 = new Version(2, 8, 18), + v2_9_5 = new Version(2, 9, 5), + v3_0_0 = new Version(3, 0, 0), + v3_2_0 = new Version(3, 2, 0), + v3_2_1 = new Version(3, 2, 1), + v4_0_0 = new Version(4, 0, 0), + v4_9_1 = new Version(4, 9, 1), // 5.0 RC1 is version 4.9.1; // 5.0 RC1 is version 4.9.1 + v5_0_0 = new Version(5, 0, 0), + v6_0_0 = new Version(6, 0, 0), + v6_0_6 = new Version(6, 0, 6), + v6_2_0 = new Version(6, 2, 0), + v7_0_0_rc1 = new Version(6, 9, 240), // 7.0 RC1 is version 6.9.240 + v7_2_0_rc1 = new Version(7, 1, 240), // 7.2 RC1 is version 7.1.240 + v7_4_0_rc1 = new Version(7, 3, 240), // 7.4 RC1 is version 7.3.240 + v7_4_0_rc2 = new Version(7, 3, 241), // 7.4 RC2 is version 7.3.241 + v8_0_0_M04 = new Version(7, 9, 227), // 8.0 M04 is version 7.9.227 + v8_2_0_rc1 = new Version(8, 1, 240); // 8.2 RC1 is version 8.1.240 + +#pragma warning restore SA1310 // Field names should not contain underscore +#pragma warning restore SA1311 // Static readonly fields should begin with upper-case letter + + private readonly Version version; + + /// + /// Create a new RedisFeatures instance for the given version. + /// + /// The version of redis to base the feature set on. + public RedisFeatures(Version version) + { + this.version = version ?? throw new ArgumentNullException(nameof(version)); + } + +#pragma warning disable SA1629 // Documentation should end with a period + + /// + /// Are BITOP and BITCOUNT available? + /// + public bool BitwiseOperations => Version.IsAtLeast(v2_6_0); + + /// + /// Is CLIENT SETNAME available? + /// + public bool ClientName => Version.IsAtLeast(v2_6_9); + + /// + /// Is CLIENT ID available? + /// + public bool ClientId => Version.IsAtLeast(v5_0_0); + + /// + /// Does EXEC support EXECABORT if there are errors? + /// + public bool ExecAbort => Version.IsAtLeast(v2_6_5) && !Version.IsEqual(v2_9_5); + + /// + /// Can EXPIRE be used to set expiration on a key that is already volatile (i.e. has an expiration)? + /// + public bool ExpireOverwrite => Version.IsAtLeast(v2_1_3); + + /// + /// Is GETDEL available? + /// + public bool GetDelete => Version.IsAtLeast(v6_2_0); + + /// + /// Is HSTRLEN available? + /// + public bool HashStringLength => Version.IsAtLeast(v3_2_0); + + /// + /// Does HDEL support variadic usage? + /// + public bool HashVaradicDelete => Version.IsAtLeast(v2_4_0); + + /// + /// Are INCRBYFLOAT and HINCRBYFLOAT available? + /// + public bool IncrementFloat => Version.IsAtLeast(v2_6_0); + + /// + /// Does INFO support sections? + /// + public bool InfoSections => Version.IsAtLeast(v2_8_0); + + /// + /// Is LINSERT available? + /// + public bool ListInsert => Version.IsAtLeast(v2_1_1); + + /// + /// Is MEMORY available? + /// + public bool Memory => Version.IsAtLeast(v4_0_0); + + /// + /// Are PEXPIRE and PTTL available? + /// + public bool MillisecondExpiry => Version.IsAtLeast(v2_6_0); + + /// + /// Is MODULE available? + /// + public bool Module => Version.IsAtLeast(v4_0_0); + + /// + /// Does SRANDMEMBER support the "count" option? + /// + public bool MultipleRandom => Version.IsAtLeast(v2_5_14); + + /// + /// Is PERSIST available? + /// + public bool Persist => Version.IsAtLeast(v2_1_2); + + /// + /// Are LPUSHX and RPUSHX available? + /// + public bool PushIfNotExists => Version.IsAtLeast(v2_1_1); + + /// + /// Does this support SORT_RO? + /// + internal bool ReadOnlySort => Version.IsAtLeast(v7_0_0_rc1); + + /// + /// Is SCAN (cursor-based scanning) available? + /// + public bool Scan => Version.IsAtLeast(v2_8_0); + + /// + /// Are EVAL, EVALSHA, and other script commands available? + /// + public bool Scripting => Version.IsAtLeast(v2_6_0); + + /// + /// Does SET support the GET option? + /// + public bool SetAndGet => Version.IsAtLeast(v6_2_0); + + /// + /// Does SET support the EX, PX, NX, and XX options? + /// + public bool SetConditional => Version.IsAtLeast(v2_6_12); + + /// + /// Does SET have the KEEPTTL option? + /// + public bool SetKeepTtl => Version.IsAtLeast(v6_0_0); + + /// + /// Does SET allow the NX and GET options to be used together? + /// + public bool SetNotExistsAndGet => Version.IsAtLeast(v7_0_0_rc1); + + /// + /// Does SADD support variadic usage? + /// + public bool SetVaradicAddRemove => Version.IsAtLeast(v2_4_0); + + /// + /// Are SSUBSCRIBE and SPUBLISH available? + /// + public bool ShardedPubSub => Version.IsAtLeast(v7_0_0_rc1); + + /// + /// Are ZPOPMIN and ZPOPMAX available? + /// + public bool SortedSetPop => Version.IsAtLeast(v5_0_0); + + /// + /// Is ZRANGESTORE available? + /// + public bool SortedSetRangeStore => Version.IsAtLeast(v6_2_0); + + /// + /// Are Redis Streams available? + /// + public bool Streams => Version.IsAtLeast(v4_9_1); + + /// + /// Is STRLEN available? + /// + public bool StringLength => Version.IsAtLeast(v2_1_2); + + /// + /// Is SETRANGE available? + /// + public bool StringSetRange => Version.IsAtLeast(v2_1_8); + + /// + /// Is SWAPDB available? + /// + public bool SwapDB => Version.IsAtLeast(v4_0_0); + + /// + /// Is TIME available? + /// + public bool Time => Version.IsAtLeast(v2_6_0); + + /// + /// Is UNLINK available? + /// + public bool Unlink => Version.IsAtLeast(v4_0_0); + + /// + /// Are Lua changes to the calling database transparent to the calling client? + /// + public bool ScriptingDatabaseSafe => Version.IsAtLeast(v2_8_12); + + /// + [Obsolete("Starting with Redis version 5, Redis has moved to 'replica' terminology. Please use " + nameof(HyperLogLogCountReplicaSafe) + " instead, this will be removed in 3.0.")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public bool HyperLogLogCountSlaveSafe => HyperLogLogCountReplicaSafe; + + /// + /// Is PFCOUNT available on replicas? + /// + public bool HyperLogLogCountReplicaSafe => Version.IsAtLeast(v2_8_18); + + /// + /// Are geospatial commands available? + /// + public bool Geo => Version.IsAtLeast(v3_2_0); + + /// + /// Can PING be used on a subscription connection? + /// + internal bool PingOnSubscriber => Version.IsAtLeast(v3_0_0); + + /// + /// Does SPOP support popping multiple items? + /// + public bool SetPopMultiple => Version.IsAtLeast(v3_2_0); + + /// + /// Is TOUCH available? + /// + public bool KeyTouch => Version.IsAtLeast(v3_2_1); + + /// + /// Does the server prefer 'replica' terminology - 'REPLICAOF', etc? + /// + public bool ReplicaCommands => Version.IsAtLeast(v5_0_0); + + /// + /// Do list-push commands support multiple arguments? + /// + public bool PushMultiple => Version.IsAtLeast(v4_0_0); + + /// + /// Is the RESP3 protocol available? + /// + public bool Resp3 => Version.IsAtLeast(v6_0_0); + +#pragma warning restore 1629 // Documentation text should end with a period. + + /// + /// The Redis version of the server. + /// + public Version Version => version ?? v2_0_0; + + /// + /// Create a string representation of the available features. + /// + public override string ToString() + { + var v = Version; // the docs lie: Version.ToString(fieldCount) only supports 0-2 fields + var sb = new StringBuilder().Append("Features in ").Append(v.Major).Append('.').Append(v.Minor); + if (v.Revision >= 0) sb.Append('.').Append(v.Revision); + if (v.Build >= 0) sb.Append('.').Append(v.Build); + sb.AppendLine(); + object boxed = this; + foreach (var prop in s_props) + { + sb.Append(prop.Name).Append(": ").Append(prop.GetValue(boxed)).AppendLine(); + } + return sb.ToString(); + } + + private static readonly PropertyInfo[] s_props = ( + from prop in typeof(RedisFeatures).GetProperties(BindingFlags.Instance | BindingFlags.Public) + where prop.PropertyType == typeof(bool) + let indexers = prop.GetIndexParameters() + where indexers == null || indexers.Length == 0 + orderby prop.Name + select prop).ToArray(); + + /// Returns the hash code for this instance. + /// A 32-bit signed integer that is the hash code for this instance. + public override int GetHashCode() => Version.GetNormalizedHashCode(); + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// + /// if and this instance are the same type and represent the same value, otherwise. + /// + /// The object to compare with the current instance. + public override bool Equals(object? obj) => obj is RedisFeatures f && f.Version.IsEqual(Version); + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// + /// if and this instance are the same type and represent the same value, otherwise. + /// + /// The object to compare with the current instance. + public bool Equals(RedisFeatures other) => other.Version.IsEqual(Version); + + /// + /// Checks if 2 are .Equal(). + /// + public static bool operator ==(RedisFeatures left, RedisFeatures right) => left.Version.IsEqual(right.Version); + + /// + /// Checks if 2 are not .Equal(). + /// + public static bool operator !=(RedisFeatures left, RedisFeatures right) => !left.Version.IsEqual(right.Version); + } +} + +internal static class VersionExtensions +{ + // normalize two version parts and smash them together into a long; if either part is -ve, + // zero is used instead; this gives us consistent ordering following "long" rules + private static long ComposeMajorMinor(Version version) // always specified + => (((long)version.Major) << 32) | (long)version.Minor; + + private static long ComposeBuildRevision(Version version) // can be -ve for "not specified" + { + int build = version.Build, revision = version.Revision; + return (((long)(build < 0 ? 0 : build)) << 32) | (long)(revision < 0 ? 0 : revision); + } + + internal static int GetNormalizedHashCode(this Version value) + => (ComposeMajorMinor(value) * ComposeBuildRevision(value)).GetHashCode(); + + internal static bool IsEqual(this Version x, Version y) + => ComposeMajorMinor(x) == ComposeMajorMinor(y) + && ComposeBuildRevision(x) == ComposeBuildRevision(y); + + internal static bool IsAtLeast(this Version x, Version y) + { + // think >=, but: without the... "unusual behaviour" in how Version's >= operator + // compares values with different part lengths, i.e. "6.0" **is not** >= "6.0.0" + // under the inbuilt operator + var delta = ComposeMajorMinor(x) - ComposeMajorMinor(y); + if (delta > 0) return true; + return delta < 0 ? false : ComposeBuildRevision(x) >= ComposeBuildRevision(y); + } +} diff --git a/src/StackExchange.Redis/RedisKey.cs b/src/StackExchange.Redis/RedisKey.cs new file mode 100644 index 000000000..0ee83d560 --- /dev/null +++ b/src/StackExchange.Redis/RedisKey.cs @@ -0,0 +1,439 @@ +using System; +using System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace StackExchange.Redis +{ + /// + /// Represents a key that can be stored in redis. + /// + public readonly struct RedisKey : IEquatable + { + internal RedisKey(byte[]? keyPrefix, object? keyValue) + { + KeyPrefix = keyPrefix?.Length == 0 ? null : keyPrefix; + KeyValue = keyValue; + } + + /// + /// Creates a from a string. + /// + public RedisKey(string? key) : this(null, key) { } + + internal RedisKey AsPrefix() => new RedisKey((byte[]?)this, null); + + internal bool IsNull => KeyPrefix == null && KeyValue == null; + + internal static RedisKey Null { get; } = new RedisKey(null, null); + + internal bool IsEmpty + { + get + { + if (KeyPrefix != null) return false; + if (KeyValue == null) return true; + if (KeyValue is string s) return s.Length == 0; + return ((byte[])KeyValue).Length == 0; + } + } + + internal byte[]? KeyPrefix { get; } + internal object? KeyValue { get; } + + /// + /// Indicate whether two keys are not equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(RedisKey x, RedisKey y) => !x.EqualsImpl(in y); + + /// + /// Indicate whether two keys are not equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(string x, RedisKey y) => !y.EqualsImpl(new RedisKey(x)); + + /// + /// Indicate whether two keys are not equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(byte[] x, RedisKey y) => !y.EqualsImpl(new RedisKey(null, x)); + + /// + /// Indicate whether two keys are not equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(RedisKey x, string y) => !x.EqualsImpl(new RedisKey(y)); + + /// + /// Indicate whether two keys are not equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(RedisKey x, byte[] y) => !x.EqualsImpl(new RedisKey(null, y)); + + /// + /// Indicate whether two keys are equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(RedisKey x, RedisKey y) => x.EqualsImpl(in y); + + /// + /// Indicate whether two keys are equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(string x, RedisKey y) => y.EqualsImpl(new RedisKey(x)); + + /// + /// Indicate whether two keys are equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(byte[] x, RedisKey y) => y.EqualsImpl(new RedisKey(null, x)); + + /// + /// Indicate whether two keys are equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(RedisKey x, string y) => x.EqualsImpl(new RedisKey(y)); + + /// + /// Indicate whether two keys are equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(RedisKey x, byte[] y) => x.EqualsImpl(new RedisKey(null, y)); + + /// + /// See . + /// + /// The to compare to. + public override bool Equals(object? obj) => obj switch + { + null => IsNull, + RedisKey key => EqualsImpl(in key), + string s => EqualsImpl(new RedisKey(s)), + byte[] b => EqualsImpl(new RedisKey(null, b)), + _ => false, + }; + + /// + /// Indicate whether two keys are equal. + /// + /// The to compare to. + public bool Equals(RedisKey other) => EqualsImpl(in other); + + private bool EqualsImpl(in RedisKey other) + { + if (IsNull) + { + return other.IsNull; + } + else if (other.IsNull) + { + return false; + } + + // if there's no prefix, we might be able to do a simple compare + if (RedisValue.Equals(KeyPrefix, other.KeyPrefix)) + { + if ((object?)KeyValue == (object?)other.KeyValue) return true; // ref equal + + if (KeyValue is string keyString1 && other.KeyValue is string keyString2) return keyString1 == keyString2; + if (KeyValue is byte[] keyBytes1 && other.KeyValue is byte[] keyBytes2) return RedisValue.Equals(keyBytes1, keyBytes2); + } + + int len = TotalLength(); + if (len != other.TotalLength()) + { + return false; // different length; can't be equal + } + if (len == 0) + { + return true; // both empty + } + if (len <= 128) + { + return CopyCompare(in this, in other, len, stackalloc byte[len * 2]); + } + else + { + byte[] arr = ArrayPool.Shared.Rent(len * 2); + var result = CopyCompare(in this, in other, len, arr); + ArrayPool.Shared.Return(arr); + return result; + } + + static bool CopyCompare(in RedisKey x, in RedisKey y, int length, Span span) + { + Span span1 = span.Slice(0, length), span2 = span.Slice(length, length); + var written = x.CopyTo(span1); + Debug.Assert(written == length, "length error (1)"); + written = y.CopyTo(span2); + Debug.Assert(written == length, "length error (2)"); + return span1.SequenceEqual(span2); + } + } + + /// + public override int GetHashCode() + { + // note that we need need eaulity-like behavior, regardless of whether the + // parts look like bytes or strings, and with/without prefix + + // the simplest way to do this is to use the CopyTo version, which normalizes that + if (IsNull) return -1; + if (TryGetSimpleBuffer(out var buffer)) return RedisValue.GetHashCode(buffer); + var len = TotalLength(); + if (len == 0) return 0; + + if (len <= 256) + { + Span span = stackalloc byte[len]; + var written = CopyTo(span); + Debug.Assert(written == len); + return RedisValue.GetHashCode(span); + } + else + { + var arr = ArrayPool.Shared.Rent(len); + var span = new Span(arr, 0, len); + var written = CopyTo(span); + Debug.Assert(written == len); + var result = RedisValue.GetHashCode(span); + ArrayPool.Shared.Return(arr); + return result; + } + } + + /// + /// Obtains a string representation of the key. + /// + public override string ToString() => ((string?)this) ?? "(null)"; + + internal RedisValue AsRedisValue() + { + if (KeyPrefix == null && KeyValue is string keyString) return keyString; + return (byte[]?)this; + } + + internal void AssertNotNull() + { + if (IsNull) throw new ArgumentException("A null key is not valid in this context"); + } + + /// + /// Create a from a . + /// + /// The string to get a key from. + public static implicit operator RedisKey(string? key) + { + if (key == null) return default; + return new RedisKey(null, key); + } + + /// + /// Create a from a . + /// + /// The byte array to get a key from. + public static implicit operator RedisKey(byte[]? key) + { + if (key == null) return default; + return new RedisKey(null, key); + } + + /// + /// Obtain the as a . + /// + /// The key to get a byte array for. + public static implicit operator byte[]?(RedisKey key) + { + if (key.IsNull) return null; + if (key.TryGetSimpleBuffer(out var arr)) return arr; + + var len = key.TotalLength(); + if (len == 0) return Array.Empty(); + arr = new byte[len]; + var written = key.CopyTo(arr); + Debug.Assert(written == len, "length/copyto error"); + return arr; + } + + /// + /// Obtain the key as a . + /// + /// The key to get a string for. + public static implicit operator string?(RedisKey key) + { + if (key.KeyPrefix is null) + { + return key.KeyValue switch + { + null => null, + string s => s, + object o => Get((byte[])o, -1), + }; + } + + var len = key.TotalLength(); + var arr = ArrayPool.Shared.Rent(len); + var written = key.CopyTo(arr); + Debug.Assert(written == len, "length error"); + var result = Get(arr, len); + ArrayPool.Shared.Return(arr); + return result; + + static string? Get(byte[] arr, int length) + { + if (length == -1) length = arr.Length; + if (length == 0) return ""; + try + { + return Encoding.UTF8.GetString(arr, 0, length); + } + catch (Exception e) when // Only catch exception throwed by Encoding.UTF8.GetString + (e is DecoderFallbackException + || e is ArgumentException + || e is ArgumentNullException) + { + return BitConverter.ToString(arr, 0, length); + } + } + } + + /// + /// Concatenate two keys. + /// + /// The first to add. + /// The second to add. + [Obsolete("Prefer WithPrefix")] + public static RedisKey operator +(RedisKey x, RedisKey y) => + new RedisKey(ConcatenateBytes(x.KeyPrefix, x.KeyValue, y.KeyPrefix), y.KeyValue); + + internal static RedisKey WithPrefix(byte[]? prefix, RedisKey value) + { + if (prefix == null || prefix.Length == 0) return value; + if (value.KeyPrefix == null) return new RedisKey(prefix, value.KeyValue); + if (value.KeyValue == null) return new RedisKey(prefix, value.KeyPrefix); + + // two prefixes; darn + byte[] copy = new byte[prefix.Length + value.KeyPrefix.Length]; + Buffer.BlockCopy(prefix, 0, copy, 0, prefix.Length); + Buffer.BlockCopy(value.KeyPrefix, 0, copy, prefix.Length, value.KeyPrefix.Length); + return new RedisKey(copy, value.KeyValue); + } + + internal static byte[]? ConcatenateBytes(byte[]? a, object? b, byte[]? c) + { + if ((a == null || a.Length == 0) && (c == null || c.Length == 0)) + { + if (b == null) return null; + if (b is string s) return Encoding.UTF8.GetBytes(s); + return (byte[])b; + } + + int aLen = a?.Length ?? 0, + bLen = b == null ? 0 : (b is string bString + ? Encoding.UTF8.GetByteCount(bString) + : ((byte[])b).Length), + cLen = c?.Length ?? 0; + + var result = new byte[aLen + bLen + cLen]; + if (aLen != 0) Buffer.BlockCopy(a!, 0, result, 0, aLen); + if (bLen != 0) + { + if (b is string s) + { + Encoding.UTF8.GetBytes(s, 0, s.Length, result, aLen); + } + else + { + Buffer.BlockCopy((byte[])b!, 0, result, aLen, bLen); + } + } + if (cLen != 0) Buffer.BlockCopy(c!, 0, result, aLen + bLen, cLen); + return result; + } + + /// + /// Prepends p to this RedisKey, returning a new RedisKey. + /// + /// Avoids some allocations if possible, repeated Prepend/Appends make it less possible. + /// + /// + /// The prefix to prepend. + public RedisKey Prepend(RedisKey prefix) => WithPrefix(prefix, this); + + /// + /// Appends p to this RedisKey, returning a new RedisKey. + /// + /// Avoids some allocations if possible, repeated Prepend/Appends make it less possible. + /// + /// + /// The suffix to append. + public RedisKey Append(RedisKey suffix) => WithPrefix(this, suffix); + + internal bool TryGetSimpleBuffer([NotNullWhen(true)] out byte[]? arr) + { + arr = KeyValue is null ? Array.Empty() : KeyValue as byte[]; + return arr is not null && (KeyPrefix is null || KeyPrefix.Length == 0); + } + + internal int TotalLength() => + (KeyPrefix is null ? 0 : KeyPrefix.Length) + KeyValue switch + { + null => 0, + string s => Encoding.UTF8.GetByteCount(s), + _ => ((byte[])KeyValue).Length, + }; + + internal int CopyTo(Span destination) + { + int written = 0; + if (KeyPrefix is not null && KeyPrefix.Length != 0) + { + KeyPrefix.CopyTo(destination); + written += KeyPrefix.Length; + destination = destination.Slice(KeyPrefix.Length); + } + switch (KeyValue) + { + case null: + break; // nothing to do + case string s: + if (s.Length != 0) + { +#if NETCOREAPP + written += Encoding.UTF8.GetBytes(s, destination); +#else + unsafe + { + fixed (byte* bPtr = destination) + { + fixed (char* cPtr = s) + { + written += Encoding.UTF8.GetBytes(cPtr, s.Length, bPtr, destination.Length); + } + } + } +#endif + } + break; + default: + var arr = (byte[])KeyValue; + arr.CopyTo(destination); + written += arr.Length; + break; + } + return written; + } + } +} diff --git a/src/StackExchange.Redis/RedisLiterals.cs b/src/StackExchange.Redis/RedisLiterals.cs new file mode 100644 index 000000000..46a64cc88 --- /dev/null +++ b/src/StackExchange.Redis/RedisLiterals.cs @@ -0,0 +1,234 @@ +using System; + +namespace StackExchange.Redis +{ +#pragma warning disable SA1310 // Field names should not contain underscore +#pragma warning disable SA1311 // Static readonly fields should begin with upper-case letter + internal static class CommonReplies + { + public static readonly CommandBytes + ASK = "ASK ", + authFail_trimmed = CommandBytes.TrimToFit("ERR operation not permitted"), + backgroundSavingStarted_trimmed = CommandBytes.TrimToFit("Background saving started"), + backgroundSavingAOFStarted_trimmed = CommandBytes.TrimToFit("Background append only file rewriting started"), + databases = "databases", + loading = "LOADING ", + MOVED = "MOVED ", + NOAUTH = "NOAUTH ", + NOSCRIPT = "NOSCRIPT ", + no = "no", + OK = "OK", + one = "1", + PONG = "PONG", + QUEUED = "QUEUED", + READONLY = "READONLY ", + replica_read_only = "replica-read-only", + slave_read_only = "slave-read-only", + timeout = "timeout", + wildcard = "*", + WRONGPASS = "WRONGPASS", + yes = "yes", + zero = "0", + + // streams + length = "length", + radixTreeKeys = "radix-tree-keys", + radixTreeNodes = "radix-tree-nodes", + groups = "groups", + lastGeneratedId = "last-generated-id", + firstEntry = "first-entry", + lastEntry = "last-entry", + + // HELLO + version = "version", + proto = "proto", + role = "role", + mode = "mode", + id = "id"; + } + internal static class RedisLiterals + { + // unlike primary commands, these do not get altered by the command-map; we may as + // well compute the bytes once and share them + public static readonly RedisValue + ACLCAT = "ACLCAT", + ADDR = "ADDR", + AFTER = "AFTER", + AGGREGATE = "AGGREGATE", + ALPHA = "ALPHA", + AND = "AND", + ANDOR = "ANDOR", + ANY = "ANY", + ASC = "ASC", + AUTH = "AUTH", + BEFORE = "BEFORE", + BIT = "BIT", + BY = "BY", + BYLEX = "BYLEX", + BYSCORE = "BYSCORE", + BYTE = "BYTE", + CH = "CH", + CHANNELS = "CHANNELS", + COPY = "COPY", + COUNT = "COUNT", + DB = "DB", + @default = "default", + DESC = "DESC", + DIFF = "DIFF", + DIFF1 = "DIFF1", + DOCTOR = "DOCTOR", + ENCODING = "ENCODING", + EX = "EX", + EXAT = "EXAT", + EXISTS = "EXISTS", + FIELDS = "FIELDS", + FILTERBY = "FILTERBY", + FLUSH = "FLUSH", + FNX = "FNX", + FREQ = "FREQ", + FXX = "FXX", + GET = "GET", + GETKEYS = "GETKEYS", + GETNAME = "GETNAME", + GT = "GT", + HISTORY = "HISTORY", + ID = "ID", + IDX = "IDX", + IDLETIME = "IDLETIME", + KEEPTTL = "KEEPTTL", + KILL = "KILL", + LADDR = "LADDR", + LATEST = "LATEST", + LEFT = "LEFT", + LEN = "LEN", + lib_name = "lib-name", + lib_ver = "lib-ver", + LIMIT = "LIMIT", + LIST = "LIST", + LOAD = "LOAD", + LT = "LT", + MATCH = "MATCH", + MALLOC_STATS = "MALLOC-STATS", + MAX = "MAX", + MAXAGE = "MAXAGE", + MAXLEN = "MAXLEN", + MIN = "MIN", + MINMATCHLEN = "MINMATCHLEN", + MODULE = "MODULE", + NODES = "NODES", + NOSAVE = "NOSAVE", + NOT = "NOT", + NOVALUES = "NOVALUES", + NUMPAT = "NUMPAT", + NUMSUB = "NUMSUB", + NX = "NX", + OBJECT = "OBJECT", + ONE = "ONE", + OR = "OR", + PATTERN = "PATTERN", + PAUSE = "PAUSE", + PERSIST = "PERSIST", + PING = "PING", + PURGE = "PURGE", + PX = "PX", + PXAT = "PXAT", + RANK = "RANK", + REFCOUNT = "REFCOUNT", + REPLACE = "REPLACE", + RESET = "RESET", + RESETSTAT = "RESETSTAT", + REV = "REV", + REWRITE = "REWRITE", + RIGHT = "RIGHT", + SAVE = "SAVE", + SEGFAULT = "SEGFAULT", + SET = "SET", + SETINFO = "SETINFO", + SETNAME = "SETNAME", + SKIPME = "SKIPME", + STATS = "STATS", + STORE = "STORE", + TYPE = "TYPE", + USERNAME = "USERNAME", + WEIGHTS = "WEIGHTS", + WITHMATCHLEN = "WITHMATCHLEN", + WITHSCORES = "WITHSCORES", + WITHVALUES = "WITHVALUES", + XOR = "XOR", + XX = "XX", + + // Sentinel Literals + MASTERS = "MASTERS", + MASTER = "MASTER", + REPLICAS = "REPLICAS", + SLAVES = "SLAVES", + GETMASTERADDRBYNAME = "GET-MASTER-ADDR-BY-NAME", + // RESET = "RESET", + FAILOVER = "FAILOVER", + SENTINELS = "SENTINELS", + + // Sentinel Literals as of 2.8.4 + MONITOR = "MONITOR", + REMOVE = "REMOVE", + // SET = "SET", + + // replication states + connect = "connect", + connected = "connected", + connecting = "connecting", + handshake = "handshake", + none = "none", + sync = "sync", + + MinusSymbol = "-", + PlusSymbol = "+", + Wildcard = "*", + + // Geo Radius/Search Literals + BYBOX = "BYBOX", + BYRADIUS = "BYRADIUS", + FROMMEMBER = "FROMMEMBER", + FROMLONLAT = "FROMLONLAT", + STOREDIST = "STOREDIST", + WITHCOORD = "WITHCOORD", + WITHDIST = "WITHDIST", + WITHHASH = "WITHHASH", + + // geo units + ft = "ft", + km = "km", + m = "m", + mi = "mi", + + // misc (config, etc) + databases = "databases", + master = "master", + no = "no", + normal = "normal", + pubsub = "pubsub", + replica = "replica", + replica_read_only = "replica-read-only", + replication = "replication", + sentinel = "sentinel", + server = "server", + slave = "slave", + slave_read_only = "slave-read-only", + timeout = "timeout", + yes = "yes"; + + internal static RedisValue Get(Bitwise operation) => operation switch + { + Bitwise.And => AND, + Bitwise.Or => OR, + Bitwise.Xor => XOR, + Bitwise.Not => NOT, + Bitwise.Diff => DIFF, + Bitwise.Diff1 => DIFF1, + Bitwise.AndOr => ANDOR, + Bitwise.One => ONE, + _ => throw new ArgumentOutOfRangeException(nameof(operation)), + }; + } +#pragma warning restore SA1310 // Field names should not contain underscore +#pragma warning restore SA1311 // Static readonly fields should begin with upper-case letter +} diff --git a/src/StackExchange.Redis/RedisProtocol.cs b/src/StackExchange.Redis/RedisProtocol.cs new file mode 100644 index 000000000..077671bd6 --- /dev/null +++ b/src/StackExchange.Redis/RedisProtocol.cs @@ -0,0 +1,22 @@ +namespace StackExchange.Redis; + +/// +/// Indicates the protocol for communicating with the server. +/// +public enum RedisProtocol +{ + // note: the non-binary safe protocol is not supported by the client, although the parser does support it (it is used in the toy server) + + // important: please use "major_minor_revision" numbers (two digit minor/revision), to allow for possible scenarios like + // "hey, we've added RESP 3.1; oops, we've added RESP 3.1.1" + + /// + /// The protocol used by all redis server versions since 1.2, as defined by https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md. + /// + Resp2 = 2_00_00, // major__minor__revision + + /// + /// Opt-in variant introduced in server version 6, as defined by https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md. + /// + Resp3 = 3_00_00, // major__minor__revision +} diff --git a/src/StackExchange.Redis/RedisResult.cs b/src/StackExchange.Redis/RedisResult.cs new file mode 100644 index 000000000..4a1644c36 --- /dev/null +++ b/src/StackExchange.Redis/RedisResult.cs @@ -0,0 +1,714 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace StackExchange.Redis +{ + /// + /// Represents a general-purpose result from redis, that may be cast into various anticipated types. + /// + public abstract class RedisResult + { + /// + /// Do not use. + /// + [Obsolete("Please specify a result type", true)] // retained purely for binary compat + public RedisResult() : this(default) { } + + internal RedisResult(ResultType resultType) => Resp3Type = resultType; + + /// + /// Create a new RedisResult representing a single value. + /// + /// The to create a result from. + /// The type of result being represented. + /// new . + [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads", Justification = "Legacy compat.")] + public static RedisResult Create(RedisValue value, ResultType? resultType = null) => new SingleRedisResult(value, resultType); + + /// + /// Create a new RedisResult representing an array of values. + /// + /// The s to create a result from. + /// new . + public static RedisResult Create(RedisValue[] values) + => Create(values, ResultType.Array); + + /// + /// Create a new RedisResult representing an array of values. + /// + /// The s to create a result from. + /// The explicit data type. + /// new . + public static RedisResult Create(RedisValue[] values, ResultType resultType) => + values == null ? NullArray : values.Length == 0 ? EmptyArray(resultType) : + new ArrayRedisResult(Array.ConvertAll(values, value => new SingleRedisResult(value, null)), resultType); + + /// + /// Create a new RedisResult representing an array of values. + /// + /// The s to create a result from. + /// new . + public static RedisResult Create(RedisResult[] values) + => Create(values, ResultType.Array); + + /// + /// Create a new RedisResult representing an array of values. + /// + /// The s to create a result from. + /// The explicit data type. + /// new . + public static RedisResult Create(RedisResult[] values, ResultType resultType) + => values == null ? NullArray : values.Length == 0 ? EmptyArray(resultType) : new ArrayRedisResult(values, resultType); + + /// + /// An empty array result. + /// + internal static RedisResult EmptyArray(ResultType type) => type switch + { + ResultType.Array => s_EmptyArray ??= new ArrayRedisResult(Array.Empty(), type), + ResultType.Set => s_EmptySet ??= new ArrayRedisResult(Array.Empty(), type), + ResultType.Map => s_EmptyMap ??= new ArrayRedisResult(Array.Empty(), type), + _ => new ArrayRedisResult(Array.Empty(), type), + }; + + private static RedisResult? s_EmptyArray, s_EmptySet, s_EmptyMap; + + /// + /// A null array result. + /// + internal static RedisResult NullArray { get; } = new ArrayRedisResult(null, ResultType.Null); + + /// + /// A null single result, to use as a default for invalid returns. + /// + internal static RedisResult NullSingle { get; } = new SingleRedisResult(RedisValue.Null, ResultType.Null); + + /// + /// Gets the number of elements in this item if it is a valid array, or -1 otherwise. + /// + public virtual int Length => -1; + + /// + public sealed override string ToString() => ToString(out _) ?? ""; + + /// + /// Gets the string content as per , but also obtains the declared type from verbatim strings (for example LATENCY DOCTOR). + /// + /// The type of the returned string. + /// The content. + public abstract string? ToString(out string? type); + + /// + /// Internally, this is very similar to RawResult, except it is designed to be usable, + /// outside of the IO-processing pipeline: the buffers are standalone, etc. + /// + internal static bool TryCreate(PhysicalConnection? connection, in RawResult result, [NotNullWhen(true)] out RedisResult? redisResult) + { + try + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + redisResult = new SingleRedisResult(result.AsRedisValue(), result.Resp3Type); + return true; + case ResultType.Array: + if (result.IsNull) + { + redisResult = NullArray; + return true; + } + var items = result.GetItems(); + if (items.Length == 0) + { + redisResult = EmptyArray(result.Resp3Type); + return true; + } + var arr = new RedisResult[items.Length]; + int i = 0; + foreach (ref RawResult item in items) + { + if (TryCreate(connection, in item, out var next)) + { + arr[i++] = next; + } + else + { + redisResult = null; + return false; + } + } + redisResult = new ArrayRedisResult(arr, result.Resp3Type); + return true; + case ResultType.Error: + redisResult = new ErrorRedisResult(result.GetString(), result.Resp3Type); + return true; + default: + redisResult = null; + return false; + } + } + catch (Exception ex) + { + connection?.OnInternalError(ex); + redisResult = null; + return false; // will be logged as a protocol fail by the processor + } + } + + /// + /// Indicate the type of result that was received from redis, in RESP2 terms. + /// + [Obsolete($"Please use either {nameof(Resp2Type)} (simplified) or {nameof(Resp3Type)} (full)")] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public ResultType Type => Resp2Type; + + /// + /// Indicate the type of result that was received from redis, in RESP3 terms. + /// + public ResultType Resp3Type { get; } + + /// + /// Indicate the type of result that was received from redis, in RESP2 terms. + /// + public ResultType Resp2Type => Resp3Type == ResultType.Null ? Resp2NullType : Resp3Type.ToResp2(); + + internal virtual ResultType Resp2NullType => ResultType.BulkString; + + /// + /// Indicates whether this result was a null result. + /// + public abstract bool IsNull { get; } + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator string?(RedisResult? result) => result?.AsString(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator byte[]?(RedisResult? result) => result?.AsByteArray(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator double(RedisResult result) => result.AsDouble(); + + /// + /// Interprets the result as an . + /// + /// The result to convert to a . + public static explicit operator long(RedisResult result) => result.AsInt64(); + + /// + /// Interprets the result as an . + /// + /// The result to convert to a . + [CLSCompliant(false)] + public static explicit operator ulong(RedisResult result) => result.AsUInt64(); + + /// + /// Interprets the result as an . + /// + /// The result to convert to a . + public static explicit operator int(RedisResult result) => result.AsInt32(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator bool(RedisResult result) => result.AsBoolean(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator RedisValue(RedisResult? result) => result?.AsRedisValue() ?? RedisValue.Null; + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator RedisKey(RedisResult? result) => result?.AsRedisKey() ?? default; + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator double?(RedisResult? result) => result?.AsNullableDouble(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator long?(RedisResult? result) => result?.AsNullableInt64(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + [CLSCompliant(false)] + public static explicit operator ulong?(RedisResult? result) => result?.AsNullableUInt64(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator int?(RedisResult? result) => result?.AsNullableInt32(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator bool?(RedisResult? result) => result?.AsNullableBoolean(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator string?[]?(RedisResult? result) => result?.AsStringArray(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator byte[]?[]?(RedisResult? result) => result?.AsByteArrayArray(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator double[]?(RedisResult? result) => result?.AsDoubleArray(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator long[]?(RedisResult? result) => result?.AsInt64Array(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + [CLSCompliant(false)] + public static explicit operator ulong[]?(RedisResult? result) => result?.AsUInt64Array(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator int[]?(RedisResult? result) => result?.AsInt32Array(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator bool[]?(RedisResult? result) => result?.AsBooleanArray(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator RedisValue[]?(RedisResult? result) => result?.AsRedisValueArray(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator RedisKey[]?(RedisResult? result) => result?.AsRedisKeyArray(); + + /// + /// Interprets the result as a . + /// + /// The result to convert to a . + public static explicit operator RedisResult[]?(RedisResult? result) => result?.AsRedisResultArray(); + + /// + /// Interprets a multi-bulk result with successive key/name values as a dictionary keyed by name. + /// + /// The key comparator to use, or by default. + public Dictionary ToDictionary(IEqualityComparer? comparer = null) + { + var arr = AsRedisResultArray(); + if (arr is null) + { + return new Dictionary(); + } + int len = arr.Length / 2; + var result = new Dictionary(len, comparer ?? StringComparer.InvariantCultureIgnoreCase); + for (int i = 0; i < arr.Length; i += 2) + { + result.Add(arr[i].AsString()!, arr[i + 1]); + } + return result; + } + + /// + /// Get a sub-item by index. + /// + public virtual RedisResult this[int index] => throw new InvalidOperationException("Indexers can only be used on array results"); + + internal abstract bool AsBoolean(); + internal abstract bool[]? AsBooleanArray(); + internal abstract byte[]? AsByteArray(); + internal abstract byte[][]? AsByteArrayArray(); + internal abstract double AsDouble(); + internal abstract double[]? AsDoubleArray(); + internal abstract int AsInt32(); + internal abstract int[]? AsInt32Array(); + internal abstract long AsInt64(); + internal abstract ulong AsUInt64(); + internal abstract long[]? AsInt64Array(); + internal abstract ulong[]? AsUInt64Array(); + internal abstract bool? AsNullableBoolean(); + internal abstract double? AsNullableDouble(); + internal abstract int? AsNullableInt32(); + internal abstract long? AsNullableInt64(); + internal abstract ulong? AsNullableUInt64(); + internal abstract RedisKey AsRedisKey(); + internal abstract RedisKey[]? AsRedisKeyArray(); + internal abstract RedisResult[]? AsRedisResultArray(); + internal abstract RedisValue AsRedisValue(); + internal abstract RedisValue[]? AsRedisValueArray(); + internal abstract string? AsString(); + internal abstract string?[]? AsStringArray(); + + private sealed class ArrayRedisResult : RedisResult + { + public override bool IsNull => _value is null; + private readonly RedisResult[]? _value; + + internal override ResultType Resp2NullType => ResultType.Array; + + public ArrayRedisResult(RedisResult[]? value, ResultType resultType) : base(value is null ? ResultType.Null : resultType) + { + _value = value; + } + + public override int Length => _value is null ? -1 : _value.Length; + + public override string? ToString(out string? type) + { + type = null; + return _value == null ? "(nil)" : (_value.Length + " element(s)"); + } + + internal override bool AsBoolean() + { + if (IsSingleton) return _value![0].AsBoolean(); + throw new InvalidCastException(); + } + + public override RedisResult this[int index] => _value![index]; + + internal override bool[]? AsBooleanArray() => IsNull ? null : Array.ConvertAll(_value!, x => x.AsBoolean()); + + internal override byte[]? AsByteArray() + { + if (IsSingleton) return _value![0].AsByteArray(); + throw new InvalidCastException(); + } + + internal override byte[][]? AsByteArrayArray() + => IsNull ? null + : _value!.Length == 0 + ? Array.Empty() + : Array.ConvertAll(_value, x => x.AsByteArray()!); + + private bool IsSingleton => _value?.Length == 1; + private bool IsEmpty => _value?.Length == 0; + internal override double AsDouble() + { + if (IsSingleton) return _value![0].AsDouble(); + throw new InvalidCastException(); + } + + internal override double[]? AsDoubleArray() + => IsNull ? null + : IsEmpty ? Array.Empty() + : Array.ConvertAll(_value!, x => x.AsDouble()); + + internal override int AsInt32() + { + if (IsSingleton) return _value![0].AsInt32(); + throw new InvalidCastException(); + } + + internal override int[]? AsInt32Array() + => IsNull ? null + : IsEmpty ? Array.Empty() + : Array.ConvertAll(_value!, x => x.AsInt32()); + + internal override long AsInt64() + { + if (IsSingleton) return _value![0].AsInt64(); + throw new InvalidCastException(); + } + internal override ulong AsUInt64() + { + if (IsSingleton) return _value![0].AsUInt64(); + throw new InvalidCastException(); + } + + internal override long[]? AsInt64Array() + => IsNull ? null + : IsEmpty ? Array.Empty() + : Array.ConvertAll(_value!, x => x.AsInt64()); + + internal override ulong[]? AsUInt64Array() + => IsNull ? null + : IsEmpty ? Array.Empty() + : Array.ConvertAll(_value!, x => x.AsUInt64()); + + internal override bool? AsNullableBoolean() + { + if (IsSingleton) return _value![0].AsNullableBoolean(); + throw new InvalidCastException(); + } + + internal override double? AsNullableDouble() + { + if (IsSingleton) return _value![0].AsNullableDouble(); + throw new InvalidCastException(); + } + + internal override int? AsNullableInt32() + { + if (IsSingleton) return _value![0].AsNullableInt32(); + throw new InvalidCastException(); + } + + internal override long? AsNullableInt64() + { + if (IsSingleton) return _value![0].AsNullableInt64(); + throw new InvalidCastException(); + } + internal override ulong? AsNullableUInt64() + { + if (IsSingleton) return _value![0].AsNullableUInt64(); + throw new InvalidCastException(); + } + + internal override RedisKey AsRedisKey() + { + if (IsSingleton) return _value![0].AsRedisKey(); + throw new InvalidCastException(); + } + + internal override RedisKey[]? AsRedisKeyArray() + => IsNull ? null + : IsEmpty ? Array.Empty() + : Array.ConvertAll(_value!, x => x.AsRedisKey()); + + internal override RedisResult[]? AsRedisResultArray() => _value; + + internal override RedisValue AsRedisValue() + { + if (IsSingleton) return _value![0].AsRedisValue(); + throw new InvalidCastException(); + } + + internal override RedisValue[]? AsRedisValueArray() + => IsNull ? null + : IsEmpty ? Array.Empty() + : Array.ConvertAll(_value!, x => x.AsRedisValue()); + + internal override string? AsString() + { + if (IsSingleton) return _value![0].AsString(); + throw new InvalidCastException(); + } + + internal override string?[]? AsStringArray() + => IsNull ? null + : IsEmpty ? Array.Empty() + : Array.ConvertAll(_value!, x => x.AsString()); + } + + /// + /// Create a from a key. + /// + /// The to create a from. + public static RedisResult Create(RedisKey key) => Create(key.AsRedisValue(), ResultType.BulkString); + + /// + /// Create a from a channel. + /// + /// The to create a from. + public static RedisResult Create(RedisChannel channel) => Create((byte[]?)channel, ResultType.BulkString); + + private sealed class ErrorRedisResult : RedisResult + { + private readonly string value; + + public ErrorRedisResult(string? value, ResultType type) : base(type) + { + this.value = value ?? throw new ArgumentNullException(nameof(value)); + } + + public override bool IsNull => value == null; + public override string? ToString(out string? type) + { + type = null; + return value; + } + internal override bool AsBoolean() => throw new RedisServerException(value); + internal override bool[] AsBooleanArray() => throw new RedisServerException(value); + internal override byte[] AsByteArray() => throw new RedisServerException(value); + internal override byte[][] AsByteArrayArray() => throw new RedisServerException(value); + internal override double AsDouble() => throw new RedisServerException(value); + internal override double[] AsDoubleArray() => throw new RedisServerException(value); + internal override int AsInt32() => throw new RedisServerException(value); + internal override int[] AsInt32Array() => throw new RedisServerException(value); + internal override long AsInt64() => throw new RedisServerException(value); + internal override ulong AsUInt64() => throw new RedisServerException(value); + internal override long[] AsInt64Array() => throw new RedisServerException(value); + internal override ulong[] AsUInt64Array() => throw new RedisServerException(value); + internal override bool? AsNullableBoolean() => throw new RedisServerException(value); + internal override double? AsNullableDouble() => throw new RedisServerException(value); + internal override int? AsNullableInt32() => throw new RedisServerException(value); + internal override long? AsNullableInt64() => throw new RedisServerException(value); + internal override ulong? AsNullableUInt64() => throw new RedisServerException(value); + internal override RedisKey AsRedisKey() => throw new RedisServerException(value); + internal override RedisKey[] AsRedisKeyArray() => throw new RedisServerException(value); + internal override RedisResult[] AsRedisResultArray() => throw new RedisServerException(value); + internal override RedisValue AsRedisValue() => throw new RedisServerException(value); + internal override RedisValue[] AsRedisValueArray() => throw new RedisServerException(value); + internal override string? AsString() => throw new RedisServerException(value); + internal override string?[]? AsStringArray() => throw new RedisServerException(value); + } + + private sealed class SingleRedisResult : RedisResult, IConvertible + { + private readonly RedisValue _value; + + public SingleRedisResult(RedisValue value, ResultType? resultType) : base(value.IsNull ? ResultType.Null : resultType ?? (value.IsInteger ? ResultType.Integer : ResultType.BulkString)) + { + _value = value; + } + + public override bool IsNull => Resp3Type == ResultType.Null || _value.IsNull; + + public override string? ToString(out string? type) + { + type = null; + string? s = _value; + if (Resp3Type == ResultType.VerbatimString && s is not null && s.Length >= 4 && s[3] == ':') + { + // remove the prefix + type = s.Substring(0, 3); + s = s.Substring(4); + } + return s; + } + + internal override bool AsBoolean() => (bool)_value; + internal override bool[] AsBooleanArray() => new[] { AsBoolean() }; + internal override byte[]? AsByteArray() => (byte[]?)_value; + internal override byte[][] AsByteArrayArray() => new[] { AsByteArray()! }; + internal override double AsDouble() => (double)_value; + internal override double[] AsDoubleArray() => new[] { AsDouble() }; + internal override int AsInt32() => (int)_value; + internal override int[] AsInt32Array() => new[] { AsInt32() }; + internal override long AsInt64() => (long)_value; + internal override ulong AsUInt64() => (ulong)_value; + internal override long[] AsInt64Array() => new[] { AsInt64() }; + internal override ulong[] AsUInt64Array() => new[] { AsUInt64() }; + internal override bool? AsNullableBoolean() => (bool?)_value; + internal override double? AsNullableDouble() => (double?)_value; + internal override int? AsNullableInt32() => (int?)_value; + internal override long? AsNullableInt64() => (long?)_value; + internal override ulong? AsNullableUInt64() => (ulong?)_value; + internal override RedisKey AsRedisKey() => (byte[]?)_value; + internal override RedisKey[] AsRedisKeyArray() => new[] { AsRedisKey() }; + internal override RedisResult[] AsRedisResultArray() => throw new InvalidCastException(); + internal override RedisValue AsRedisValue() => _value; + internal override RedisValue[] AsRedisValueArray() => new[] { AsRedisValue() }; + internal override string? AsString() => (string?)_value; + internal override string?[]? AsStringArray() => new[] { AsString() }; + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + bool IConvertible.ToBoolean(IFormatProvider? provider) => AsBoolean(); + char IConvertible.ToChar(IFormatProvider? provider) + { + checked { return (char)AsInt32(); } + } + sbyte IConvertible.ToSByte(IFormatProvider? provider) + { + checked { return (sbyte)AsInt32(); } + } + byte IConvertible.ToByte(IFormatProvider? provider) + { + checked { return (byte)AsInt32(); } + } + short IConvertible.ToInt16(IFormatProvider? provider) + { + checked { return (short)AsInt32(); } + } + ushort IConvertible.ToUInt16(IFormatProvider? provider) + { + checked { return (ushort)AsInt32(); } + } + int IConvertible.ToInt32(IFormatProvider? provider) => AsInt32(); + uint IConvertible.ToUInt32(IFormatProvider? provider) + { + checked { return (uint)AsInt64(); } + } + long IConvertible.ToInt64(IFormatProvider? provider) => AsInt64(); + ulong IConvertible.ToUInt64(IFormatProvider? provider) + { + checked { return (ulong)AsInt64(); } + } + float IConvertible.ToSingle(IFormatProvider? provider) => (float)AsDouble(); + double IConvertible.ToDouble(IFormatProvider? provider) => AsDouble(); + decimal IConvertible.ToDecimal(IFormatProvider? provider) + { + // we can do this safely *sometimes* + if (Resp2Type == ResultType.Integer) return AsInt64(); + // but not always + ThrowNotSupported(); + return default; + } + DateTime IConvertible.ToDateTime(IFormatProvider? provider) + { + ThrowNotSupported(); + return default; + } + string IConvertible.ToString(IFormatProvider? provider) => AsString()!; + object IConvertible.ToType(Type conversionType, IFormatProvider? provider) + { + switch (System.Type.GetTypeCode(conversionType)) + { + case TypeCode.Boolean: return AsBoolean(); + case TypeCode.Char: checked { return (char)AsInt32(); } + case TypeCode.SByte: checked { return (sbyte)AsInt32(); } + case TypeCode.Byte: checked { return (byte)AsInt32(); } + case TypeCode.Int16: checked { return (short)AsInt32(); } + case TypeCode.UInt16: checked { return (ushort)AsInt32(); } + case TypeCode.Int32: return AsInt32(); + case TypeCode.UInt32: checked { return (uint)AsInt64(); } + case TypeCode.Int64: return AsInt64(); + case TypeCode.UInt64: checked { return (ulong)AsInt64(); } + case TypeCode.Single: return (float)AsDouble(); + case TypeCode.Double: return AsDouble(); + case TypeCode.Decimal when Resp2Type == ResultType.Integer: return AsInt64(); + case TypeCode.String: return AsString()!; + default: + ThrowNotSupported(); + return default; + } + } + + [DoesNotReturn] + private void ThrowNotSupported([CallerMemberName] string? caller = null) + => throw new NotSupportedException($"{typeof(SingleRedisResult).FullName} does not support {nameof(IConvertible)}.{caller} with value '{AsString()}'"); + } + } +} diff --git a/src/StackExchange.Redis/RedisServer.cs b/src/StackExchange.Redis/RedisServer.cs new file mode 100644 index 000000000..3bc306c69 --- /dev/null +++ b/src/StackExchange.Redis/RedisServer.cs @@ -0,0 +1,1180 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Pipelines.Sockets.Unofficial.Arenas; + +namespace StackExchange.Redis +{ + internal sealed class RedisServer : RedisBase, IServer + { + private readonly ServerEndPoint server; + + internal RedisServer(ServerEndPoint server, object? asyncState) : base(server.Multiplexer, asyncState) + { + this.server = server; // definitely can't be null because .Multiplexer in base call + } + + int IServer.DatabaseCount => server.Databases; + + public ClusterConfiguration? ClusterConfiguration => server.ClusterConfiguration; + + public EndPoint EndPoint => server.EndPoint; + + public RedisFeatures Features => server.GetFeatures(); + + public bool IsConnected => server.IsConnected; + + bool IServer.IsSlave => IsReplica; + public bool IsReplica => server.IsReplica; + + public RedisProtocol Protocol => server.Protocol ?? (multiplexer.RawConfig.TryResp3() ? RedisProtocol.Resp3 : RedisProtocol.Resp2); + + bool IServer.AllowSlaveWrites + { + get => AllowReplicaWrites; + set => AllowReplicaWrites = value; + } + public bool AllowReplicaWrites + { + get => server.AllowReplicaWrites; + set => server.AllowReplicaWrites = value; + } + + public ServerType ServerType => server.ServerType; + + public Version Version => server.Version; + + public void ClientKill(EndPoint endpoint, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.KILL, (RedisValue)Format.ToString(endpoint)); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task ClientKillAsync(EndPoint endpoint, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.KILL, (RedisValue)Format.ToString(endpoint)); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public long ClientKill(long? id = null, ClientType? clientType = null, EndPoint? endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None) + { + var msg = GetClientKillMessage(endpoint, id, clientType, skipMe, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task ClientKillAsync(long? id = null, ClientType? clientType = null, EndPoint? endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None) + { + var msg = GetClientKillMessage(endpoint, id, clientType, skipMe, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long ClientKill(ClientKillFilter filter, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CLIENT, filter.ToList(Features.ReplicaCommands)); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task ClientKillAsync(ClientKillFilter filter, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CLIENT, filter.ToList(Features.ReplicaCommands)); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + private Message GetClientKillMessage(EndPoint? endpoint, long? id, ClientType? clientType, bool? skipMe, CommandFlags flags) + { + var args = new ClientKillFilter().WithId(id).WithClientType(clientType).WithEndpoint(endpoint).WithSkipMe(skipMe).ToList(Features.ReplicaCommands); + return Message.Create(-1, flags, RedisCommand.CLIENT, args); + } + + public ClientInfo[] ClientList(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.LIST); + return ExecuteSync(msg, ClientInfo.Processor, defaultValue: Array.Empty()); + } + + public Task ClientListAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.LIST); + return ExecuteAsync(msg, ClientInfo.Processor, defaultValue: Array.Empty()); + } + + public ClusterConfiguration? ClusterNodes(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); + return ExecuteSync(msg, ResultProcessor.ClusterNodes); + } + + public Task ClusterNodesAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); + return ExecuteAsync(msg, ResultProcessor.ClusterNodes); + } + + public string? ClusterNodesRaw(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); + return ExecuteSync(msg, ResultProcessor.ClusterNodesRaw); + } + + public Task ClusterNodesRawAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); + return ExecuteAsync(msg, ResultProcessor.ClusterNodesRaw); + } + + public KeyValuePair[] ConfigGet(RedisValue pattern = default, CommandFlags flags = CommandFlags.None) + { + if (pattern.IsNullOrEmpty) pattern = RedisLiterals.Wildcard; + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, pattern); + return ExecuteSync(msg, ResultProcessor.StringPairInterleaved, defaultValue: Array.Empty>()); + } + + public Task[]> ConfigGetAsync(RedisValue pattern = default, CommandFlags flags = CommandFlags.None) + { + if (pattern.IsNullOrEmpty) pattern = RedisLiterals.Wildcard; + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, pattern); + return ExecuteAsync(msg, ResultProcessor.StringPairInterleaved, defaultValue: Array.Empty>()); + } + + public void ConfigResetStatistics(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.RESETSTAT); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task ConfigResetStatisticsAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.RESETSTAT); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public void ConfigRewrite(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.REWRITE); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task ConfigRewriteAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.REWRITE); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public void ConfigSet(RedisValue setting, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.SET, setting, value); + ExecuteSync(msg, ResultProcessor.DemandOK); + ExecuteSync(Message.Create(-1, flags | CommandFlags.FireAndForget, RedisCommand.CONFIG, RedisLiterals.GET, setting), ResultProcessor.AutoConfigure); + } + + public Task ConfigSetAsync(RedisValue setting, RedisValue value, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.SET, setting, value); + var task = ExecuteAsync(msg, ResultProcessor.DemandOK); + ExecuteSync(Message.Create(-1, flags | CommandFlags.FireAndForget, RedisCommand.CONFIG, RedisLiterals.GET, setting), ResultProcessor.AutoConfigure); + return task; + } + + public long CommandCount(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.COMMAND, RedisLiterals.COUNT); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task CommandCountAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.COMMAND, RedisLiterals.COUNT); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public RedisKey[] CommandGetKeys(RedisValue[] command, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.COMMAND, AddValueToArray(RedisLiterals.GETKEYS, command)); + return ExecuteSync(msg, ResultProcessor.RedisKeyArray, defaultValue: Array.Empty()); + } + + public Task CommandGetKeysAsync(RedisValue[] command, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.COMMAND, AddValueToArray(RedisLiterals.GETKEYS, command)); + return ExecuteAsync(msg, ResultProcessor.RedisKeyArray, defaultValue: Array.Empty()); + } + + public string[] CommandList(RedisValue? moduleName = null, RedisValue? category = null, RedisValue? pattern = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetCommandListMessage(moduleName, category, pattern, flags); + return ExecuteSync(msg, ResultProcessor.StringArray, defaultValue: Array.Empty()); + } + + public Task CommandListAsync(RedisValue? moduleName = null, RedisValue? category = null, RedisValue? pattern = null, CommandFlags flags = CommandFlags.None) + { + var msg = GetCommandListMessage(moduleName, category, pattern, flags); + return ExecuteAsync(msg, ResultProcessor.StringArray, defaultValue: Array.Empty()); + } + + private Message GetCommandListMessage(RedisValue? moduleName = null, RedisValue? category = null, RedisValue? pattern = null, CommandFlags flags = CommandFlags.None) + { + if (moduleName == null && category == null && pattern == null) + { + return Message.Create(-1, flags, RedisCommand.COMMAND, RedisLiterals.LIST); + } + else if (moduleName != null && category == null && pattern == null) + { + return Message.Create(-1, flags, RedisCommand.COMMAND, MakeArray(RedisLiterals.LIST, RedisLiterals.FILTERBY, RedisLiterals.MODULE, (RedisValue)moduleName)); + } + else if (moduleName == null && category != null && pattern == null) + { + return Message.Create(-1, flags, RedisCommand.COMMAND, MakeArray(RedisLiterals.LIST, RedisLiterals.FILTERBY, RedisLiterals.ACLCAT, (RedisValue)category)); + } + else if (moduleName == null && category == null && pattern != null) + { + return Message.Create(-1, flags, RedisCommand.COMMAND, MakeArray(RedisLiterals.LIST, RedisLiterals.FILTERBY, RedisLiterals.PATTERN, (RedisValue)pattern)); + } + else + { + throw new ArgumentException("More then one filter is not allowed"); + } + } + + private RedisValue[] AddValueToArray(RedisValue val, RedisValue[] arr) + { + var result = new RedisValue[arr.Length + 1]; + var i = 0; + result[i++] = val; + foreach (var item in arr) result[i++] = item; + return result; + } + + private RedisValue[] MakeArray(params RedisValue[] redisValues) => redisValues; + + public long DatabaseSize(int database = -1, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.DBSIZE); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task DatabaseSizeAsync(int database = -1, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.DBSIZE); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public RedisValue Echo(RedisValue message, CommandFlags flags) + { + var msg = Message.Create(-1, flags, RedisCommand.ECHO, message); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task EchoAsync(RedisValue message, CommandFlags flags) + { + var msg = Message.Create(-1, flags, RedisCommand.ECHO, message); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public void FlushAllDatabases(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.FLUSHALL); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task FlushAllDatabasesAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.FLUSHALL); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public void FlushDatabase(int database = -1, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.FLUSHDB); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task FlushDatabaseAsync(int database = -1, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.FLUSHDB); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public ServerCounters GetCounters() => server.GetCounters(); + + private static IGrouping>[] InfoDefault => + Array.Empty>>(); + + public IGrouping>[] Info(RedisValue section = default, CommandFlags flags = CommandFlags.None) + { + var msg = section.IsNullOrEmpty + ? Message.Create(-1, flags, RedisCommand.INFO) + : Message.Create(-1, flags, RedisCommand.INFO, section); + + return ExecuteSync(msg, ResultProcessor.Info, defaultValue: InfoDefault); + } + + public Task>[]> InfoAsync(RedisValue section = default, CommandFlags flags = CommandFlags.None) + { + var msg = section.IsNullOrEmpty + ? Message.Create(-1, flags, RedisCommand.INFO) + : Message.Create(-1, flags, RedisCommand.INFO, section); + + return ExecuteAsync(msg, ResultProcessor.Info, defaultValue: InfoDefault); + } + + public string? InfoRaw(RedisValue section = default, CommandFlags flags = CommandFlags.None) + { + var msg = section.IsNullOrEmpty + ? Message.Create(-1, flags, RedisCommand.INFO) + : Message.Create(-1, flags, RedisCommand.INFO, section); + + return ExecuteSync(msg, ResultProcessor.String); + } + + public Task InfoRawAsync(RedisValue section = default, CommandFlags flags = CommandFlags.None) + { + var msg = section.IsNullOrEmpty + ? Message.Create(-1, flags, RedisCommand.INFO) + : Message.Create(-1, flags, RedisCommand.INFO, section); + + return ExecuteAsync(msg, ResultProcessor.String); + } + + IEnumerable IServer.Keys(int database, RedisValue pattern, int pageSize, CommandFlags flags) + => KeysAsync(database, pattern, pageSize, CursorUtils.Origin, 0, flags); + + IEnumerable IServer.Keys(int database, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => KeysAsync(database, pattern, pageSize, cursor, pageOffset, flags); + + IAsyncEnumerable IServer.KeysAsync(int database, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + => KeysAsync(database, pattern, pageSize, cursor, pageOffset, flags); + + private CursorEnumerable KeysAsync(int database, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) + { + database = multiplexer.ApplyDefaultDatabase(database); + if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + if (CursorUtils.IsNil(pattern)) pattern = RedisLiterals.Wildcard; + + if (multiplexer.CommandMap.IsAvailable(RedisCommand.SCAN)) + { + var features = server.GetFeatures(); + + if (features.Scan) return new KeysScanEnumerable(this, database, pattern, pageSize, cursor, pageOffset, flags); + } + + if (cursor != 0) throw ExceptionFactory.NoCursor(RedisCommand.KEYS); + Message msg = Message.Create(database, flags, RedisCommand.KEYS, pattern); + return CursorEnumerable.From(this, server, ExecuteAsync(msg, ResultProcessor.RedisKeyArray, defaultValue: Array.Empty()), pageOffset); + } + + public DateTime LastSave(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.LASTSAVE); + return ExecuteSync(msg, ResultProcessor.DateTime); + } + + public Task LastSaveAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.LASTSAVE); + return ExecuteAsync(msg, ResultProcessor.DateTime); + } + + public void MakeMaster(ReplicationChangeOptions options, TextWriter? log = null) + { + // Do you believe in magic? + multiplexer.MakePrimaryAsync(server, options, log).Wait(60000); + } + + public async Task MakePrimaryAsync(ReplicationChangeOptions options, TextWriter? log = null) + { + await multiplexer.MakePrimaryAsync(server, options, log).ForAwait(); + } + + public Role Role(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.ROLE); + return ExecuteSync(msg, ResultProcessor.Role, defaultValue: Redis.Role.Null); + } + + public Task RoleAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.ROLE); + return ExecuteAsync(msg, ResultProcessor.Role, defaultValue: Redis.Role.Null); + } + + public void Save(SaveType type, CommandFlags flags = CommandFlags.None) + { + var msg = GetSaveMessage(type, flags); + ExecuteSync(msg, GetSaveResultProcessor(type)); + } + + public Task SaveAsync(SaveType type, CommandFlags flags = CommandFlags.None) + { + var msg = GetSaveMessage(type, flags); + return ExecuteAsync(msg, GetSaveResultProcessor(type)); + } + + public bool ScriptExists(string script, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Hash(script)); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public bool ScriptExists(byte[] sha1, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Encode(sha1)); + return ExecuteSync(msg, ResultProcessor.Boolean); + } + + public Task ScriptExistsAsync(string script, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Hash(script)); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public Task ScriptExistsAsync(byte[] sha1, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Encode(sha1)); + return ExecuteAsync(msg, ResultProcessor.Boolean); + } + + public void ScriptFlush(CommandFlags flags = CommandFlags.None) + { + if (!multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(multiplexer.RawConfig.IncludeDetailInExceptions, RedisCommand.SCRIPT, null, server); + var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.FLUSH); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task ScriptFlushAsync(CommandFlags flags = CommandFlags.None) + { + if (!multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(multiplexer.RawConfig.IncludeDetailInExceptions, RedisCommand.SCRIPT, null, server); + var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.FLUSH); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public byte[] ScriptLoad(string script, CommandFlags flags = CommandFlags.None) + { + var msg = new RedisDatabase.ScriptLoadMessage(flags, script); + return ExecuteSync(msg, ResultProcessor.ScriptLoad, defaultValue: Array.Empty()); // Note: default isn't used on failure - we'll throw + } + + public Task ScriptLoadAsync(string script, CommandFlags flags = CommandFlags.None) + { + var msg = new RedisDatabase.ScriptLoadMessage(flags, script); + return ExecuteAsync(msg, ResultProcessor.ScriptLoad, defaultValue: Array.Empty()); // Note: default isn't used on failure - we'll throw + } + + public LoadedLuaScript ScriptLoad(LuaScript script, CommandFlags flags = CommandFlags.None) + { + return script.Load(this, flags); + } + + public Task ScriptLoadAsync(LuaScript script, CommandFlags flags = CommandFlags.None) + { + return script.LoadAsync(this, flags); + } + + public void Shutdown(ShutdownMode shutdownMode = ShutdownMode.Default, CommandFlags flags = CommandFlags.None) + { + Message msg = shutdownMode switch + { + ShutdownMode.Default => Message.Create(-1, flags, RedisCommand.SHUTDOWN), + ShutdownMode.Always => Message.Create(-1, flags, RedisCommand.SHUTDOWN, RedisLiterals.SAVE), + ShutdownMode.Never => Message.Create(-1, flags, RedisCommand.SHUTDOWN, RedisLiterals.NOSAVE), + _ => throw new ArgumentOutOfRangeException(nameof(shutdownMode)), + }; + try + { + ExecuteSync(msg, ResultProcessor.DemandOK); + } + catch (RedisConnectionException ex) when (ex.FailureType == ConnectionFailureType.SocketClosed || ex.FailureType == ConnectionFailureType.SocketFailure) + { + // that's fine + return; + } + } + + public CommandTrace[] SlowlogGet(int count = 0, CommandFlags flags = CommandFlags.None) + { + var msg = count > 0 + ? Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET, count) + : Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET); + + return ExecuteSync(msg, CommandTrace.Processor, defaultValue: Array.Empty()); + } + + public Task SlowlogGetAsync(int count = 0, CommandFlags flags = CommandFlags.None) + { + var msg = count > 0 + ? Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET, count) + : Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET); + + return ExecuteAsync(msg, CommandTrace.Processor, defaultValue: Array.Empty()); + } + + public void SlowlogReset(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.RESET); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task SlowlogResetAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.RESET); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public RedisValue StringGet(int db, RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(db, flags, RedisCommand.GET, key); + return ExecuteSync(msg, ResultProcessor.RedisValue); + } + + public Task StringGetAsync(int db, RedisKey key, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(db, flags, RedisCommand.GET, key); + return ExecuteAsync(msg, ResultProcessor.RedisValue); + } + + public RedisChannel[] SubscriptionChannels(RedisChannel pattern = default, CommandFlags flags = CommandFlags.None) + { + var msg = pattern.IsNullOrEmpty ? Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS) + : Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS, pattern); + return ExecuteSync(msg, ResultProcessor.RedisChannelArrayLiteral, defaultValue: Array.Empty()); + } + + public Task SubscriptionChannelsAsync(RedisChannel pattern = default, CommandFlags flags = CommandFlags.None) + { + var msg = pattern.IsNullOrEmpty ? Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS) + : Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS, pattern); + return ExecuteAsync(msg, ResultProcessor.RedisChannelArrayLiteral, defaultValue: Array.Empty()); + } + + public long SubscriptionPatternCount(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMPAT); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task SubscriptionPatternCountAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMPAT); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long SubscriptionSubscriberCount(RedisChannel channel, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); + return ExecuteSync(msg, ResultProcessor.PubSubNumSub); + } + + public Task SubscriptionSubscriberCountAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); + return ExecuteAsync(msg, ResultProcessor.PubSubNumSub); + } + + public void SwapDatabases(int first, int second, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SWAPDB, first, second); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task SwapDatabasesAsync(int first, int second, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SWAPDB, first, second); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public DateTime Time(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.TIME); + return ExecuteSync(msg, ResultProcessor.DateTime); + } + + public Task TimeAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.TIME); + return ExecuteAsync(msg, ResultProcessor.DateTime); + } + + internal static Message CreateReplicaOfMessage(ServerEndPoint sendMessageTo, EndPoint? primaryEndpoint, CommandFlags flags = CommandFlags.None) + { + RedisValue host, port; + if (primaryEndpoint == null) + { + host = "NO"; + port = "ONE"; + } + else + { + if (Format.TryGetHostPort(primaryEndpoint, out string? hostRaw, out int? portRaw)) + { + host = hostRaw; + port = portRaw; + } + else + { + throw new NotSupportedException("Unknown endpoint type: " + primaryEndpoint.GetType().Name); + } + } + return Message.Create(-1, flags, sendMessageTo.GetFeatures().ReplicaCommands ? RedisCommand.REPLICAOF : RedisCommand.SLAVEOF, host, port); + } + + private Message? GetTiebreakerRemovalMessage() + { + var configuration = multiplexer.RawConfig; + + if (configuration.TryGetTieBreaker(out var tieBreakerKey) && multiplexer.CommandMap.IsAvailable(RedisCommand.DEL)) + { + var msg = Message.Create(0, CommandFlags.FireAndForget | CommandFlags.NoRedirect, RedisCommand.DEL, tieBreakerKey); + msg.SetInternalCall(); + return msg; + } + return null; + } + + private Message? GetConfigChangeMessage() + { + // attempt to broadcast a reconfigure message to anybody listening to this server + var channel = multiplexer.ConfigurationChangedChannel; + if (channel != null && multiplexer.CommandMap.IsAvailable(RedisCommand.PUBLISH)) + { + var msg = Message.Create(-1, CommandFlags.FireAndForget | CommandFlags.NoRedirect, RedisCommand.PUBLISH, (RedisValue)channel, RedisLiterals.Wildcard); + msg.SetInternalCall(); + return msg; + } + return null; + } + + internal override Task ExecuteAsync(Message? message, ResultProcessor? processor, T defaultValue, ServerEndPoint? server = null) + { + // inject our expected server automatically + server ??= this.server; + FixFlags(message, server); + if (!server.IsConnected) + { + if (message == null) return CompletedTask.FromDefault(defaultValue, asyncState); + if (message.IsFireAndForget) return CompletedTask.FromDefault(defaultValue, null); // F+F explicitly does not get async-state + + // After the "don't care" cases above, if we can't queue then it's time to error - otherwise call through to queuing. + if (!multiplexer.RawConfig.BacklogPolicy.QueueWhileDisconnected) + { + // no need to deny exec-sync here; will be complete before they see if + var tcs = TaskSource.Create(asyncState); + ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(multiplexer, message, server)); + return tcs.Task; + } + } + return base.ExecuteAsync(message, processor, defaultValue, server); + } + + internal override Task ExecuteAsync(Message? message, ResultProcessor? processor, ServerEndPoint? server = null) where T : default + { + // inject our expected server automatically + server ??= this.server; + FixFlags(message, server); + if (!server.IsConnected) + { + if (message == null) return CompletedTask.Default(asyncState); + if (message.IsFireAndForget) return CompletedTask.Default(null); // F+F explicitly does not get async-state + + // After the "don't care" cases above, if we can't queue then it's time to error - otherwise call through to queuing. + if (!multiplexer.RawConfig.BacklogPolicy.QueueWhileDisconnected) + { + // no need to deny exec-sync here; will be complete before they see if + var tcs = TaskSource.Create(asyncState); + ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(multiplexer, message, server)); + return tcs.Task; + } + } + return base.ExecuteAsync(message, processor, server); + } + + [return: NotNullIfNotNull("defaultValue")] + internal override T? ExecuteSync(Message? message, ResultProcessor? processor, ServerEndPoint? server = null, T? defaultValue = default) where T : default + { + // inject our expected server automatically + if (server == null) server = this.server; + FixFlags(message, server); + if (!server.IsConnected) + { + if (message == null || message.IsFireAndForget) return defaultValue; + + // After the "don't care" cases above, if we can't queue then it's time to error - otherwise call through to queuing. + if (!multiplexer.RawConfig.BacklogPolicy.QueueWhileDisconnected) + { + throw ExceptionFactory.NoConnectionAvailable(multiplexer, message, server); + } + } + return base.ExecuteSync(message, processor, server, defaultValue); + } + + internal override RedisFeatures GetFeatures(in RedisKey key, CommandFlags flags, RedisCommand command, out ServerEndPoint server) + { + server = this.server; + return server.GetFeatures(); + } + + void IServer.SlaveOf(EndPoint master, CommandFlags flags) => ReplicaOf(master, flags); + + public void ReplicaOf(EndPoint master, CommandFlags flags = CommandFlags.None) + { + if (master == server.EndPoint) + { + throw new ArgumentException("Cannot replicate to self"); + } + +#pragma warning disable CS0618 // Type or member is obsolete + // attempt to cease having an opinion on the master; will resume that when replication completes + // (note that this may fail; we aren't depending on it) + if (GetTiebreakerRemovalMessage() is Message tieBreakerRemoval) + { + tieBreakerRemoval.SetSource(ResultProcessor.Boolean, null); + server.GetBridge(tieBreakerRemoval)?.TryWriteSync(tieBreakerRemoval, server.IsReplica); + } + + var replicaOfMsg = CreateReplicaOfMessage(server, master, flags); + ExecuteSync(replicaOfMsg, ResultProcessor.DemandOK); + + // attempt to broadcast a reconfigure message to anybody listening to this server + if (GetConfigChangeMessage() is Message configChangeMessage) + { + configChangeMessage.SetSource(ResultProcessor.Int64, null); + server.GetBridge(configChangeMessage)?.TryWriteSync(configChangeMessage, server.IsReplica); + } +#pragma warning restore CS0618 + } + + Task IServer.SlaveOfAsync(EndPoint master, CommandFlags flags) => ReplicaOfAsync(master, flags); + + public async Task ReplicaOfAsync(EndPoint? master, CommandFlags flags = CommandFlags.None) + { + if (master == server.EndPoint) + { + throw new ArgumentException("Cannot replicate to self"); + } + + // Attempt to cease having an opinion on the primary - will resume that when replication completes + // (note that this may fail - we aren't depending on it) + if (GetTiebreakerRemovalMessage() is Message tieBreakerRemoval && !server.IsReplica) + { + try + { + await server.WriteDirectAsync(tieBreakerRemoval, ResultProcessor.Boolean).ForAwait(); + } + catch { } + } + + var msg = CreateReplicaOfMessage(server, master, flags); + await ExecuteAsync(msg, ResultProcessor.DemandOK).ForAwait(); + + // attempt to broadcast a reconfigure message to anybody listening to this server + if (GetConfigChangeMessage() is Message configChangeMessage) + { + await server.WriteDirectAsync(configChangeMessage, ResultProcessor.Int64).ForAwait(); + } + } + + private static void FixFlags(Message? message, ServerEndPoint server) + { + if (message is null) + { + return; + } + + // since the server is specified explicitly, we don't want defaults + // to make the "non-preferred-endpoint" counters look artificially + // inflated; note we only change *prefer* options + switch (Message.GetPrimaryReplicaFlags(message.Flags)) + { + case CommandFlags.PreferMaster: + if (server.IsReplica) message.SetPreferReplica(); + break; + case CommandFlags.PreferReplica: + if (!server.IsReplica) message.SetPreferPrimary(); + break; + } + } + + private static Message GetSaveMessage(SaveType type, CommandFlags flags = CommandFlags.None) => type switch + { + SaveType.BackgroundRewriteAppendOnlyFile => Message.Create(-1, flags, RedisCommand.BGREWRITEAOF), + SaveType.BackgroundSave => Message.Create(-1, flags, RedisCommand.BGSAVE), +#pragma warning disable CS0618 // Type or member is obsolete + SaveType.ForegroundSave => Message.Create(-1, flags, RedisCommand.SAVE), +#pragma warning restore CS0618 + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + + private static ResultProcessor GetSaveResultProcessor(SaveType type) => type switch + { + SaveType.BackgroundRewriteAppendOnlyFile => ResultProcessor.BackgroundSaveAOFStarted, + SaveType.BackgroundSave => ResultProcessor.BackgroundSaveStarted, +#pragma warning disable CS0618 // Type or member is obsolete + SaveType.ForegroundSave => ResultProcessor.DemandOK, +#pragma warning restore CS0618 + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + + private static class ScriptHash + { + public static RedisValue Encode(byte[] value) + { + const string hex = "0123456789abcdef"; + if (value == null) + { + return default; + } + var result = new byte[value.Length * 2]; + int offset = 0; + for (int i = 0; i < value.Length; i++) + { + int val = value[i]; + result[offset++] = (byte)hex[val >> 4]; + result[offset++] = (byte)hex[val & 15]; + } + return result; + } + + public static RedisValue Hash(string value) + { + if (value is null) return default; + using (var sha1 = SHA1.Create()) + { + var bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(value)); + return Encode(bytes); + } + } + } + + private sealed class KeysScanEnumerable : CursorEnumerable + { + private readonly RedisValue pattern; + + public KeysScanEnumerable(RedisServer server, int db, in RedisValue pattern, int pageSize, in RedisValue cursor, int pageOffset, CommandFlags flags) + : base(server, server.server, db, pageSize, cursor, pageOffset, flags) + { + this.pattern = pattern; + } + + private protected override Message CreateMessage(in RedisValue cursor) + { + if (CursorUtils.IsNil(pattern)) + { + if (pageSize == CursorUtils.DefaultRedisPageSize) + { + return Message.Create(db, flags, RedisCommand.SCAN, cursor); + } + else + { + return Message.Create(db, flags, RedisCommand.SCAN, cursor, RedisLiterals.COUNT, pageSize); + } + } + else + { + if (pageSize == CursorUtils.DefaultRedisPageSize) + { + return Message.Create(db, flags, RedisCommand.SCAN, cursor, RedisLiterals.MATCH, pattern); + } + else + { + return Message.Create(db, flags, RedisCommand.SCAN, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize); + } + } + } + + private protected override ResultProcessor Processor => processor; + + public static readonly ResultProcessor processor = new ScanResultProcessor(); + private sealed class ScanResultProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var arr = result.GetItems(); + RawResult inner; + if (arr.Length == 2 && (inner = arr[1]).Resp2TypeArray == ResultType.Array) + { + var items = inner.GetItems(); + RedisKey[] keys; + int count; + if (items.IsEmpty) + { + keys = Array.Empty(); + count = 0; + } + else + { + count = (int)items.Length; + keys = ArrayPool.Shared.Rent(count); + items.CopyTo(keys, (in RawResult r) => r.AsRedisKey()); + } + var keysResult = new ScanResult(arr[0].AsRedisValue(), keys, count, true); + SetResult(message, keysResult); + return true; + } + break; + } + return false; + } + } + } + + public EndPoint? SentinelGetMasterAddressByName(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.GETMASTERADDRBYNAME, (RedisValue)serviceName); + return ExecuteSync(msg, ResultProcessor.SentinelPrimaryEndpoint); + } + + public Task SentinelGetMasterAddressByNameAsync(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.GETMASTERADDRBYNAME, (RedisValue)serviceName); + return ExecuteAsync(msg, ResultProcessor.SentinelPrimaryEndpoint); + } + + public EndPoint[] SentinelGetSentinelAddresses(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SENTINELS, (RedisValue)serviceName); + return ExecuteSync(msg, ResultProcessor.SentinelAddressesEndPoints, defaultValue: Array.Empty()); + } + + public Task SentinelGetSentinelAddressesAsync(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SENTINELS, (RedisValue)serviceName); + return ExecuteAsync(msg, ResultProcessor.SentinelAddressesEndPoints, defaultValue: Array.Empty()); + } + + public EndPoint[] SentinelGetReplicaAddresses(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, Features.ReplicaCommands ? RedisLiterals.REPLICAS : RedisLiterals.SLAVES, (RedisValue)serviceName); + return ExecuteSync(msg, ResultProcessor.SentinelAddressesEndPoints, defaultValue: Array.Empty()); + } + + public Task SentinelGetReplicaAddressesAsync(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, Features.ReplicaCommands ? RedisLiterals.REPLICAS : RedisLiterals.SLAVES, (RedisValue)serviceName); + return ExecuteAsync(msg, ResultProcessor.SentinelAddressesEndPoints, defaultValue: Array.Empty()); + } + + public KeyValuePair[] SentinelMaster(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTER, (RedisValue)serviceName); + return ExecuteSync(msg, ResultProcessor.StringPairInterleaved, defaultValue: Array.Empty>()); + } + + public Task[]> SentinelMasterAsync(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTER, (RedisValue)serviceName); + return ExecuteAsync(msg, ResultProcessor.StringPairInterleaved, defaultValue: Array.Empty>()); + } + + public void SentinelFailover(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.FAILOVER, (RedisValue)serviceName); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task SentinelFailoverAsync(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.FAILOVER, (RedisValue)serviceName); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public KeyValuePair[][] SentinelMasters(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTERS); + return ExecuteSync(msg, ResultProcessor.SentinelArrayOfArrays, defaultValue: Array.Empty[]>()); + } + + public Task[][]> SentinelMastersAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTERS); + return ExecuteAsync(msg, ResultProcessor.SentinelArrayOfArrays, defaultValue: Array.Empty[]>()); + } + + // For previous compat only + KeyValuePair[][] IServer.SentinelSlaves(string serviceName, CommandFlags flags) + => SentinelReplicas(serviceName, flags); + + public KeyValuePair[][] SentinelReplicas(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, Features.ReplicaCommands ? RedisLiterals.REPLICAS : RedisLiterals.SLAVES, (RedisValue)serviceName); + return ExecuteSync(msg, ResultProcessor.SentinelArrayOfArrays, defaultValue: Array.Empty[]>()); + } + + // For previous compat only + Task[][]> IServer.SentinelSlavesAsync(string serviceName, CommandFlags flags) + => SentinelReplicasAsync(serviceName, flags); + + public Task[][]> SentinelReplicasAsync(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, Features.ReplicaCommands ? RedisLiterals.REPLICAS : RedisLiterals.SLAVES, (RedisValue)serviceName); + return ExecuteAsync(msg, ResultProcessor.SentinelArrayOfArrays, defaultValue: Array.Empty[]>()); + } + + public KeyValuePair[][] SentinelSentinels(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SENTINELS, (RedisValue)serviceName); + return ExecuteSync(msg, ResultProcessor.SentinelArrayOfArrays, defaultValue: Array.Empty[]>()); + } + + public Task[][]> SentinelSentinelsAsync(string serviceName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SENTINELS, (RedisValue)serviceName); + return ExecuteAsync(msg, ResultProcessor.SentinelArrayOfArrays, defaultValue: Array.Empty[]>()); + } + + public RedisResult Execute(string command, params object[] args) => Execute(command, args, CommandFlags.None); + + public RedisResult Execute(string command, ICollection args, CommandFlags flags = CommandFlags.None) + { + var msg = new RedisDatabase.ExecuteMessage(multiplexer?.CommandMap, -1, flags, command, args); + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + + public Task ExecuteAsync(string command, params object[] args) => ExecuteAsync(command, args, CommandFlags.None); + + public Task ExecuteAsync(string command, ICollection args, CommandFlags flags = CommandFlags.None) + { + var msg = new RedisDatabase.ExecuteMessage(multiplexer?.CommandMap, -1, flags, command, args); + return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + + public RedisResult Execute(int? database, string command, ICollection args, CommandFlags flags = CommandFlags.None) + { + var db = multiplexer.ApplyDefaultDatabase(database ?? -1); + var msg = new RedisDatabase.ExecuteMessage(multiplexer?.CommandMap, db, flags, command, args); + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + + public Task ExecuteAsync(int? database, string command, ICollection args, CommandFlags flags = CommandFlags.None) + { + var db = multiplexer.ApplyDefaultDatabase(database ?? -1); + var msg = new RedisDatabase.ExecuteMessage(multiplexer?.CommandMap, db, flags, command, args); + return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); + } + + /// + /// For testing only. + /// + internal void SimulateConnectionFailure(SimulatedFailureType failureType) => server.SimulateConnectionFailure(failureType); + + public Task LatencyDoctorAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.DOCTOR); + return ExecuteAsync(msg, ResultProcessor.String!, defaultValue: string.Empty); + } + + public string LatencyDoctor(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.DOCTOR); + return ExecuteSync(msg, ResultProcessor.String, defaultValue: string.Empty); + } + + private static Message LatencyResetCommand(string[]? eventNames, CommandFlags flags) + { + if (eventNames == null) eventNames = Array.Empty(); + switch (eventNames.Length) + { + case 0: + return Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.RESET); + case 1: + return Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.RESET, (RedisValue)eventNames[0]); + default: + var arr = new RedisValue[eventNames.Length + 1]; + arr[0] = RedisLiterals.RESET; + for (int i = 0; i < eventNames.Length; i++) + arr[i + 1] = eventNames[i]; + return Message.Create(-1, flags, RedisCommand.LATENCY, arr); + } + } + public Task LatencyResetAsync(string[]? eventNames = null, CommandFlags flags = CommandFlags.None) + { + var msg = LatencyResetCommand(eventNames, flags); + return ExecuteAsync(msg, ResultProcessor.Int64); + } + + public long LatencyReset(string[]? eventNames = null, CommandFlags flags = CommandFlags.None) + { + var msg = LatencyResetCommand(eventNames, flags); + return ExecuteSync(msg, ResultProcessor.Int64); + } + + public Task LatencyHistoryAsync(string eventName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.HISTORY, (RedisValue)eventName); + return ExecuteAsync(msg, LatencyHistoryEntry.ToArray, defaultValue: Array.Empty()); + } + + public LatencyHistoryEntry[] LatencyHistory(string eventName, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.HISTORY, (RedisValue)eventName); + return ExecuteSync(msg, LatencyHistoryEntry.ToArray, defaultValue: Array.Empty()); + } + + public Task LatencyLatestAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.LATEST); + return ExecuteAsync(msg, LatencyLatestEntry.ToArray, defaultValue: Array.Empty()); + } + + public LatencyLatestEntry[] LatencyLatest(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.LATEST); + return ExecuteSync(msg, LatencyLatestEntry.ToArray, defaultValue: Array.Empty()); + } + + public Task MemoryDoctorAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.DOCTOR); + return ExecuteAsync(msg, ResultProcessor.String!, defaultValue: string.Empty); + } + + public string MemoryDoctor(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.DOCTOR); + return ExecuteSync(msg, ResultProcessor.String, defaultValue: string.Empty); + } + + public Task MemoryPurgeAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.PURGE); + return ExecuteAsync(msg, ResultProcessor.DemandOK); + } + + public void MemoryPurge(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.PURGE); + ExecuteSync(msg, ResultProcessor.DemandOK); + } + + public Task MemoryAllocatorStatsAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.MALLOC_STATS); + return ExecuteAsync(msg, ResultProcessor.String); + } + + public string? MemoryAllocatorStats(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.MALLOC_STATS); + return ExecuteSync(msg, ResultProcessor.String); + } + + public Task MemoryStatsAsync(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.STATS); + return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullArray); + } + + public RedisResult MemoryStats(CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.STATS); + return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullArray); + } + } +} diff --git a/src/StackExchange.Redis/RedisSubscriber.cs b/src/StackExchange.Redis/RedisSubscriber.cs new file mode 100644 index 000000000..9ade78c2d --- /dev/null +++ b/src/StackExchange.Redis/RedisSubscriber.cs @@ -0,0 +1,597 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.SymbolStore; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Pipelines.Sockets.Unofficial; +using Pipelines.Sockets.Unofficial.Arenas; +using static StackExchange.Redis.ConnectionMultiplexer; + +namespace StackExchange.Redis +{ + public partial class ConnectionMultiplexer + { + private RedisSubscriber? _defaultSubscriber; + internal RedisSubscriber DefaultSubscriber => _defaultSubscriber ??= new RedisSubscriber(this, null); + + private readonly ConcurrentDictionary subscriptions = new(); + + internal ConcurrentDictionary GetSubscriptions() => subscriptions; + ConcurrentDictionary IInternalConnectionMultiplexer.GetSubscriptions() => GetSubscriptions(); + + internal int GetSubscriptionsCount() => subscriptions.Count; + int IInternalConnectionMultiplexer.GetSubscriptionsCount() => GetSubscriptionsCount(); + + internal Subscription GetOrAddSubscription(in RedisChannel channel, CommandFlags flags) + { + lock (subscriptions) + { + if (!subscriptions.TryGetValue(channel, out var sub)) + { + sub = new Subscription(flags); + subscriptions.TryAdd(channel, sub); + } + return sub; + } + } + internal bool TryGetSubscription(in RedisChannel channel, [NotNullWhen(true)] out Subscription? sub) => subscriptions.TryGetValue(channel, out sub); + internal bool TryRemoveSubscription(in RedisChannel channel, [NotNullWhen(true)] out Subscription? sub) + { + lock (subscriptions) + { + return subscriptions.TryRemove(channel, out sub); + } + } + + /// + /// Gets the subscriber counts for a channel. + /// + /// if there's a subscription registered at all. + internal bool GetSubscriberCounts(in RedisChannel channel, out int handlers, out int queues) + { + if (subscriptions.TryGetValue(channel, out var sub)) + { + sub.GetSubscriberCounts(out handlers, out queues); + return true; + } + handlers = queues = 0; + return false; + } + + /// + /// Gets which server, if any, there's a registered subscription to for this channel. + /// + /// + /// This may be null if there is a subscription, but we don't have a connected server at the moment. + /// This behavior is fine but IsConnected checks, but is a subtle difference in . + /// + internal ServerEndPoint? GetSubscribedServer(in RedisChannel channel) + { + if (!channel.IsNullOrEmpty && subscriptions.TryGetValue(channel, out Subscription? sub)) + { + return sub.GetCurrentServer(); + } + return null; + } + + /// + /// Handler that executes whenever a message comes in, this doles out messages to any registered handlers. + /// + internal void OnMessage(in RedisChannel subscription, in RedisChannel channel, in RedisValue payload) + { + ICompletable? completable = null; + ChannelMessageQueue? queues = null; + if (subscriptions.TryGetValue(subscription, out Subscription? sub)) + { + completable = sub.ForInvoke(channel, payload, out queues); + } + if (queues != null) + { + ChannelMessageQueue.WriteAll(ref queues, channel, payload); + } + if (completable != null && !completable.TryComplete(false)) + { + CompleteAsWorker(completable); + } + } + + internal void OnMessage(in RedisChannel subscription, in RedisChannel channel, Sequence payload) + { + if (payload.IsSingleSegment) + { + foreach (var message in payload.FirstSpan) + { + OnMessage(subscription, channel, message.AsRedisValue()); + } + } + else + { + foreach (var message in payload) + { + OnMessage(subscription, channel, message.AsRedisValue()); + } + } + } + + /// + /// Updates all subscriptions re-evaluating their state. + /// This clears the current server if it's not connected, prepping them to reconnect. + /// + internal void UpdateSubscriptions() + { + foreach (var pair in subscriptions) + { + pair.Value.UpdateServer(); + } + } + + /// + /// Ensures all subscriptions are connected to a server, if possible. + /// + /// The count of subscriptions attempting to reconnect (same as the count currently not connected). + internal long EnsureSubscriptions(CommandFlags flags = CommandFlags.None) + { + // TODO: Subscribe with variadic commands to reduce round trips + long count = 0; + foreach (var pair in subscriptions) + { + if (!pair.Value.IsConnected) + { + count++; + DefaultSubscriber.EnsureSubscribedToServer(pair.Value, pair.Key, flags, true); + } + } + return count; + } + + internal enum SubscriptionAction + { + Subscribe, + Unsubscribe, + } + + /// + /// This is the record of a single subscription to a redis server. + /// It's the singular channel (which may or may not be a pattern), to one or more handlers. + /// We subscriber to a redis server once (for all messages) and execute 1-many handlers when a message arrives. + /// + internal sealed class Subscription + { + private Action? _handlers; + private readonly object _handlersLock = new object(); + private ChannelMessageQueue? _queues; + private ServerEndPoint? CurrentServer; + public CommandFlags Flags { get; } + public ResultProcessor.TrackSubscriptionsProcessor Processor { get; } + + /// + /// Whether the we have is connected. + /// Since we clear on a disconnect, this should stay correct. + /// + internal bool IsConnected => CurrentServer?.IsSubscriberConnected == true; + + public Subscription(CommandFlags flags) + { + Flags = flags; + Processor = new ResultProcessor.TrackSubscriptionsProcessor(this); + } + + /// + /// Gets the configured (P)SUBSCRIBE or (P)UNSUBSCRIBE for an action. + /// + internal Message GetMessage(RedisChannel channel, SubscriptionAction action, CommandFlags flags, bool internalCall) + { + var command = action switch // note that the Routed flag doesn't impact the message here - just the routing + { + SubscriptionAction.Subscribe => (channel.Options & ~RedisChannel.RedisChannelOptions.KeyRouted) switch + { + RedisChannel.RedisChannelOptions.None => RedisCommand.SUBSCRIBE, + RedisChannel.RedisChannelOptions.Pattern => RedisCommand.PSUBSCRIBE, + RedisChannel.RedisChannelOptions.Sharded => RedisCommand.SSUBSCRIBE, + _ => Unknown(action, channel.Options), + }, + SubscriptionAction.Unsubscribe => (channel.Options & ~RedisChannel.RedisChannelOptions.KeyRouted) switch + { + RedisChannel.RedisChannelOptions.None => RedisCommand.UNSUBSCRIBE, + RedisChannel.RedisChannelOptions.Pattern => RedisCommand.PUNSUBSCRIBE, + RedisChannel.RedisChannelOptions.Sharded => RedisCommand.SUNSUBSCRIBE, + _ => Unknown(action, channel.Options), + }, + _ => Unknown(action, channel.Options), + }; + + // TODO: Consider flags here - we need to pass Fire and Forget, but don't want to intermingle Primary/Replica + var msg = Message.Create(-1, Flags | flags, command, channel); + msg.SetForSubscriptionBridge(); + if (internalCall) + { + msg.SetInternalCall(); + } + return msg; + } + + private RedisCommand Unknown(SubscriptionAction action, RedisChannel.RedisChannelOptions options) + => throw new ArgumentException($"Unable to determine pub/sub operation for '{action}' against '{options}'"); + + public void Add(Action? handler, ChannelMessageQueue? queue) + { + if (handler != null) + { + lock (_handlersLock) + { + _handlers += handler; + } + } + if (queue != null) + { + ChannelMessageQueue.Combine(ref _queues, queue); + } + } + + public bool Remove(Action? handler, ChannelMessageQueue? queue) + { + if (handler != null) + { + lock (_handlersLock) + { + _handlers -= handler; + } + } + if (queue != null) + { + ChannelMessageQueue.Remove(ref _queues, queue); + } + return _handlers == null & _queues == null; + } + + public ICompletable? ForInvoke(in RedisChannel channel, in RedisValue message, out ChannelMessageQueue? queues) + { + var handlers = _handlers; + queues = Volatile.Read(ref _queues); + return handlers == null ? null : new MessageCompletable(channel, message, handlers); + } + + internal void MarkCompleted() + { + lock (_handlersLock) + { + _handlers = null; + } + ChannelMessageQueue.MarkAllCompleted(ref _queues); + } + + internal void GetSubscriberCounts(out int handlers, out int queues) + { + queues = ChannelMessageQueue.Count(ref _queues); + var tmp = _handlers; + if (tmp == null) + { + handlers = 0; + } + else if (tmp.IsSingle()) + { + handlers = 1; + } + else + { + handlers = 0; + foreach (var sub in tmp.AsEnumerable()) { handlers++; } + } + } + + internal ServerEndPoint? GetCurrentServer() => Volatile.Read(ref CurrentServer); + internal void SetCurrentServer(ServerEndPoint? server) => CurrentServer = server; + // conditional clear + internal bool ClearCurrentServer(ServerEndPoint expected) + { + if (CurrentServer == expected) + { + CurrentServer = null; + return true; + } + + return false; + } + + /// + /// Evaluates state and if we're not currently connected, clears the server reference. + /// + internal void UpdateServer() + { + if (!IsConnected) + { + CurrentServer = null; + } + } + } + } + + /// + /// A wrapper for subscription actions. + /// + /// + /// By having most functionality here and state on , we can + /// use the baseline execution methods to take the normal message paths. + /// + internal sealed class RedisSubscriber : RedisBase, ISubscriber + { + internal RedisSubscriber(ConnectionMultiplexer multiplexer, object? asyncState) : base(multiplexer, asyncState) + { + } + + public EndPoint? IdentifyEndpoint(RedisChannel channel, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); + msg.SetInternalCall(); + return ExecuteSync(msg, ResultProcessor.ConnectionIdentity); + } + + public Task IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None) + { + var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); + msg.SetInternalCall(); + return ExecuteAsync(msg, ResultProcessor.ConnectionIdentity); + } + + /// + /// This is *could* we be connected, as in "what's the theoretical endpoint for this channel?", + /// rather than if we're actually connected and actually listening on that channel. + /// + public bool IsConnected(RedisChannel channel = default) + { + var server = multiplexer.GetSubscribedServer(channel) ?? multiplexer.SelectServer(RedisCommand.SUBSCRIBE, CommandFlags.DemandMaster, channel); + return server?.IsConnected == true && server.IsSubscriberConnected; + } + + public override TimeSpan Ping(CommandFlags flags = CommandFlags.None) + { + var msg = CreatePingMessage(flags); + return ExecuteSync(msg, ResultProcessor.ResponseTimer); + } + + public override Task PingAsync(CommandFlags flags = CommandFlags.None) + { + var msg = CreatePingMessage(flags); + return ExecuteAsync(msg, ResultProcessor.ResponseTimer); + } + + private Message CreatePingMessage(CommandFlags flags) + { + bool usePing = false; + if (multiplexer.CommandMap.IsAvailable(RedisCommand.PING)) + { + try { usePing = GetFeatures(default, flags, RedisCommand.PING, out _).PingOnSubscriber; } + catch { } + } + + Message msg; + if (usePing) + { + msg = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.PING); + } + else + { + // can't use regular PING, but we can unsubscribe from something random that we weren't even subscribed to... + RedisValue channel = multiplexer.UniqueId; + msg = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.UNSUBSCRIBE, channel); + } + // Ensure the ping is sent over the intended subscriber connection, which wouldn't happen in GetBridge() by default with PING; + msg.SetForSubscriptionBridge(); + return msg; + } + + private static void ThrowIfNull(in RedisChannel channel) + { + if (channel.IsNullOrEmpty) + { + throw new ArgumentNullException(nameof(channel)); + } + } + + public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) + { + ThrowIfNull(channel); + var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message); + // if we're actively subscribed: send via that connection (otherwise, follow normal rules) + return ExecuteSync(msg, ResultProcessor.Int64, server: multiplexer.GetSubscribedServer(channel)); + } + + public Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) + { + ThrowIfNull(channel); + var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message); + // if we're actively subscribed: send via that connection (otherwise, follow normal rules) + return ExecuteAsync(msg, ResultProcessor.Int64, server: multiplexer.GetSubscribedServer(channel)); + } + + void ISubscriber.Subscribe(RedisChannel channel, Action handler, CommandFlags flags) + => Subscribe(channel, handler, null, flags); + + public ChannelMessageQueue Subscribe(RedisChannel channel, CommandFlags flags = CommandFlags.None) + { + var queue = new ChannelMessageQueue(channel, this); + Subscribe(channel, null, queue, flags); + return queue; + } + + private bool Subscribe(RedisChannel channel, Action? handler, ChannelMessageQueue? queue, CommandFlags flags) + { + ThrowIfNull(channel); + if (handler == null && queue == null) { return true; } + + var sub = multiplexer.GetOrAddSubscription(channel, flags); + sub.Add(handler, queue); + return EnsureSubscribedToServer(sub, channel, flags, false); + } + + internal bool EnsureSubscribedToServer(Subscription sub, RedisChannel channel, CommandFlags flags, bool internalCall) + { + if (sub.IsConnected) { return true; } + + // TODO: Cleanup old hangers here? + sub.SetCurrentServer(null); // we're not appropriately connected, so blank it out for eligible reconnection + var message = sub.GetMessage(channel, SubscriptionAction.Subscribe, flags, internalCall); + var selected = multiplexer.SelectServer(message); + return ExecuteSync(message, sub.Processor, selected); + } + + internal void ResubscribeToServer(Subscription sub, RedisChannel channel, ServerEndPoint serverEndPoint, string cause) + { + // conditional: only if that's the server we were connected to, or "none"; we don't want to end up duplicated + if (sub.ClearCurrentServer(serverEndPoint) || !sub.IsConnected) + { + if (serverEndPoint.IsSubscriberConnected) + { + // we'll *try* for a simple resubscribe, following any -MOVED etc, but if that fails: fall back + // to full reconfigure; importantly, note that we've already recorded the disconnect + var message = sub.GetMessage(channel, SubscriptionAction.Subscribe, CommandFlags.None, false); + _ = ExecuteAsync(message, sub.Processor, serverEndPoint).ContinueWith( + t => multiplexer.ReconfigureIfNeeded(serverEndPoint.EndPoint, false, cause: cause), + TaskContinuationOptions.OnlyOnFaulted); + } + else + { + multiplexer.ReconfigureIfNeeded(serverEndPoint.EndPoint, false, cause: cause); + } + } + } + + Task ISubscriber.SubscribeAsync(RedisChannel channel, Action handler, CommandFlags flags) + => SubscribeAsync(channel, handler, null, flags); + + Task ISubscriber.SubscribeAsync(RedisChannel channel, CommandFlags flags) => SubscribeAsync(channel, flags); + + public async Task SubscribeAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None, ServerEndPoint? server = null) + { + var queue = new ChannelMessageQueue(channel, this); + await SubscribeAsync(channel, null, queue, flags, server).ForAwait(); + return queue; + } + + private Task SubscribeAsync(RedisChannel channel, Action? handler, ChannelMessageQueue? queue, CommandFlags flags, ServerEndPoint? server = null) + { + ThrowIfNull(channel); + if (handler == null && queue == null) { return CompletedTask.Default(null); } + + var sub = multiplexer.GetOrAddSubscription(channel, flags); + sub.Add(handler, queue); + return EnsureSubscribedToServerAsync(sub, channel, flags, false, server); + } + + public Task EnsureSubscribedToServerAsync(Subscription sub, RedisChannel channel, CommandFlags flags, bool internalCall, ServerEndPoint? server = null) + { + if (sub.IsConnected) { return CompletedTask.Default(null); } + + // TODO: Cleanup old hangers here? + sub.SetCurrentServer(null); // we're not appropriately connected, so blank it out for eligible reconnection + var message = sub.GetMessage(channel, SubscriptionAction.Subscribe, flags, internalCall); + server ??= multiplexer.SelectServer(message); + return ExecuteAsync(message, sub.Processor, server); + } + + public EndPoint? SubscribedEndpoint(RedisChannel channel) => multiplexer.GetSubscribedServer(channel)?.EndPoint; + + void ISubscriber.Unsubscribe(RedisChannel channel, Action? handler, CommandFlags flags) + => Unsubscribe(channel, handler, null, flags); + + public bool Unsubscribe(in RedisChannel channel, Action? handler, ChannelMessageQueue? queue, CommandFlags flags) + { + ThrowIfNull(channel); + // Unregister the subscription handler/queue, and if that returns true (last handler removed), also disconnect from the server + return UnregisterSubscription(channel, handler, queue, out var sub) + ? UnsubscribeFromServer(sub, channel, flags, false) + : true; + } + + private bool UnsubscribeFromServer(Subscription sub, RedisChannel channel, CommandFlags flags, bool internalCall) + { + if (sub.GetCurrentServer() is ServerEndPoint oldOwner) + { + var message = sub.GetMessage(channel, SubscriptionAction.Unsubscribe, flags, internalCall); + return multiplexer.ExecuteSyncImpl(message, sub.Processor, oldOwner); + } + return false; + } + + Task ISubscriber.UnsubscribeAsync(RedisChannel channel, Action? handler, CommandFlags flags) + => UnsubscribeAsync(channel, handler, null, flags); + + public Task UnsubscribeAsync(in RedisChannel channel, Action? handler, ChannelMessageQueue? queue, CommandFlags flags) + { + ThrowIfNull(channel); + // Unregister the subscription handler/queue, and if that returns true (last handler removed), also disconnect from the server + return UnregisterSubscription(channel, handler, queue, out var sub) + ? UnsubscribeFromServerAsync(sub, channel, flags, asyncState, false) + : CompletedTask.Default(asyncState); + } + + private Task UnsubscribeFromServerAsync(Subscription sub, RedisChannel channel, CommandFlags flags, object? asyncState, bool internalCall) + { + if (sub.GetCurrentServer() is ServerEndPoint oldOwner) + { + var message = sub.GetMessage(channel, SubscriptionAction.Unsubscribe, flags, internalCall); + return multiplexer.ExecuteAsyncImpl(message, sub.Processor, asyncState, oldOwner); + } + return CompletedTask.FromResult(true, asyncState); + } + + /// + /// Unregisters a handler or queue and returns if we should remove it from the server. + /// + /// if we should remove the subscription from the server, otherwise. + private bool UnregisterSubscription(in RedisChannel channel, Action? handler, ChannelMessageQueue? queue, [NotNullWhen(true)] out Subscription? sub) + { + ThrowIfNull(channel); + if (multiplexer.TryGetSubscription(channel, out sub)) + { + if (handler == null & queue == null) + { + // This was a blanket wipe, so clear it completely + sub.MarkCompleted(); + multiplexer.TryRemoveSubscription(channel, out _); + return true; + } + else if (sub.Remove(handler, queue)) + { + // Or this was the last handler and/or queue, which also means unsubscribe + multiplexer.TryRemoveSubscription(channel, out _); + return true; + } + } + return false; + } + + // TODO: We need a new api to support SUNSUBSCRIBE all. Calling this now would unsubscribe both sharded and unsharded channels. + public void UnsubscribeAll(CommandFlags flags = CommandFlags.None) + { + // TODO: Unsubscribe variadic commands to reduce round trips + var subs = multiplexer.GetSubscriptions(); + foreach (var pair in subs) + { + if (subs.TryRemove(pair.Key, out var sub)) + { + sub.MarkCompleted(); + UnsubscribeFromServer(sub, pair.Key, flags, false); + } + } + } + + public Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None) + { + // TODO: Unsubscribe variadic commands to reduce round trips + Task? last = null; + var subs = multiplexer.GetSubscriptions(); + foreach (var pair in subs) + { + if (subs.TryRemove(pair.Key, out var sub)) + { + sub.MarkCompleted(); + last = UnsubscribeFromServerAsync(sub, pair.Key, flags, asyncState, false); + } + } + return last ?? CompletedTask.Default(asyncState); + } + } +} diff --git a/src/StackExchange.Redis/RedisTransaction.cs b/src/StackExchange.Redis/RedisTransaction.cs new file mode 100644 index 000000000..f0a9600fa --- /dev/null +++ b/src/StackExchange.Redis/RedisTransaction.cs @@ -0,0 +1,571 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + internal sealed class RedisTransaction : RedisDatabase, ITransaction + { + private List? _conditions; + private List? _pending; + private object SyncLock => this; + + public RedisTransaction(RedisDatabase wrapped, object? asyncState) : base(wrapped.multiplexer, wrapped.Database, asyncState ?? wrapped.AsyncState) + { + // need to check we can reliably do this... + var commandMap = multiplexer.CommandMap; + commandMap.AssertAvailable(RedisCommand.MULTI); + commandMap.AssertAvailable(RedisCommand.EXEC); + commandMap.AssertAvailable(RedisCommand.DISCARD); + } + + public ConditionResult AddCondition(Condition condition) + { + if (condition == null) throw new ArgumentNullException(nameof(condition)); + + var commandMap = multiplexer.CommandMap; + lock (SyncLock) + { + if (_conditions == null) + { + // we don't demand these unless the user is requesting conditions, but we need both... + commandMap.AssertAvailable(RedisCommand.WATCH); + commandMap.AssertAvailable(RedisCommand.UNWATCH); + _conditions = new List(); + } + condition.CheckCommands(commandMap); + var result = new ConditionResult(condition); + _conditions.Add(result); + return result; + } + } + + public void Execute() => Execute(CommandFlags.FireAndForget); + + public bool Execute(CommandFlags flags) + { + var msg = CreateMessage(flags, out ResultProcessor? proc); + return base.ExecuteSync(msg, proc); // need base to avoid our local "not supported" override + } + + public Task ExecuteAsync(CommandFlags flags) + { + var msg = CreateMessage(flags, out ResultProcessor? proc); + return base.ExecuteAsync(msg, proc); // need base to avoid our local wrapping override + } + + internal override Task ExecuteAsync(Message? message, ResultProcessor? processor, T defaultValue, ServerEndPoint? server = null) + { + if (message == null) return CompletedTask.FromDefault(defaultValue, asyncState); + multiplexer.CheckMessage(message); + + multiplexer.Trace("Wrapping " + message.Command, "Transaction"); + // prepare the inner command as a task + Task task; + if (message.IsFireAndForget) + { + task = CompletedTask.FromDefault(defaultValue, null); // F+F explicitly does not get async-state + } + else + { + var source = TaskResultBox.Create(out var tcs, asyncState); + message.SetSource(source, processor); + task = tcs.Task; + } + + QueueMessage(message); + + return task; + } + + internal override Task ExecuteAsync(Message? message, ResultProcessor? processor, ServerEndPoint? server = null) where T : default + { + if (message == null) return CompletedTask.Default(asyncState); + multiplexer.CheckMessage(message); + + multiplexer.Trace("Wrapping " + message.Command, "Transaction"); + // prepare the inner command as a task + Task task; + if (message.IsFireAndForget) + { + task = CompletedTask.Default(null); // F+F explicitly does not get async-state + } + else + { + var source = TaskResultBox.Create(out var tcs, asyncState); + message.SetSource(source!, processor); + task = tcs.Task; + } + + QueueMessage(message); + + return task; + } + + private void QueueMessage(Message message) + { + // prepare an outer-command that decorates that, but expects QUEUED + var queued = new QueuedMessage(message); + var wasQueued = SimpleResultBox.Create(); + queued.SetSource(wasQueued, QueuedProcessor.Default); + + // store it, and return the task of the *outer* command + // (there is no task for the inner command) + lock (SyncLock) + { + (_pending ??= new List()).Add(queued); + switch (message.Command) + { + case RedisCommand.UNKNOWN: + case RedisCommand.EVAL: + case RedisCommand.EVALSHA: + var server = multiplexer.SelectServer(message); + if (server != null && server.SupportsDatabases) + { + // people can do very naughty things in an EVAL + // including change the DB; change it back to what we + // think it should be! + var sel = PhysicalConnection.GetSelectDatabaseCommand(message.Db); + queued = new QueuedMessage(sel); + wasQueued = SimpleResultBox.Create(); + queued.SetSource(wasQueued, QueuedProcessor.Default); + _pending.Add(queued); + } + + break; + } + } + } + + internal override T? ExecuteSync(Message? message, ResultProcessor? processor, ServerEndPoint? server = null, T? defaultValue = default) where T : default + { + throw new NotSupportedException("ExecuteSync cannot be used inside a transaction"); + } + + private Message? CreateMessage(CommandFlags flags, out ResultProcessor? processor) + { + List? cond; + List? work; + lock (SyncLock) + { + work = _pending; + _pending = null; // any new operations go into a different queue + cond = _conditions; + _conditions = null; // any new conditions go into a different queue + } + if ((work == null || work.Count == 0) && (cond == null || cond.Count == 0)) + { + if ((flags & CommandFlags.FireAndForget) != 0) + { + processor = null; + return null; // they won't notice if we don't do anything... + } + processor = ResultProcessor.DemandPONG; + return Message.Create(-1, flags, RedisCommand.PING); + } + processor = TransactionProcessor.Default; + return new TransactionMessage(Database, flags, cond, work); + } + + private sealed class QueuedMessage : Message + { + public Message Wrapped { get; } + private volatile bool wasQueued; + + public QueuedMessage(Message message) : base(message.Db, message.Flags | CommandFlags.NoRedirect, message.Command) + { + message.SetNoRedirect(); + Wrapped = message; + } + + public bool WasQueued + { + get => wasQueued; + set => wasQueued = value; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + Wrapped.WriteTo(physical); + Wrapped.SetRequestSent(); + } + public override int ArgCount => Wrapped.ArgCount; + public override string CommandAndKey => Wrapped.CommandAndKey; + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + => Wrapped.GetHashSlot(serverSelectionStrategy); + } + + private sealed class QueuedProcessor : ResultProcessor + { + public static readonly ResultProcessor Default = new QueuedProcessor(); + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeBulkString == ResultType.SimpleString && result.IsEqual(CommonReplies.QUEUED)) + { + if (message is QueuedMessage q) + { + connection?.BridgeCouldBeNull?.Multiplexer?.OnTransactionLog("Observed QUEUED for " + q.Wrapped?.CommandAndKey); + q.WasQueued = true; + } + return true; + } + return false; + } + } + + private sealed class TransactionMessage : Message, IMultiMessage + { + private readonly ConditionResult[] conditions; + + public QueuedMessage[] InnerOperations { get; } + + public TransactionMessage(int db, CommandFlags flags, List? conditions, List? operations) + : base(db, flags, RedisCommand.EXEC) + { + InnerOperations = (operations?.Count > 0) ? operations.ToArray() : Array.Empty(); + this.conditions = (conditions?.Count > 0) ? conditions.ToArray() : Array.Empty(); + } + + internal override void SetExceptionAndComplete(Exception exception, PhysicalBridge? bridge) + { + var inner = InnerOperations; + if (inner != null) + { + for (int i = 0; i < inner.Length; i++) + { + inner[i]?.Wrapped?.SetExceptionAndComplete(exception, bridge); + } + } + base.SetExceptionAndComplete(exception, bridge); + } + + public bool IsAborted => command != RedisCommand.EXEC; + + public override void AppendStormLog(StringBuilder sb) + { + base.AppendStormLog(sb); + if (conditions.Length != 0) + { + sb.Append(", ").Append(conditions.Length).Append(" conditions"); + } + sb.Append(", ").Append(InnerOperations.Length).Append(" operations"); + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + { + int slot = ServerSelectionStrategy.NoSlot; + for (int i = 0; i < conditions.Length; i++) + { + int newSlot = conditions[i].Condition.GetHashSlot(serverSelectionStrategy); + slot = ServerSelectionStrategy.CombineSlot(slot, newSlot); + if (slot == ServerSelectionStrategy.MultipleSlots) return slot; + } + for (int i = 0; i < InnerOperations.Length; i++) + { + int newSlot = InnerOperations[i].Wrapped.GetHashSlot(serverSelectionStrategy); + slot = ServerSelectionStrategy.CombineSlot(slot, newSlot); + if (slot == ServerSelectionStrategy.MultipleSlots) return slot; + } + return slot; + } + + public IEnumerable GetMessages(PhysicalConnection connection) + { + IResultBox? lastBox = null; + var bridge = connection.BridgeCouldBeNull ?? throw new ObjectDisposedException(connection.ToString()); + + bool explicitCheckForQueued = !bridge.ServerEndPoint.GetFeatures().ExecAbort; + var multiplexer = bridge.Multiplexer; + var sb = new StringBuilder(); + try + { + try + { + // Important: if the server supports EXECABORT, then we can check the preconditions (pause there), + // which will usually be pretty small and cheap to do - if that passes, we can just issue all the commands + // and rely on EXECABORT to kick us if we are being idiotic inside the MULTI. However, if the server does + // *not* support EXECABORT, then we need to explicitly check for QUEUED anyway; we might as well defer + // checking the preconditions to the same time to avoid having to pause twice. This will mean that on + // up-version servers, precondition failures exit with UNWATCH; and on down-version servers precondition + // failures exit with DISCARD - but that's okay : both work fine + + // PART 1: issue the preconditions + if (!IsAborted && conditions.Length != 0) + { + sb.AppendLine("issuing conditions..."); + int cmdCount = 0; + for (int i = 0; i < conditions.Length; i++) + { + // need to have locked them before sending them + // to guarantee that we see the pulse + IResultBox latestBox = conditions[i].GetBox()!; + Monitor.Enter(latestBox); + if (lastBox != null) Monitor.Exit(lastBox); + lastBox = latestBox; + foreach (var msg in conditions[i].CreateMessages(Db)) + { + msg.SetNoRedirect(); // need to keep them in the current context only + yield return msg; + sb.Append("issuing ").AppendLine(msg.CommandAndKey); + cmdCount++; + } + } + sb.Append("issued ").Append(conditions.Length).Append(" conditions (").Append(cmdCount).AppendLine(" commands)"); + + if (!explicitCheckForQueued && lastBox != null) + { + sb.AppendLine("checking conditions in the *early* path"); + // need to get those sent ASAP; if they are stuck in the buffers, we die + multiplexer.Trace("Flushing and waiting for precondition responses"); +#pragma warning disable CS0618 // Type or member is obsolete + connection.FlushSync(true, multiplexer.TimeoutMilliseconds); // make sure they get sent, so we can check for QUEUED (and the preconditions if necessary) +#pragma warning restore CS0618 + + if (Monitor.Wait(lastBox, multiplexer.TimeoutMilliseconds)) + { + if (!AreAllConditionsSatisfied(multiplexer)) + command = RedisCommand.UNWATCH; // somebody isn't happy + + sb.Append("after condition check, we are ").Append(command).AppendLine(); + } + else + { // timeout running preconditions + multiplexer.Trace("Timeout checking preconditions"); + command = RedisCommand.UNWATCH; + + sb.Append("timeout waiting for conditions, we are ").Append(command).AppendLine(); + } + Monitor.Exit(lastBox); + lastBox = null; + } + } + + // PART 2: begin the transaction + if (!IsAborted) + { + multiplexer.Trace("Beginning transaction"); + yield return Message.Create(-1, CommandFlags.None, RedisCommand.MULTI); + sb.AppendLine("issued MULTI"); + } + + // PART 3: issue the commands + if (!IsAborted && InnerOperations.Length != 0) + { + multiplexer.Trace("Issuing operations..."); + + foreach (var op in InnerOperations) + { + if (explicitCheckForQueued) + { + // need to have locked them before sending them + // to guarantee that we see the pulse + IResultBox? thisBox = op.ResultBox; + if (thisBox != null) + { + Monitor.Enter(thisBox); + if (lastBox != null) Monitor.Exit(lastBox); + lastBox = thisBox; + } + } + yield return op; + sb.Append("issued ").AppendLine(op.CommandAndKey); + } + sb.Append("issued ").Append(InnerOperations.Length).AppendLine(" operations"); + + if (explicitCheckForQueued && lastBox != null) + { + sb.AppendLine("checking conditions in the *late* path"); + + multiplexer.Trace("Flushing and waiting for precondition+queued responses"); +#pragma warning disable CS0618 // Type or member is obsolete + connection.FlushSync(true, multiplexer.TimeoutMilliseconds); // make sure they get sent, so we can check for QUEUED (and the preconditions if necessary) +#pragma warning restore CS0618 + if (Monitor.Wait(lastBox, multiplexer.TimeoutMilliseconds)) + { + if (!AreAllConditionsSatisfied(multiplexer)) + { + command = RedisCommand.DISCARD; + } + else + { + foreach (var op in InnerOperations) + { + if (!op.WasQueued) + { + multiplexer.Trace("Aborting: operation was not queued: " + op.Command); + sb.Append("command was not issued: ").AppendLine(op.CommandAndKey); + command = RedisCommand.DISCARD; + break; + } + } + } + multiplexer.Trace("Confirmed: QUEUED x " + InnerOperations.Length); + sb.Append("after condition check, we are ").Append(command).AppendLine(); + } + else + { + multiplexer.Trace("Aborting: timeout checking queued messages"); + command = RedisCommand.DISCARD; + sb.Append("timeout waiting for conditions, we are ").Append(command).AppendLine(); + } + Monitor.Exit(lastBox); + lastBox = null; + } + } + } + finally + { + if (lastBox != null) Monitor.Exit(lastBox); + } + if (IsAborted) + { + sb.Append("aborting ").Append(InnerOperations.Length).AppendLine(" wrapped commands..."); + connection.Trace("Aborting: canceling wrapped messages"); + foreach (var op in InnerOperations) + { + var inner = op.Wrapped; + inner.Cancel(); + inner.Complete(); + } + } + connection.Trace("End of transaction: " + Command); + sb.Append("issuing ").Append(Command).AppendLine(); + yield return this; // acts as either an EXEC or an UNWATCH, depending on "aborted" + } + finally + { + multiplexer.OnTransactionLog(sb.ToString()); + } + } + + protected override void WriteImpl(PhysicalConnection physical) => physical.WriteHeader(Command, 0); + + public override int ArgCount => 0; + + private bool AreAllConditionsSatisfied(ConnectionMultiplexer multiplexer) + { + bool result = true; + for (int i = 0; i < conditions.Length; i++) + { + var condition = conditions[i]; + if (condition.UnwrapBox()) + { + multiplexer.Trace("Precondition passed: " + condition.Condition); + } + else + { + multiplexer.Trace("Precondition failed: " + condition.Condition); + result = false; + } + } + return result; + } + } + + private sealed class TransactionProcessor : ResultProcessor + { + public static readonly TransactionProcessor Default = new(); + + public override bool SetResult(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.IsError && message is TransactionMessage tran) + { + string error = result.GetString()!; + foreach (var op in tran.InnerOperations) + { + var inner = op.Wrapped; + ServerFail(inner, error); + inner.Complete(); + } + } + return base.SetResult(connection, message, result); + } + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + var muxer = connection.BridgeCouldBeNull?.Multiplexer; + muxer?.OnTransactionLog($"got {result} for {message.CommandAndKey}"); + if (message is TransactionMessage tran) + { + var wrapped = tran.InnerOperations; + switch (result.Resp2TypeArray) + { + case ResultType.SimpleString: + if (tran.IsAborted && result.IsEqual(CommonReplies.OK)) + { + connection.Trace("Acknowledging UNWATCH (aborted electively)"); + SetResult(message, false); + return true; + } + // EXEC returned with a NULL + if (!tran.IsAborted && result.IsNull) + { + connection.Trace("Server aborted due to failed EXEC"); + // cancel the commands in the transaction and mark them as complete with the completion manager + foreach (var op in wrapped) + { + var inner = op.Wrapped; + inner.Cancel(); + inner.Complete(); + } + SetResult(message, false); + return true; + } + break; + case ResultType.Array: + if (!tran.IsAborted) + { + var arr = result.GetItems(); + if (result.IsNull) + { + muxer?.OnTransactionLog("Aborting wrapped messages (failed watch)"); + connection.Trace("Server aborted due to failed WATCH"); + foreach (var op in wrapped) + { + var inner = op.Wrapped; + inner.Cancel(); + inner.Complete(); + } + SetResult(message, false); + return true; + } + else if (wrapped.Length == arr.Length) + { + connection.Trace("Server committed; processing nested replies"); + muxer?.OnTransactionLog($"Processing {arr.Length} wrapped messages"); + + int i = 0; + foreach (ref RawResult item in arr) + { + var inner = wrapped[i++].Wrapped; + muxer?.OnTransactionLog($"> got {item} for {inner.CommandAndKey}"); + if (inner.ComputeResult(connection, in item)) + { + inner.Complete(); + } + } + SetResult(message, true); + return true; + } + } + break; + } + // even if we didn't fully understand the result, we still need to do something with + // the pending tasks + foreach (var op in wrapped) + { + if (op?.Wrapped is Message inner) + { + inner.Fail(ConnectionFailureType.ProtocolFailure, null, "Transaction failure", muxer); + inner.Complete(); + } + } + } + return false; + } + } + } +} diff --git a/src/StackExchange.Redis/RedisValue.cs b/src/StackExchange.Redis/RedisValue.cs new file mode 100644 index 000000000..da33c803e --- /dev/null +++ b/src/StackExchange.Redis/RedisValue.cs @@ -0,0 +1,1227 @@ +using System; +using System.Buffers; +using System.Buffers.Text; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace StackExchange.Redis +{ + /// + /// Represents values that can be stored in redis. + /// + public readonly struct RedisValue : IEquatable, IComparable, IComparable, IConvertible + { + internal static readonly RedisValue[] EmptyArray = Array.Empty(); + + private readonly object? _objectOrSentinel; + private readonly ReadOnlyMemory _memory; + private readonly long _overlappedBits64; + + private RedisValue(long overlappedValue64, ReadOnlyMemory memory, object? objectOrSentinel) + { + _overlappedBits64 = overlappedValue64; + _memory = memory; + _objectOrSentinel = objectOrSentinel; + } + + internal RedisValue(object obj, long overlappedBits) + { + // this creates a bodged RedisValue which should **never** + // be seen directly; the contents are ... unexpected + _overlappedBits64 = overlappedBits; + _objectOrSentinel = obj; + _memory = default; + } + + /// + /// Creates a from a string. + /// + public RedisValue(string value) : this(0, default, value) { } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1085:Use auto-implemented property.", Justification = "Intentional field ref")] + internal object? DirectObject => _objectOrSentinel; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1085:Use auto-implemented property.", Justification = "Intentional field ref")] + internal long DirectOverlappedBits64 => _overlappedBits64; + + private static readonly object Sentinel_SignedInteger = new(); + private static readonly object Sentinel_UnsignedInteger = new(); + private static readonly object Sentinel_Raw = new(); + private static readonly object Sentinel_Double = new(); + + /// + /// Obtain this value as an object - to be used alongside Unbox. + /// + public object? Box() + { + var obj = _objectOrSentinel; + if (obj is null || obj is string || obj is byte[]) return obj; + if (obj == Sentinel_SignedInteger) + { + var l = OverlappedValueInt64; + if (l >= -1 && l <= 20) return s_CommonInt32[((int)l) + 1]; + return l; + } + if (obj == Sentinel_UnsignedInteger) + { + return OverlappedValueUInt64; + } + if (obj == Sentinel_Double) + { + var d = OverlappedValueDouble; + if (double.IsPositiveInfinity(d)) return s_DoublePosInf; + if (double.IsNegativeInfinity(d)) return s_DoubleNegInf; + if (double.IsNaN(d)) return s_DoubleNAN; + return d; + } + if (obj == Sentinel_Raw && _memory.IsEmpty) return s_EmptyString; + return this; + } + + /// + /// Parse this object as a value - to be used alongside Box. + /// + /// The value to unbox. + public static RedisValue Unbox(object? value) + { + var val = TryParse(value, out var valid); + if (!valid) throw new ArgumentException("Could not parse value", nameof(value)); + return val; + } + + /// + /// Represents the string "". + /// + public static RedisValue EmptyString { get; } = new RedisValue(0, default, Sentinel_Raw); + + // note: it is *really important* that this s_EmptyString assignment happens *after* the EmptyString initializer above! + private static readonly object s_DoubleNAN = double.NaN, s_DoublePosInf = double.PositiveInfinity, s_DoubleNegInf = double.NegativeInfinity, + s_EmptyString = RedisValue.EmptyString; + private static readonly object[] s_CommonInt32 = Enumerable.Range(-1, 22).Select(i => (object)i).ToArray(); // [-1,20] = 22 values + + /// + /// A null value. + /// + public static RedisValue Null { get; } = new RedisValue(0, default, null); + + /// + /// Indicates whether the **underlying** value is a primitive integer (signed or unsigned); this is **not** + /// the same as whether the value can be *treated* as an integer - see + /// and , which is usually the more appropriate test. + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced)] // hide it, because this *probably* isn't what callers need + public bool IsInteger => _objectOrSentinel == Sentinel_SignedInteger || _objectOrSentinel == Sentinel_UnsignedInteger; + + /// + /// Indicates whether the value should be considered a null value. + /// + public bool IsNull => _objectOrSentinel == null; + + /// + /// Indicates whether the value is either null or a zero-length value. + /// + public bool IsNullOrEmpty + { + get + { + if (IsNull) return true; + if (_objectOrSentinel == Sentinel_Raw && _memory.IsEmpty) return true; + if (_objectOrSentinel is string s && s.Length == 0) return true; + if (_objectOrSentinel is byte[] arr && arr.Length == 0) return true; + return false; + } + } + + /// + /// Indicates whether the value is greater than zero-length or has an integer value. + /// + public bool HasValue => !IsNullOrEmpty; + + /// + /// Indicates whether two RedisValue values are equivalent. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(RedisValue x, RedisValue y) => !(x == y); + + internal double OverlappedValueDouble + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => BitConverter.Int64BitsToDouble(_overlappedBits64); + } + + internal long OverlappedValueInt64 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _overlappedBits64; + } + + internal ulong OverlappedValueUInt64 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => unchecked((ulong)_overlappedBits64); + } + + /// + /// Indicates whether two RedisValue values are equivalent. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(RedisValue x, RedisValue y) + { + x = x.Simplify(); + y = y.Simplify(); + StorageType xType = x.Type, yType = y.Type; + + if (xType == StorageType.Null) return yType == StorageType.Null; + if (yType == StorageType.Null) return false; + + if (xType == yType) + { + switch (xType) + { + case StorageType.Double: // make sure we use double equality rules + return x.OverlappedValueDouble == y.OverlappedValueDouble; + case StorageType.Int64: + case StorageType.UInt64: // as long as xType == yType, only need to check the bits + return x._overlappedBits64 == y._overlappedBits64; + case StorageType.String: + return (string?)x._objectOrSentinel == (string?)y._objectOrSentinel; + case StorageType.Raw: + return x._memory.Span.SequenceEqual(y._memory.Span); + } + } + + // if either is a numeric type, and the other isn't the *same* type (above), then: + // it can't be equal + switch (xType) + { + case StorageType.UInt64: + case StorageType.Int64: + case StorageType.Double: + return false; + } + switch (yType) + { + case StorageType.UInt64: + case StorageType.Int64: + case StorageType.Double: + return false; + } + + // otherwise, compare as strings + return (string?)x == (string?)y; + } + + /// + /// See . + /// + /// The other to compare. + public override bool Equals(object? obj) + { + if (obj == null) return IsNull; + if (obj is RedisValue typed) return Equals(typed); + var other = TryParse(obj, out var valid); + return valid && this == other; // can't be equal if parse fail + } + + /// + /// Indicates whether two RedisValue values are equivalent. + /// + /// The to compare to. + public bool Equals(RedisValue other) => this == other; + + /// + public override int GetHashCode() => GetHashCode(this); + private static int GetHashCode(RedisValue x) + { + x = x.Simplify(); + return x.Type switch + { + StorageType.Null => -1, + StorageType.Double => x.OverlappedValueDouble.GetHashCode(), + StorageType.Int64 or StorageType.UInt64 => x._overlappedBits64.GetHashCode(), + StorageType.Raw => ((string)x!).GetHashCode(), // to match equality + _ => x._objectOrSentinel!.GetHashCode(), + }; + } + + /// + /// Returns a string representation of the value. + /// + public override string ToString() => (string?)this ?? string.Empty; + + internal static unsafe bool Equals(byte[]? x, byte[]? y) + { + if ((object?)x == (object?)y) return true; // ref equals + if (x == null || y == null) return false; + int len = x.Length; + if (len != y.Length) return false; + + int octets = len / 8, spare = len % 8; + fixed (byte* x8 = x, y8 = y) + { + long* x64 = (long*)x8, y64 = (long*)y8; + for (int i = 0; i < octets; i++) + { + if (x64[i] != y64[i]) return false; + } + int offset = len - spare; + while (spare-- != 0) + { + if (x8[offset] != y8[offset++]) return false; + } + } + return true; + } + + internal static unsafe int GetHashCode(ReadOnlySpan span) + { + unchecked + { + int len = span.Length; + if (len == 0) return 0; + + int acc = 728271210; + + var span64 = MemoryMarshal.Cast(span); + for (int i = 0; i < span64.Length; i++) + { + var val = span64[i]; + int valHash = ((int)val) ^ ((int)(val >> 32)); + acc = ((acc << 5) + acc) ^ valHash; + } + int spare = len % 8, offset = len - spare; + while (spare-- != 0) + { + acc = ((acc << 5) + acc) ^ span[offset++]; + } + return acc; + } + } + + internal void AssertNotNull() + { + if (IsNull) throw new ArgumentException("A null value is not valid in this context"); + } + + internal enum StorageType + { + Null, + Int64, + UInt64, + Double, + Raw, + String, + } + + internal StorageType Type + { + get + { + var objectOrSentinel = _objectOrSentinel; + if (objectOrSentinel == null) return StorageType.Null; + if (objectOrSentinel == Sentinel_SignedInteger) return StorageType.Int64; + if (objectOrSentinel == Sentinel_Double) return StorageType.Double; + if (objectOrSentinel == Sentinel_Raw) return StorageType.Raw; + if (objectOrSentinel is string) return StorageType.String; + if (objectOrSentinel is byte[]) return StorageType.Raw; // doubled-up, but retaining the array + if (objectOrSentinel == Sentinel_UnsignedInteger) return StorageType.UInt64; + throw new InvalidOperationException("Unknown type"); + } + } + + /// + /// Get the size of this value in bytes. + /// + public long Length() => Type switch + { + StorageType.Null => 0, + StorageType.Raw => _memory.Length, + StorageType.String => Encoding.UTF8.GetByteCount((string)_objectOrSentinel!), + StorageType.Int64 => Format.MeasureInt64(OverlappedValueInt64), + StorageType.UInt64 => Format.MeasureUInt64(OverlappedValueUInt64), + StorageType.Double => Format.MeasureDouble(OverlappedValueDouble), + _ => throw new InvalidOperationException("Unable to compute length of type: " + Type), + }; + + /// + /// Compare against a RedisValue for relative order. + /// + /// The other to compare. + public int CompareTo(RedisValue other) => CompareTo(this, other); + + private static int CompareTo(RedisValue x, RedisValue y) + { + try + { + x = x.Simplify(); + y = y.Simplify(); + StorageType xType = x.Type, yType = y.Type; + + if (xType == StorageType.Null) return yType == StorageType.Null ? 0 : -1; + if (yType == StorageType.Null) return 1; + + if (xType == yType) + { + switch (xType) + { + case StorageType.Double: + return x.OverlappedValueDouble.CompareTo(y.OverlappedValueDouble); + case StorageType.Int64: + return x.OverlappedValueInt64.CompareTo(y.OverlappedValueInt64); + case StorageType.UInt64: + return x.OverlappedValueUInt64.CompareTo(y.OverlappedValueUInt64); + case StorageType.String: + return string.CompareOrdinal((string)x._objectOrSentinel!, (string)y._objectOrSentinel!); + case StorageType.Raw: + return x._memory.Span.SequenceCompareTo(y._memory.Span); + } + } + + switch (xType) + { // numbers can be still be compared between types + case StorageType.Double: + if (yType == StorageType.Int64) return x.OverlappedValueDouble.CompareTo((double)y.OverlappedValueInt64); + if (yType == StorageType.UInt64) return x.OverlappedValueDouble.CompareTo((double)y.OverlappedValueUInt64); + break; + case StorageType.Int64: + if (yType == StorageType.Double) return ((double)x.OverlappedValueInt64).CompareTo(y.OverlappedValueDouble); + if (yType == StorageType.UInt64) return 1; // we only use unsigned if > int64, so: y is bigger + break; + case StorageType.UInt64: + if (yType == StorageType.Double) return ((double)x.OverlappedValueUInt64).CompareTo(y.OverlappedValueDouble); + if (yType == StorageType.Int64) return -1; // we only use unsigned if > int64, so: x is bigger + break; + } + + // otherwise, compare as strings + return string.CompareOrdinal((string?)x, (string?)y); + } + catch (Exception ex) + { + ConnectionMultiplexer.TraceWithoutContext(ex.Message); + } + // if all else fails, consider equivalent + return 0; + } + + int IComparable.CompareTo(object? obj) + { + if (obj == null) return CompareTo(Null); + + var val = TryParse(obj, out var valid); + if (!valid) return -1; // parse fail + + return CompareTo(val); + } + + internal static RedisValue TryParse(object? obj, out bool valid) + { + valid = true; + switch (obj) + { + case null: return Null; + case string v: return v; + case int v: return v; + case uint v: return v; + case double v: return v; + case byte[] v: return v; + case bool v: return v; + case long v: return v; + case ulong v: return v; + case float v: return v; + case ReadOnlyMemory v: return v; + case Memory v: return v; + case RedisValue v: return v; + default: + valid = false; + return Null; + } + } + + /// + /// Creates a new from an . + /// + /// The to convert to a . + public static implicit operator RedisValue(int value) => new RedisValue(value, default, Sentinel_SignedInteger); + + /// + /// Creates a new from an . + /// + /// The to convert to a . + public static implicit operator RedisValue(int? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault(); + + /// + /// Creates a new from an . + /// + /// The to convert to a . + public static implicit operator RedisValue(long value) => new RedisValue(value, default, Sentinel_SignedInteger); + + /// + /// Creates a new from an . + /// + /// The to convert to a . + public static implicit operator RedisValue(long? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault(); + + /// + /// Creates a new from an . + /// + /// The to convert to a . + [CLSCompliant(false)] + public static implicit operator RedisValue(ulong value) + { + const ulong MSB = 1UL << 63; + return (value & MSB) == 0 + ? new RedisValue((long)value, default, Sentinel_SignedInteger) // prefer signed whenever we can + : new RedisValue(unchecked((long)value), default, Sentinel_UnsignedInteger); // with unsigned as the fallback + } + + /// + /// Creates a new from an . + /// + /// The to convert to a . + [CLSCompliant(false)] + public static implicit operator RedisValue(ulong? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault(); + + /// + /// Creates a new from an . + /// + /// The to convert to a . + [CLSCompliant(false)] + public static implicit operator RedisValue(uint value) => new RedisValue(value, default, Sentinel_SignedInteger); // 32-bits always fits as signed + + /// + /// Creates a new from an . + /// + /// The to convert to a . + [CLSCompliant(false)] + public static implicit operator RedisValue(uint? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault(); + + /// + /// Creates a new from an . + /// + /// The to convert to a . + public static implicit operator RedisValue(double value) + { + try + { + var i64 = (long)value; + // note: double doesn't offer integer accuracy at 64 bits, so we know it can't be unsigned (only use that for 64-bit) + if (value == i64) return new RedisValue(i64, default, Sentinel_SignedInteger); + } + catch { } + return new RedisValue(BitConverter.DoubleToInt64Bits(value), default, Sentinel_Double); + } + + /// + /// Creates a new from an . + /// + /// The to convert to a . + public static implicit operator RedisValue(double? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault(); + + /// + /// Creates a new from a . + /// + /// The to convert to a . + public static implicit operator RedisValue(ReadOnlyMemory value) + { + if (value.Length == 0) return EmptyString; + return new RedisValue(0, value, Sentinel_Raw); + } + + /// + /// Creates a new from a . + /// + /// The to convert to a . + public static implicit operator RedisValue(Memory value) => (ReadOnlyMemory)value; + + /// + /// Creates a new from an . + /// + /// The to convert to a . + public static implicit operator RedisValue(string? value) + { + if (value == null) return Null; + if (value.Length == 0) return EmptyString; + return new RedisValue(0, default, value); + } + + /// + /// Creates a new from an . + /// + /// The to convert to a . + public static implicit operator RedisValue(byte[]? value) + { + if (value == null) return Null; + if (value.Length == 0) return EmptyString; + return new RedisValue(0, new Memory(value), value); + } + + /// + /// Creates a new from an . + /// + /// The to convert to a . + public static implicit operator RedisValue(bool value) => new RedisValue(value ? 1 : 0, default, Sentinel_SignedInteger); + + /// + /// Creates a new from an . + /// + /// The to convert to a . + public static implicit operator RedisValue(bool? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault(); + + /// + /// Converts a to a . + /// + /// The to convert. + public static explicit operator bool(RedisValue value) => (long)value switch + { + 0 => false, + 1 => true, + _ => throw new InvalidCastException(), + }; + + /// + /// Converts a to a . + /// + /// The to convert. + public static explicit operator int(RedisValue value) + => checked((int)(long)value); + + /// + /// Converts a to a . + /// + /// The to convert. + public static explicit operator long(RedisValue value) + { + value = value.Simplify(); + return value.Type switch + { + StorageType.Null => 0, // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr") + StorageType.Int64 => value.OverlappedValueInt64, + StorageType.UInt64 => checked((long)value.OverlappedValueUInt64), // this will throw since unsigned is always 64-bit + _ => throw new InvalidCastException($"Unable to cast from {value.Type} to long: '{value}'"), + }; + } + + /// + /// Converts a to a . + /// + /// The to convert. + [CLSCompliant(false)] + public static explicit operator uint(RedisValue value) + { + value = value.Simplify(); + return value.Type switch + { + StorageType.Null => 0, // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr") + StorageType.Int64 => checked((uint)value.OverlappedValueInt64), + StorageType.UInt64 => checked((uint)value.OverlappedValueUInt64), + _ => throw new InvalidCastException($"Unable to cast from {value.Type} to uint: '{value}'"), + }; + } + + /// + /// Converts a to a . + /// + /// The to convert. + [CLSCompliant(false)] + public static explicit operator ulong(RedisValue value) + { + value = value.Simplify(); + return value.Type switch + { + StorageType.Null => 0, // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr") + StorageType.Int64 => checked((ulong)value.OverlappedValueInt64), // throw if negative + StorageType.UInt64 => value.OverlappedValueUInt64, + _ => throw new InvalidCastException($"Unable to cast from {value.Type} to ulong: '{value}'"), + }; + } + + /// + /// Converts a to a . + /// + /// The to convert. + public static explicit operator double(RedisValue value) + { + value = value.Simplify(); + return value.Type switch + { + StorageType.Null => 0, // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr") + StorageType.Int64 => value.OverlappedValueInt64, + StorageType.UInt64 => value.OverlappedValueUInt64, + StorageType.Double => value.OverlappedValueDouble, + // special values like NaN/Inf are deliberately not handled by Simplify, but need to be considered for casting + StorageType.String when Format.TryParseDouble((string)value._objectOrSentinel!, out var d) => d, + StorageType.Raw when TryParseDouble(value._memory.Span, out var d) => d, + // anything else: fail + _ => throw new InvalidCastException($"Unable to cast from {value.Type} to double: '{value}'"), + }; + } + + /// + /// Converts a to a . + /// + /// The to convert. + public static explicit operator decimal(RedisValue value) + { + value = value.Simplify(); + return value.Type switch + { + StorageType.Null => 0, // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr") + StorageType.Int64 => value.OverlappedValueInt64, + StorageType.UInt64 => value.OverlappedValueUInt64, + StorageType.Double => (decimal)value.OverlappedValueDouble, + _ => throw new InvalidCastException($"Unable to cast from {value.Type} to decimal: '{value}'"), + }; + } + + /// + /// Converts a to a . + /// + /// The to convert. + public static explicit operator float(RedisValue value) + { + value = value.Simplify(); + return value.Type switch + { + StorageType.Null => 0, // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr") + StorageType.Int64 => value.OverlappedValueInt64, + StorageType.UInt64 => value.OverlappedValueUInt64, + StorageType.Double => (float)value.OverlappedValueDouble, + _ => throw new InvalidCastException($"Unable to cast from {value.Type} to double: '{value}'"), + }; + } + + private static bool TryParseDouble(ReadOnlySpan blob, out double value) + { + // simple integer? + if (Format.CouldBeInteger(blob) && Format.TryParseInt64(blob, out var i64)) + { + value = i64; + return true; + } + + return Format.TryParseDouble(blob, out value); + } + + /// + /// Converts the to a . + /// + /// The to convert. + public static explicit operator double?(RedisValue value) + => value.IsNull ? (double?)null : (double)value; + + /// + /// Converts the to a . + /// + /// The to convert. + public static explicit operator float?(RedisValue value) + => value.IsNull ? (float?)null : (float)value; + + /// + /// Converts the to a . + /// + /// The to convert. + public static explicit operator decimal?(RedisValue value) + => value.IsNull ? (decimal?)null : (decimal)value; + + /// + /// Converts the to a . + /// + /// The to convert. + public static explicit operator long?(RedisValue value) + => value.IsNull ? (long?)null : (long)value; + + /// + /// Converts the to a . + /// + /// The to convert. + [CLSCompliant(false)] + public static explicit operator ulong?(RedisValue value) + => value.IsNull ? (ulong?)null : (ulong)value; + + /// + /// Converts the to a . + /// + /// The to convert. + public static explicit operator int?(RedisValue value) + => value.IsNull ? (int?)null : (int)value; + + /// + /// Converts the to a . + /// + /// The to convert. + [CLSCompliant(false)] + public static explicit operator uint?(RedisValue value) + => value.IsNull ? (uint?)null : (uint)value; + + /// + /// Converts the to a . + /// + /// The to convert. + public static explicit operator bool?(RedisValue value) + => value.IsNull ? (bool?)null : (bool)value; + + /// + /// Converts a to a . + /// + /// The to convert. + public static implicit operator string?(RedisValue value) + { + switch (value.Type) + { + case StorageType.Null: return null; + case StorageType.Double: return Format.ToString(value.OverlappedValueDouble); + case StorageType.Int64: return Format.ToString(value.OverlappedValueInt64); + case StorageType.UInt64: return Format.ToString(value.OverlappedValueUInt64); + case StorageType.String: return (string)value._objectOrSentinel!; + case StorageType.Raw: + var span = value._memory.Span; + if (span.IsEmpty) return ""; + if (span.Length == 2 && span[0] == (byte)'O' && span[1] == (byte)'K') return "OK"; // frequent special-case + try + { + return Format.GetString(span); + } + catch (Exception e) when // Only catch exception throwed by Encoding.UTF8.GetString + (e is DecoderFallbackException + || e is ArgumentException + || e is ArgumentNullException) + { + return ToHex(span); + } + default: + throw new InvalidOperationException(); + } + } + private static string ToHex(ReadOnlySpan src) + { + const string HexValues = "0123456789ABCDEF"; + + if (src.IsEmpty) return ""; + var s = new string((char)0, (src.Length * 3) - 1); + var dst = MemoryMarshal.AsMemory(s.AsMemory()).Span; + + int i = 0; + int j = 0; + + byte b = src[i++]; + dst[j++] = HexValues[b >> 4]; + dst[j++] = HexValues[b & 0xF]; + + while (i < src.Length) + { + b = src[i++]; + dst[j++] = '-'; + dst[j++] = HexValues[b >> 4]; + dst[j++] = HexValues[b & 0xF]; + } + return s; + } + + /// + /// Converts a to a . + /// + /// The to convert. + public static implicit operator byte[]?(RedisValue value) + { + switch (value.Type) + { + case StorageType.Null: return null; + case StorageType.Raw: + if (value._objectOrSentinel is byte[] arr) return arr; + + if (MemoryMarshal.TryGetArray(value._memory, out var segment) + && segment.Offset == 0 + && segment.Count == (segment.Array?.Length ?? -1)) + { + return segment.Array; // the memory is backed by an array, and we're reading all of it + } + + return value._memory.ToArray(); + case StorageType.Int64: + Debug.Assert(Format.MaxInt64TextLen <= 24); + Span span = stackalloc byte[24]; + int len = Format.FormatInt64(value.OverlappedValueInt64, span); + return span.Slice(0, len).ToArray(); + case StorageType.UInt64: + Debug.Assert(Format.MaxInt64TextLen <= 24); + span = stackalloc byte[24]; + len = Format.FormatUInt64(value.OverlappedValueUInt64, span); + return span.Slice(0, len).ToArray(); + case StorageType.Double: + span = stackalloc byte[Format.MaxDoubleTextLen]; + len = Format.FormatDouble(value.OverlappedValueDouble, span); + return span.Slice(0, len).ToArray(); + case StorageType.String: + return Encoding.UTF8.GetBytes((string)value._objectOrSentinel!); + } + // fallback: stringify and encode + return Encoding.UTF8.GetBytes((string)value!); + } + + /// + /// Gets the length of the value in bytes. + /// + public int GetByteCount() + { + switch (Type) + { + case StorageType.Null: return 0; + case StorageType.Raw: return _memory.Length; + case StorageType.String: return Encoding.UTF8.GetByteCount((string)_objectOrSentinel!); + case StorageType.Int64: return Format.MeasureInt64(OverlappedValueInt64); + case StorageType.UInt64: return Format.MeasureUInt64(OverlappedValueUInt64); + case StorageType.Double: return Format.MeasureDouble(OverlappedValueDouble); + default: return ThrowUnableToMeasure(); + } + } + + private int ThrowUnableToMeasure() => throw new InvalidOperationException("Unable to compute length of type: " + Type); + + /// + /// Gets the length of the value in bytes. + /// + /* right now, we only support int lengths, but adding this now so that + there are no surprises if/when we add support for discontiguous buffers */ + public long GetLongByteCount() => GetByteCount(); + + /// + /// Copy the value as bytes to the provided . + /// + public int CopyTo(Span destination) + { + switch (Type) + { + case StorageType.Null: + return 0; + case StorageType.Raw: + var srcBytes = _memory.Span; + srcBytes.CopyTo(destination); + return srcBytes.Length; + case StorageType.String: + return Encoding.UTF8.GetBytes(((string)_objectOrSentinel!).AsSpan(), destination); + case StorageType.Int64: + return Format.FormatInt64(OverlappedValueInt64, destination); + case StorageType.UInt64: + return Format.FormatUInt64(OverlappedValueUInt64, destination); + case StorageType.Double: + return Format.FormatDouble(OverlappedValueDouble, destination); + default: + return ThrowUnableToMeasure(); + } + } + + /// + /// Converts a to a . + /// + /// The to convert. + public static implicit operator ReadOnlyMemory(RedisValue value) + => value.Type == StorageType.Raw ? value._memory : (byte[]?)value; + + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + + bool IConvertible.ToBoolean(IFormatProvider? provider) => (bool)this; + byte IConvertible.ToByte(IFormatProvider? provider) => (byte)(uint)this; + char IConvertible.ToChar(IFormatProvider? provider) => (char)(uint)this; + DateTime IConvertible.ToDateTime(IFormatProvider? provider) => DateTime.Parse(((string?)this)!, provider); + decimal IConvertible.ToDecimal(IFormatProvider? provider) => (decimal)this; + double IConvertible.ToDouble(IFormatProvider? provider) => (double)this; + short IConvertible.ToInt16(IFormatProvider? provider) => (short)this; + int IConvertible.ToInt32(IFormatProvider? provider) => (int)this; + long IConvertible.ToInt64(IFormatProvider? provider) => (long)this; + sbyte IConvertible.ToSByte(IFormatProvider? provider) => (sbyte)this; + float IConvertible.ToSingle(IFormatProvider? provider) => (float)this; + string IConvertible.ToString(IFormatProvider? provider) => ((string?)this)!; + + object IConvertible.ToType(Type conversionType, IFormatProvider? provider) + { + if (conversionType == null) throw new ArgumentNullException(nameof(conversionType)); + if (conversionType == typeof(byte[])) return ((byte[]?)this)!; + if (conversionType == typeof(ReadOnlyMemory)) return (ReadOnlyMemory)this; + if (conversionType == typeof(RedisValue)) return this; + return System.Type.GetTypeCode(conversionType) switch + { + TypeCode.Boolean => (bool)this, + TypeCode.Byte => checked((byte)(uint)this), + TypeCode.Char => checked((char)(uint)this), + TypeCode.DateTime => DateTime.Parse(((string?)this)!, provider), + TypeCode.Decimal => (decimal)this, + TypeCode.Double => (double)this, + TypeCode.Int16 => (short)this, + TypeCode.Int32 => (int)this, + TypeCode.Int64 => (long)this, + TypeCode.SByte => (sbyte)this, + TypeCode.Single => (float)this, + TypeCode.String => ((string?)this)!, + TypeCode.UInt16 => checked((ushort)(uint)this), + TypeCode.UInt32 => (uint)this, + TypeCode.UInt64 => (ulong)this, + TypeCode.Object => this, + _ => throw new NotSupportedException(), + }; + } + + ushort IConvertible.ToUInt16(IFormatProvider? provider) => checked((ushort)(uint)this); + uint IConvertible.ToUInt32(IFormatProvider? provider) => (uint)this; + ulong IConvertible.ToUInt64(IFormatProvider? provider) => (ulong)this; + + /// + /// Attempt to reduce to canonical terms ahead of time; parses integers, floats, etc + /// Note: we don't use this aggressively ahead of time, a: because of extra CPU, + /// but more importantly b: because it can change values - for example, if they start + /// with "123.000", it should **stay** as "123.000", not become 123L; this could be + /// a hash key or similar - we don't want to break it; RedisConnection uses + /// the storage type, not the "does it look like a long?" - for this reason. + /// + internal RedisValue Simplify() + { + long i64; + ulong u64; + switch (Type) + { + case StorageType.String: + string s = (string)_objectOrSentinel!; + if (Format.CouldBeInteger(s)) + { + if (Format.TryParseInt64(s, out i64)) return i64; + if (Format.TryParseUInt64(s, out u64)) return u64; + } + // note: don't simplify inf/nan, as that causes equality semantic problems + if (Format.TryParseDouble(s, out var f64) && !IsSpecialDouble(f64)) return f64; + break; + case StorageType.Raw: + var b = _memory.Span; + if (Format.CouldBeInteger(b)) + { + if (Format.TryParseInt64(b, out i64)) return i64; + if (Format.TryParseUInt64(b, out u64)) return u64; + } + // note: don't simplify inf/nan, as that causes equality semantic problems + if (TryParseDouble(b, out f64) && !IsSpecialDouble(f64)) return f64; + break; + case StorageType.Double: + // is the double actually an integer? + f64 = OverlappedValueDouble; + if (f64 >= long.MinValue && f64 <= long.MaxValue && (i64 = (long)f64) == f64) return i64; + break; + } + return this; + } + + private static bool IsSpecialDouble(double d) => double.IsNaN(d) || double.IsInfinity(d); + + /// + /// Convert to a signed if possible. + /// + /// The value, if conversion was possible. + /// if successfully parsed, otherwise. + public bool TryParse(out long val) + { + switch (Type) + { + case StorageType.Int64: + val = OverlappedValueInt64; + return true; + case StorageType.UInt64: + // we only use unsigned for oversize, so no: it doesn't fit + val = default; + return false; + case StorageType.String: + return Format.TryParseInt64((string)_objectOrSentinel!, out val); + case StorageType.Raw: + return Format.TryParseInt64(_memory.Span, out val); + case StorageType.Double: + var d = OverlappedValueDouble; + try + { + val = (long)d; + } + catch + { + val = default; + return false; + } + return val == d; + case StorageType.Null: + // in redis-land 0 approx. equal null; so roll with it + val = 0; + return true; + } + val = default; + return false; + } + + /// + /// Convert to an if possible. + /// + /// The value, if conversion was possible. + /// if successfully parsed, otherwise. + public bool TryParse(out int val) + { + if (!TryParse(out long l) || l > int.MaxValue || l < int.MinValue) + { + val = 0; + return false; + } + + val = (int)l; + return true; + } + + /// + /// Convert to a if possible. + /// + /// The value, if conversion was possible. + /// if successfully parsed, otherwise. + public bool TryParse(out double val) + { + switch (Type) + { + case StorageType.Int64: + val = OverlappedValueInt64; + return true; + case StorageType.UInt64: + val = OverlappedValueUInt64; + return true; + case StorageType.Double: + val = OverlappedValueDouble; + return true; + case StorageType.String: + return Format.TryParseDouble((string)_objectOrSentinel!, out val); + case StorageType.Raw: + return TryParseDouble(_memory.Span, out val); + case StorageType.Null: + // in redis-land 0 approx. equal null; so roll with it + val = 0; + return true; + } + val = default; + return false; + } + + /// + /// Create a from a . + /// It will *attempt* to use the internal buffer directly, but if this isn't possible it will fallback to . + /// + /// The to create a value from. + public static RedisValue CreateFrom(MemoryStream stream) + { + if (stream == null) return Null; + if (stream.Length == 0) return Array.Empty(); + if (stream.TryGetBuffer(out var segment) || ReflectionTryGetBuffer(stream, out segment)) + { + return new Memory(segment.Array, segment.Offset, segment.Count); + } + else + { + // nowhere near as efficient, but... + return stream.ToArray(); + } + } + + private static readonly FieldInfo? + s_origin = typeof(MemoryStream).GetField("_origin", BindingFlags.NonPublic | BindingFlags.Instance), + s_buffer = typeof(MemoryStream).GetField("_buffer", BindingFlags.NonPublic | BindingFlags.Instance); + + private static bool ReflectionTryGetBuffer(MemoryStream ms, out ArraySegment buffer) + { + if (s_origin != null && s_buffer != null) + { + try + { + int offset = (int)s_origin.GetValue(ms)!; + byte[] arr = (byte[])s_buffer.GetValue(ms)!; + buffer = new ArraySegment(arr, offset, checked((int)ms.Length)); + return true; + } + catch { } + } + buffer = default; + return false; + } + + /// + /// Indicates whether the current value has the supplied value as a prefix. + /// + /// The to check. + public bool StartsWith(RedisValue value) + { + if (IsNull || value.IsNull) return false; + if (value.IsNullOrEmpty) return true; + if (IsNullOrEmpty) return false; + + ReadOnlyMemory rawThis, rawOther; + var thisType = Type; + if (thisType == value.Type) // same? can often optimize + { + switch (thisType) + { + case StorageType.String: + var sThis = (string)_objectOrSentinel!; + var sOther = (string)value._objectOrSentinel!; + return sThis.StartsWith(sOther, StringComparison.Ordinal); + case StorageType.Raw: + rawThis = _memory; + rawOther = value._memory; + return rawThis.Span.StartsWith(rawOther.Span); + } + } + byte[]? arr0 = null, arr1 = null; + try + { + rawThis = AsMemory(out arr0); + rawOther = value.AsMemory(out arr1); + + return rawThis.Span.StartsWith(rawOther.Span); + } + finally + { + if (arr0 != null) ArrayPool.Shared.Return(arr0); + if (arr1 != null) ArrayPool.Shared.Return(arr1); + } + } + + private ReadOnlyMemory AsMemory(out byte[]? leased) + { + switch (Type) + { + case StorageType.Raw: + leased = null; + return _memory; + case StorageType.String: + string s = (string)_objectOrSentinel!; +HaveString: + if (s.Length == 0) + { + leased = null; + return default; + } + leased = ArrayPool.Shared.Rent(Encoding.UTF8.GetByteCount(s)); + var len = Encoding.UTF8.GetBytes(s, 0, s.Length, leased, 0); + return new ReadOnlyMemory(leased, 0, len); + case StorageType.Double: + s = Format.ToString(OverlappedValueDouble); + goto HaveString; + case StorageType.Int64: + leased = ArrayPool.Shared.Rent(Format.MaxInt64TextLen + 2); // reused code has CRLF terminator + len = PhysicalConnection.WriteRaw(leased, OverlappedValueInt64) - 2; // drop the CRLF + return new ReadOnlyMemory(leased, 0, len); + case StorageType.UInt64: + leased = ArrayPool.Shared.Rent(Format.MaxInt64TextLen); // reused code has CRLF terminator + // value is huge, jump direct to Utf8Formatter + if (!Utf8Formatter.TryFormat(OverlappedValueUInt64, leased, out len)) + throw new InvalidOperationException("TryFormat failed"); + return new ReadOnlyMemory(leased, 0, len); + } + leased = null; + return default; + } + } +} diff --git a/src/StackExchange.Redis/ResultBox.cs b/src/StackExchange.Redis/ResultBox.cs new file mode 100644 index 000000000..20b76ba15 --- /dev/null +++ b/src/StackExchange.Redis/ResultBox.cs @@ -0,0 +1,156 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + internal interface IResultBox + { + bool IsAsync { get; } + bool IsFaulted { get; } + void SetException(Exception ex); + void ActivateContinuations(); + void Cancel(); + } + internal interface IResultBox : IResultBox + { + T? GetResult(out Exception? ex, bool canRecycle = false); + void SetResult(T value); + } + + internal abstract class SimpleResultBox : IResultBox + { + private volatile Exception? _exception; + + bool IResultBox.IsAsync => false; + bool IResultBox.IsFaulted => _exception != null; + void IResultBox.SetException(Exception exception) => _exception = exception ?? CancelledException; + void IResultBox.Cancel() => _exception = CancelledException; + + void IResultBox.ActivateContinuations() + { + lock (this) + { // tell the waiting thread that we're done + Monitor.PulseAll(this); + } + ConnectionMultiplexer.TraceWithoutContext("Pulsed", "Result"); + } + + // in theory nobody should directly observe this; the only things + // that call Cancel are transactions etc - TCS-based, and we detect + // that and use TrySetCanceled instead + // about any confusion in stack-trace + internal static readonly Exception CancelledException = new TaskCanceledException(); + + protected Exception? Exception + { + get => _exception; + set => _exception = value; + } + } + + internal sealed class SimpleResultBox : SimpleResultBox, IResultBox + { + private SimpleResultBox() { } + private T? _value; + + [ThreadStatic] + private static SimpleResultBox? _perThreadInstance; + + public static IResultBox Create() => new SimpleResultBox(); + public static IResultBox Get() // includes recycled boxes; used from sync, so makes re-use easy + { + var obj = _perThreadInstance ?? new SimpleResultBox(); + _perThreadInstance = null; // in case of oddness; only set back when recycled + return obj; + } + + void IResultBox.SetResult(T value) => _value = value; + + T? IResultBox.GetResult(out Exception? ex, bool canRecycle) + { + var value = _value; + ex = Exception; + if (canRecycle) + { + Exception = null; + _value = default!; + _perThreadInstance = this; + } + return value; + } + } + + internal sealed class TaskResultBox : TaskCompletionSource, IResultBox + { + // you might be asking "wait, doesn't the Task own these?", to which + // I say: no; we can't set *immediately* due to thread-theft etc, hence + // the fun TryComplete indirection - so we need somewhere to buffer them + private volatile Exception? _exception; + private T _value = default!; + + private TaskResultBox(object? asyncState, TaskCreationOptions creationOptions) : base(asyncState, creationOptions) + { } + + bool IResultBox.IsAsync => true; + + bool IResultBox.IsFaulted => _exception != null; + + void IResultBox.Cancel() => _exception = SimpleResultBox.CancelledException; + + void IResultBox.SetException(Exception ex) => _exception = ex ?? SimpleResultBox.CancelledException; + + void IResultBox.SetResult(T value) => _value = value; + + T? IResultBox.GetResult(out Exception? ex, bool unused) + { + ex = _exception; + return _value; + // nothing to do re recycle: TaskCompletionSource cannot be recycled + } + + private static readonly WaitCallback s_ActivateContinuations = state => ((TaskResultBox)state!).ActivateContinuationsImpl(); + void IResultBox.ActivateContinuations() + { + if ((Task.CreationOptions & TaskCreationOptions.RunContinuationsAsynchronously) == 0) + ThreadPool.UnsafeQueueUserWorkItem(s_ActivateContinuations, this); + else + ActivateContinuationsImpl(); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1816:Dispose methods should call SuppressFinalize", Justification = "Intentional observation")] + private void ActivateContinuationsImpl() + { + var val = _value; + var ex = _exception; + + if (ex == null) + { + TrySetResult(val); + } + else + { + if (ex is TaskCanceledException) TrySetCanceled(); + else TrySetException(ex); + var task = Task; + GC.KeepAlive(task.Exception); // mark any exception as observed + GC.SuppressFinalize(task); // finalizer only exists for unobserved-exception purposes + } + } + + public static IResultBox Create(out TaskCompletionSource source, object? asyncState) + { + // it might look a little odd to return the same object as two different things, + // but that's because it is serving two purposes, and I want to make it clear + // how it is being used in those 2 different ways; also, the *fact* that they + // are the same underlying object is an implementation detail that the rest of + // the code doesn't need to know about + var obj = new TaskResultBox( + asyncState, + // if we don't trust the TPL/sync-context, avoid a double QUWI dispatch + ConnectionMultiplexer.PreventThreadTheft ? TaskCreationOptions.None : TaskCreationOptions.RunContinuationsAsynchronously); + source = obj; + return obj; + } + } +} diff --git a/src/StackExchange.Redis/ResultProcessor.Lease.cs b/src/StackExchange.Redis/ResultProcessor.Lease.cs new file mode 100644 index 000000000..c0f9e6d8e --- /dev/null +++ b/src/StackExchange.Redis/ResultProcessor.Lease.cs @@ -0,0 +1,218 @@ +using System.Diagnostics; +using Pipelines.Sockets.Unofficial.Arenas; + +// ReSharper disable once CheckNamespace +namespace StackExchange.Redis; + +internal abstract partial class ResultProcessor +{ + // Lease result processors + public static readonly ResultProcessor?> LeaseFloat32 = new LeaseFloat32Processor(); + + public static readonly ResultProcessor> + Lease = new LeaseProcessor(); + + public static readonly ResultProcessor> + LeaseFromArray = new LeaseFromArrayProcessor(); + + private abstract class LeaseProcessor : ResultProcessor?> + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray != ResultType.Array) + { + return false; // not an array + } + + // deal with null + if (result.IsNull) + { + SetResult(message, Lease.Empty); + return true; + } + + // lease and fill + var items = result.GetItems(); + var length = checked((int)items.Length); + var lease = Lease.Create(length, clear: false); // note this handles zero nicely + var target = lease.Span; + int index = 0; + foreach (ref RawResult item in items) + { + if (!TryParse(item, out target[index++])) + { + // something went wrong; recycle and quit + lease.Dispose(); + return false; + } + } + Debug.Assert(index == length, "length mismatch"); + SetResult(message, lease); + return true; + } + + protected abstract bool TryParse(in RawResult raw, out T parsed); + } + + private abstract class InterleavedLeaseProcessor : ResultProcessor?> + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray != ResultType.Array) + { + return false; // not an array + } + + // deal with null + if (result.IsNull) + { + SetResult(message, Lease.Empty); + return true; + } + + // lease and fill + var items = result.GetItems(); + var length = checked((int)items.Length) / 2; + var lease = Lease.Create(length, clear: false); // note this handles zero nicely + var target = lease.Span; + + var iter = items.GetEnumerator(); + for (int i = 0; i < target.Length; i++) + { + bool ok = iter.MoveNext(); + if (ok) + { + ref readonly RawResult first = ref iter.Current; + ok = iter.MoveNext() && TryParse(in first, in iter.Current, out target[i]); + } + if (!ok) + { + lease.Dispose(); + return false; + } + } + SetResult(message, lease); + return true; + } + + protected abstract bool TryParse(in RawResult first, in RawResult second, out T parsed); + } + + // takes a nested vector of the form [[A],[B,C],[D]] and exposes it as [A,B,C,D]; this is + // especially useful for VLINKS + private abstract class FlattenedLeaseProcessor : ResultProcessor?> + { + protected virtual long GetArrayLength(in RawResult array) => array.GetItems().Length; + + protected virtual bool TryReadOne(ref Sequence.Enumerator reader, out T value) + { + if (reader.MoveNext()) + { + return TryReadOne(in reader.Current, out value); + } + value = default!; + return false; + } + + protected virtual bool TryReadOne(in RawResult result, out T value) + { + value = default!; + return false; + } + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray != ResultType.Array) + { + return false; // not an array + } + if (result.IsNull) + { + SetResult(message, Lease.Empty); + return true; + } + var items = result.GetItems(); + long length = 0; + foreach (ref RawResult item in items) + { + if (item.Resp2TypeArray == ResultType.Array && !item.IsNull) + { + length += GetArrayLength(in item); + } + } + + if (length == 0) + { + SetResult(message, Lease.Empty); + return true; + } + var lease = Lease.Create(checked((int)length), clear: false); + int index = 0; + var target = lease.Span; + foreach (ref RawResult item in items) + { + if (item.Resp2TypeArray == ResultType.Array && !item.IsNull) + { + var iter = item.GetItems().GetEnumerator(); + while (index < target.Length && TryReadOne(ref iter, out target[index])) + { + index++; + } + } + } + + if (index == length) + { + SetResult(message, lease); + return true; + } + lease.Dispose(); // failed to fill? + return false; + } + } + + private sealed class LeaseFloat32Processor : LeaseProcessor + { + protected override bool TryParse(in RawResult raw, out float parsed) + { + var result = raw.TryGetDouble(out double val); + parsed = (float)val; + return result; + } + } + + private sealed class LeaseProcessor : ResultProcessor> + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + SetResult(message, result.AsLease()!); + return true; + } + return false; + } + } + + private sealed class LeaseFromArrayProcessor : ResultProcessor> + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Array: + var items = result.GetItems(); + if (items.Length == 1) + { // treat an array of 1 like a single reply + SetResult(message, items[0].AsLease()!); + return true; + } + break; + } + return false; + } + } +} diff --git a/src/StackExchange.Redis/ResultProcessor.VectorSets.cs b/src/StackExchange.Redis/ResultProcessor.VectorSets.cs new file mode 100644 index 000000000..8743ebd0b --- /dev/null +++ b/src/StackExchange.Redis/ResultProcessor.VectorSets.cs @@ -0,0 +1,138 @@ +using Pipelines.Sockets.Unofficial.Arenas; + +// ReSharper disable once CheckNamespace +namespace StackExchange.Redis; + +internal abstract partial class ResultProcessor +{ + // VectorSet result processors + public static readonly ResultProcessor?> VectorSetLinksWithScores = + new VectorSetLinksWithScoresProcessor(); + + public static readonly ResultProcessor?> VectorSetLinks = new VectorSetLinksProcessor(); + + public static ResultProcessor VectorSetInfo = new VectorSetInfoProcessor(); + + private sealed class VectorSetLinksWithScoresProcessor : FlattenedLeaseProcessor + { + protected override long GetArrayLength(in RawResult array) => array.GetItems().Length / 2; + + protected override bool TryReadOne(ref Sequence.Enumerator reader, out VectorSetLink value) + { + if (reader.MoveNext()) + { + ref readonly RawResult first = ref reader.Current; + if (reader.MoveNext() && reader.Current.TryGetDouble(out var score)) + { + value = new VectorSetLink(first.AsRedisValue(), score); + return true; + } + } + + value = default; + return false; + } + } + + private sealed class VectorSetLinksProcessor : FlattenedLeaseProcessor + { + protected override bool TryReadOne(in RawResult result, out RedisValue value) + { + value = result.AsRedisValue(); + return true; + } + } + + private sealed partial class VectorSetInfoProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray == ResultType.Array) + { + if (result.IsNull) + { + SetResult(message, null); + return true; + } + + var quantType = VectorSetQuantization.Unknown; + string? quantTypeRaw = null; + int vectorDim = 0, maxLevel = 0; + long resultSize = 0, vsetUid = 0, hnswMaxNodeUid = 0; + var iter = result.GetItems().GetEnumerator(); + while (iter.MoveNext()) + { + ref readonly RawResult key = ref iter.Current; + if (!iter.MoveNext()) break; + ref readonly RawResult value = ref iter.Current; + + var len = key.Payload.Length; + var keyHash = key.Payload.Hash64(); + switch (key.Payload.Length) + { + case size.Length when size.Is(keyHash, key) && value.TryGetInt64(out var i64): + resultSize = i64; + break; + case vset_uid.Length when vset_uid.Is(keyHash, key) && value.TryGetInt64(out var i64): + vsetUid = i64; + break; + case max_level.Length when max_level.Is(keyHash, key) && value.TryGetInt64(out var i64): + maxLevel = checked((int)i64); + break; + case vector_dim.Length + when vector_dim.Is(keyHash, key) && value.TryGetInt64(out var i64): + vectorDim = checked((int)i64); + break; + case quant_type.Length when quant_type.Is(keyHash, key): + var qHash = value.Payload.Hash64(); + switch (value.Payload.Length) + { + case bin.Length when bin.Is(qHash, value): + quantType = VectorSetQuantization.Binary; + break; + case f32.Length when f32.Is(qHash, value): + quantType = VectorSetQuantization.None; + break; + case int8.Length when int8.Is(qHash, value): + quantType = VectorSetQuantization.Int8; + break; + default: + quantTypeRaw = value.GetString(); + quantType = VectorSetQuantization.Unknown; + break; + } + + break; + case hnsw_max_node_uid.Length + when hnsw_max_node_uid.Is(keyHash, key) && value.TryGetInt64(out var i64): + hnswMaxNodeUid = i64; + break; + } + } + + SetResult( + message, + new VectorSetInfo(quantType, quantTypeRaw, vectorDim, resultSize, maxLevel, vsetUid, hnswMaxNodeUid)); + return true; + } + + return false; + } + +#pragma warning disable CS8981, SA1134, SA1300, SA1303, SA1502 + // ReSharper disable InconsistentNaming - to better represent expected literals + // ReSharper disable IdentifierTypo + [FastHash] private static partial class bin { } + [FastHash] private static partial class f32 { } + [FastHash] private static partial class int8 { } + [FastHash] private static partial class size { } + [FastHash] private static partial class vset_uid { } + [FastHash] private static partial class max_level { } + [FastHash] private static partial class quant_type { } + [FastHash] private static partial class vector_dim { } + [FastHash] private static partial class hnsw_max_node_uid { } + // ReSharper restore InconsistentNaming + // ReSharper restore IdentifierTypo +#pragma warning restore CS8981, SA1134, SA1300, SA1303, SA1502 + } +} diff --git a/src/StackExchange.Redis/ResultProcessor.cs b/src/StackExchange.Redis/ResultProcessor.cs new file mode 100644 index 000000000..650cba603 --- /dev/null +++ b/src/StackExchange.Redis/ResultProcessor.cs @@ -0,0 +1,3050 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Net; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.Extensions.Logging; +using Pipelines.Sockets.Unofficial.Arenas; + +namespace StackExchange.Redis +{ + internal abstract partial class ResultProcessor + { + public static readonly ResultProcessor + Boolean = new BooleanProcessor(), + DemandOK = new ExpectBasicStringProcessor(CommonReplies.OK), + DemandPONG = new ExpectBasicStringProcessor(CommonReplies.PONG), + DemandZeroOrOne = new DemandZeroOrOneProcessor(), + AutoConfigure = new AutoConfigureProcessor(), + TrackSubscriptions = new TrackSubscriptionsProcessor(null), + Tracer = new TracerProcessor(false), + EstablishConnection = new TracerProcessor(true), + BackgroundSaveStarted = new ExpectBasicStringProcessor(CommonReplies.backgroundSavingStarted_trimmed, startsWith: true), + BackgroundSaveAOFStarted = new ExpectBasicStringProcessor(CommonReplies.backgroundSavingAOFStarted_trimmed, startsWith: true); + + public static readonly ResultProcessor + ByteArray = new ByteArrayProcessor(); + + public static readonly ResultProcessor + ScriptLoad = new ScriptLoadProcessor(); + + public static readonly ResultProcessor + ClusterNodes = new ClusterNodesProcessor(); + + public static readonly ResultProcessor + ConnectionIdentity = new ConnectionIdentityProcessor(); + + public static readonly ResultProcessor + DateTime = new DateTimeProcessor(); + + public static readonly ResultProcessor + NullableDateTimeFromMilliseconds = new NullableDateTimeProcessor(fromMilliseconds: true), + NullableDateTimeFromSeconds = new NullableDateTimeProcessor(fromMilliseconds: false); + + public static readonly ResultProcessor + Double = new DoubleProcessor(); + public static readonly ResultProcessor>[]> + Info = new InfoProcessor(); + + public static readonly MultiStreamProcessor + MultiStream = new MultiStreamProcessor(); + + public static readonly ResultProcessor + Int64 = new Int64Processor(), + PubSubNumSub = new PubSubNumSubProcessor(), + Int64DefaultNegativeOne = new Int64DefaultValueProcessor(-1); + + public static readonly ResultProcessor Int32 = new Int32Processor(); + + public static readonly ResultProcessor + NullableDouble = new NullableDoubleProcessor(); + + public static readonly ResultProcessor + NullableDoubleArray = new NullableDoubleArrayProcessor(); + + public static readonly ResultProcessor + NullableInt64 = new NullableInt64Processor(); + + public static readonly ResultProcessor ExpireResultArray = new ExpireResultArrayProcessor(); + + public static readonly ResultProcessor PersistResultArray = new PersistResultArrayProcessor(); + + public static readonly ResultProcessor + RedisChannelArrayLiteral = new RedisChannelArrayProcessor(RedisChannel.RedisChannelOptions.None); + + public static readonly ResultProcessor + RedisKey = new RedisKeyProcessor(); + + public static readonly ResultProcessor + RedisKeyArray = new RedisKeyArrayProcessor(); + + public static readonly ResultProcessor + RedisType = new RedisTypeProcessor(); + + public static readonly ResultProcessor + RedisValue = new RedisValueProcessor(); + + public static readonly ResultProcessor + RedisValueFromArray = new RedisValueFromArrayProcessor(); + + public static readonly ResultProcessor + RedisValueArray = new RedisValueArrayProcessor(); + + public static readonly ResultProcessor + Int64Array = new Int64ArrayProcessor(); + + public static readonly ResultProcessor + NullableStringArray = new NullableStringArrayProcessor(); + + public static readonly ResultProcessor + StringArray = new StringArrayProcessor(); + + public static readonly ResultProcessor + BooleanArray = new BooleanArrayProcessor(); + + public static readonly ResultProcessor + RedisGeoPositionArray = new RedisValueGeoPositionArrayProcessor(); + public static readonly ResultProcessor + RedisGeoPosition = new RedisValueGeoPositionProcessor(); + + public static readonly ResultProcessor + ResponseTimer = new TimingProcessor(); + + public static readonly ResultProcessor + Role = new RoleProcessor(); + + public static readonly ResultProcessor + ScriptResult = new ScriptResultProcessor(); + + public static readonly SortedSetEntryProcessor + SortedSetEntry = new SortedSetEntryProcessor(); + public static readonly SortedSetEntryArrayProcessor + SortedSetWithScores = new SortedSetEntryArrayProcessor(); + + public static readonly SortedSetPopResultProcessor + SortedSetPopResult = new SortedSetPopResultProcessor(); + + public static readonly ListPopResultProcessor + ListPopResult = new ListPopResultProcessor(); + + public static readonly SingleStreamProcessor + SingleStream = new SingleStreamProcessor(); + + public static readonly SingleStreamProcessor + SingleStreamWithNameSkip = new SingleStreamProcessor(skipStreamName: true); + + public static readonly StreamAutoClaimProcessor + StreamAutoClaim = new StreamAutoClaimProcessor(); + + public static readonly StreamAutoClaimIdsOnlyProcessor + StreamAutoClaimIdsOnly = new StreamAutoClaimIdsOnlyProcessor(); + + public static readonly StreamConsumerInfoProcessor + StreamConsumerInfo = new StreamConsumerInfoProcessor(); + + public static readonly StreamGroupInfoProcessor + StreamGroupInfo = new StreamGroupInfoProcessor(); + + public static readonly StreamInfoProcessor + StreamInfo = new StreamInfoProcessor(); + + public static readonly StreamPendingInfoProcessor + StreamPendingInfo = new StreamPendingInfoProcessor(); + + public static readonly StreamPendingMessagesProcessor + StreamPendingMessages = new StreamPendingMessagesProcessor(); + + public static ResultProcessor GeoRadiusArray(GeoRadiusOptions options) => GeoRadiusResultArrayProcessor.Get(options); + + public static readonly ResultProcessor + LCSMatchResult = new LongestCommonSubsequenceProcessor(); + + public static readonly ResultProcessor + String = new StringProcessor(), + TieBreaker = new TieBreakerProcessor(), + ClusterNodesRaw = new ClusterNodesRawProcessor(); + + public static readonly ResultProcessor + SentinelPrimaryEndpoint = new SentinelGetPrimaryAddressByNameProcessor(); + + public static readonly ResultProcessor + SentinelAddressesEndPoints = new SentinelGetSentinelAddressesProcessor(); + + public static readonly ResultProcessor + SentinelReplicaEndPoints = new SentinelGetReplicaAddressesProcessor(); + + public static readonly ResultProcessor[][]> + SentinelArrayOfArrays = new SentinelArrayOfArraysProcessor(); + + public static readonly ResultProcessor[]> + StringPairInterleaved = new StringPairInterleavedProcessor(); + public static readonly TimeSpanProcessor + TimeSpanFromMilliseconds = new TimeSpanProcessor(true), + TimeSpanFromSeconds = new TimeSpanProcessor(false); + public static readonly HashEntryArrayProcessor + HashEntryArray = new HashEntryArrayProcessor(); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Conditionally run on instance")] + public void ConnectionFail(Message message, ConnectionFailureType fail, Exception? innerException, string? annotation, ConnectionMultiplexer? muxer) + { + PhysicalConnection.IdentifyFailureType(innerException, ref fail); + + var sb = new StringBuilder(fail.ToString()); + if (message is not null) + { + sb.Append(" on "); + sb.Append(muxer?.RawConfig.IncludeDetailInExceptions == true ? message.ToString() : message.ToStringCommandOnly()); + } + if (!string.IsNullOrWhiteSpace(annotation)) + { + sb.Append(", "); + sb.Append(annotation); + } + var ex = new RedisConnectionException(fail, sb.ToString(), innerException); + SetException(message, ex); + } + + public static void ConnectionFail(Message message, ConnectionFailureType fail, string errorMessage) => + SetException(message, new RedisConnectionException(fail, errorMessage)); + + public static void ServerFail(Message message, string errorMessage) => + SetException(message, new RedisServerException(errorMessage)); + + public static void SetException(Message? message, Exception ex) + { + var box = message?.ResultBox; + box?.SetException(ex); + } + // true if ready to be completed (i.e. false if re-issued to another server) + public virtual bool SetResult(PhysicalConnection connection, Message message, in RawResult result) + { + var bridge = connection.BridgeCouldBeNull; + if (message is LoggingMessage logging) + { + try + { + logging.Log?.LogInformationResponse(bridge?.Name, message.CommandAndKey, result); + } + catch { } + } + if (result.IsError) + { + if (result.StartsWith(CommonReplies.NOAUTH)) + { + bridge?.Multiplexer.SetAuthSuspect(new RedisServerException("NOAUTH Returned - connection has not yet authenticated")); + } + else if (result.StartsWith(CommonReplies.WRONGPASS)) + { + bridge?.Multiplexer.SetAuthSuspect(new RedisServerException(result.ToString())); + } + + var server = bridge?.ServerEndPoint; + bool log = !message.IsInternalCall; + bool isMoved = result.StartsWith(CommonReplies.MOVED); + bool wasNoRedirect = (message.Flags & CommandFlags.NoRedirect) != 0; + string? err = string.Empty; + bool unableToConnectError = false; + if (isMoved || result.StartsWith(CommonReplies.ASK)) + { + message.SetResponseReceived(); + + log = false; + string[] parts = result.GetString()!.Split(StringSplits.Space, 3); + if (Format.TryParseInt32(parts[1], out int hashSlot) + && Format.TryParseEndPoint(parts[2], out var endpoint)) + { + // no point sending back to same server, and no point sending to a dead server + if (!Equals(server?.EndPoint, endpoint)) + { + if (bridge is null) + { + // already toast + } + else if (bridge.Multiplexer.TryResend(hashSlot, message, endpoint, isMoved)) + { + bridge.Multiplexer.Trace(message.Command + " re-issued to " + endpoint, isMoved ? "MOVED" : "ASK"); + return false; + } + else + { + if (isMoved && wasNoRedirect) + { + if (bridge.Multiplexer.RawConfig.IncludeDetailInExceptions) + { + err = $"Key has MOVED to Endpoint {endpoint} and hashslot {hashSlot} but CommandFlags.NoRedirect was specified - redirect not followed for {message.CommandAndKey}. "; + } + else + { + err = "Key has MOVED but CommandFlags.NoRedirect was specified - redirect not followed. "; + } + } + else + { + unableToConnectError = true; + if (bridge.Multiplexer.RawConfig.IncludeDetailInExceptions) + { + err = $"Endpoint {endpoint} serving hashslot {hashSlot} is not reachable at this point of time. Please check connectTimeout value. If it is low, try increasing it to give the ConnectionMultiplexer a chance to recover from the network disconnect. " + + PerfCounterHelper.GetThreadPoolAndCPUSummary(); + } + else + { + err = "Endpoint is not reachable at this point of time. Please check connectTimeout value. If it is low, try increasing it to give the ConnectionMultiplexer a chance to recover from the network disconnect. "; + } + } + } + } + } + } + + if (string.IsNullOrWhiteSpace(err)) + { + err = result.GetString()!; + } + + if (log && server != null) + { + bridge?.Multiplexer.OnErrorMessage(server.EndPoint, err); + } + bridge?.Multiplexer.Trace("Completed with error: " + err + " (" + GetType().Name + ")", ToString()); + if (unableToConnectError) + { + ConnectionFail(message, ConnectionFailureType.UnableToConnect, err); + } + else + { + ServerFail(message, err); + } + } + else + { + bool coreResult = SetResultCore(connection, message, result); + if (coreResult) + { + bridge?.Multiplexer.Trace("Completed with success: " + result.ToString() + " (" + GetType().Name + ")", ToString()); + } + else + { + UnexpectedResponse(message, result); + } + } + return true; + } + + protected abstract bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result); + + private void UnexpectedResponse(Message message, in RawResult result) + { + ConnectionMultiplexer.TraceWithoutContext("From " + GetType().Name, "Unexpected Response"); + ConnectionFail(message, ConnectionFailureType.ProtocolFailure, "Unexpected response to " + (message?.CommandString ?? "n/a") + ": " + result.ToString()); + } + + public sealed class TimeSpanProcessor : ResultProcessor + { + private readonly bool isMilliseconds; + public TimeSpanProcessor(bool isMilliseconds) + { + this.isMilliseconds = isMilliseconds; + } + + public bool TryParse(in RawResult result, out TimeSpan? expiry) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + if (result.TryGetInt64(out long time)) + { + if (time < 0) + { + expiry = null; + } + else if (isMilliseconds) + { + expiry = TimeSpan.FromMilliseconds(time); + } + else + { + expiry = TimeSpan.FromSeconds(time); + } + return true; + } + break; + // e.g. OBJECT IDLETIME on a key that doesn't exist + case ResultType.BulkString when result.IsNull: + expiry = null; + return true; + } + expiry = null; + return false; + } + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (TryParse(result, out TimeSpan? expiry)) + { + SetResult(message, expiry); + return true; + } + return false; + } + } + + public sealed class TimingProcessor : ResultProcessor + { + private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; + + public static TimerMessage CreateMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value = default) => + new TimerMessage(db, flags, command, value); + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.IsError) + { + return false; + } + else + { + // don't check the actual reply; there are multiple ways of constructing + // a timing message, and we don't actually care about what approach was used + TimeSpan duration; + if (message is TimerMessage timingMessage) + { + var timestampDelta = Stopwatch.GetTimestamp() - timingMessage.StartedWritingTimestamp; + var ticks = (long)(TimestampToTicks * timestampDelta); + duration = new TimeSpan(ticks); + } + else + { + duration = TimeSpan.MaxValue; + } + SetResult(message, duration); + return true; + } + } + + internal sealed class TimerMessage : Message + { + public long StartedWritingTimestamp; + private readonly RedisValue value; + public TimerMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value) + : base(db, flags, command) + { + this.value = value; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + StartedWritingTimestamp = Stopwatch.GetTimestamp(); + if (value.IsNull) + { + physical.WriteHeader(command, 0); + } + else + { + physical.WriteHeader(command, 1); + physical.WriteBulkString(value); + } + } + public override int ArgCount => value.IsNull ? 0 : 1; + } + } + + public sealed class TrackSubscriptionsProcessor : ResultProcessor + { + private ConnectionMultiplexer.Subscription? Subscription { get; } + public TrackSubscriptionsProcessor(ConnectionMultiplexer.Subscription? sub) => Subscription = sub; + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray == ResultType.Array) + { + var items = result.GetItems(); + if (items.Length >= 3 && items[2].TryGetInt64(out long count)) + { + connection.SubscriptionCount = count; + SetResult(message, true); + + var newServer = message.Command switch + { + RedisCommand.SUBSCRIBE or RedisCommand.SSUBSCRIBE or RedisCommand.PSUBSCRIBE => connection.BridgeCouldBeNull?.ServerEndPoint, + _ => null, + }; + Subscription?.SetCurrentServer(newServer); + return true; + } + } + SetResult(message, false); + return false; + } + } + + internal sealed class DemandZeroOrOneProcessor : ResultProcessor + { + public static bool TryGet(in RawResult result, out bool value) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + if (result.IsEqual(CommonReplies.one)) + { + value = true; + return true; + } + else if (result.IsEqual(CommonReplies.zero)) + { + value = false; + return true; + } + break; + } + value = false; + return false; + } + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (TryGet(result, out bool value)) + { + SetResult(message, value); + return true; + } + return false; + } + } + + internal sealed class ScriptLoadProcessor : ResultProcessor + { + /// + /// Anything hashed with SHA1 has exactly 40 characters. We can use that as a shortcut in the code bellow. + /// + private const int SHA1Length = 40; + + private static readonly Regex sha1 = new Regex("^[0-9a-f]{40}$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + internal static bool IsSHA1(string? script) => script is not null && script.Length == SHA1Length && sha1.IsMatch(script); + + internal const int Sha1HashLength = 20; + internal static byte[] ParseSHA1(byte[] value) + { + static int FromHex(char c) + { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; + } + + if (value?.Length == Sha1HashLength * 2) + { + var tmp = new byte[Sha1HashLength]; + int charIndex = 0; + for (int i = 0; i < tmp.Length; i++) + { + int x = FromHex((char)value[charIndex++]), y = FromHex((char)value[charIndex++]); + if (x < 0 || y < 0) + { + throw new ArgumentException("Unable to parse response as SHA1", nameof(value)); + } + tmp[i] = (byte)((x << 4) | y); + } + return tmp; + } + throw new ArgumentException("Unable to parse response as SHA1", nameof(value)); + } + + // note that top-level error messages still get handled by SetResult, but nested errors + // (is that a thing?) will be wrapped in the RedisResult + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.BulkString: + var asciiHash = result.GetBlob(); + if (asciiHash == null || asciiHash.Length != (Sha1HashLength * 2)) return false; + + // External caller wants the hex bytes, not the ASCII bytes + // For nullability/consistency reasons, we always do the parse here. + byte[] hash = ParseSHA1(asciiHash); + + if (message is RedisDatabase.ScriptLoadMessage sl) + { + connection.BridgeCouldBeNull?.ServerEndPoint?.AddScript(sl.Script, asciiHash); + } + SetResult(message, hash); + return true; + } + return false; + } + } + + internal sealed class SortedSetEntryProcessor : ResultProcessor + { + public static bool TryParse(in RawResult result, out SortedSetEntry? entry) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + if (result.IsNull || result.ItemsCount < 2) + { + entry = null; + } + else + { + var arr = result.GetItems(); + entry = new SortedSetEntry(arr[0].AsRedisValue(), arr[1].TryGetDouble(out double val) ? val : double.NaN); + } + return true; + default: + entry = null; + return false; + } + } + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (TryParse(result, out SortedSetEntry? entry)) + { + SetResult(message, entry); + return true; + } + return false; + } + } + + internal sealed class SortedSetEntryArrayProcessor : ValuePairInterleavedProcessorBase + { + protected override SortedSetEntry Parse(in RawResult first, in RawResult second, object? state) => + new SortedSetEntry(first.AsRedisValue(), second.TryGetDouble(out double val) ? val : double.NaN); + } + + internal sealed class SortedSetPopResultProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray == ResultType.Array) + { + if (result.IsNull) + { + SetResult(message, Redis.SortedSetPopResult.Null); + return true; + } + + var arr = result.GetItems(); + SetResult(message, new SortedSetPopResult(arr[0].AsRedisKey(), arr[1].GetItemsAsSortedSetEntryArray()!)); + return true; + } + + return false; + } + } + + internal sealed class ListPopResultProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray == ResultType.Array) + { + if (result.IsNull) + { + SetResult(message, Redis.ListPopResult.Null); + return true; + } + + var arr = result.GetItems(); + SetResult(message, new ListPopResult(arr[0].AsRedisKey(), arr[1].GetItemsAsValues()!)); + return true; + } + + return false; + } + } + + internal sealed class HashEntryArrayProcessor : ValuePairInterleavedProcessorBase + { + protected override HashEntry Parse(in RawResult first, in RawResult second, object? state) => + new HashEntry(first.AsRedisValue(), second.AsRedisValue()); + } + + internal abstract class ValuePairInterleavedProcessorBase : ResultProcessor + { + // when RESP3 was added, some interleaved value/pair responses: became jagged instead; + // this isn't strictly a RESP3 thing (RESP2 supports jagged), but: it is a thing that + // happened, and we need to handle that; thus, by default, we'll detect jagged data + // and handle it automatically; this virtual is included so we can turn it off + // on a per-processor basis if needed + protected virtual bool AllowJaggedPairs => true; + + public bool TryParse(in RawResult result, out T[]? pairs) + => TryParse(result, out pairs, false, out _); + + public T[]? ParseArray(in RawResult result, bool allowOversized, out int count, object? state) + { + if (result.IsNull) + { + count = 0; + return null; + } + + var arr = result.GetItems(); + count = (int)arr.Length; + if (count == 0) + { + return []; + } + + bool interleaved = !(result.IsResp3 && AllowJaggedPairs && IsAllJaggedPairs(arr)); + if (interleaved) count >>= 1; // so: half of that + var pairs = allowOversized ? ArrayPool.Shared.Rent(count) : new T[count]; + + if (interleaved) + { + // linear elements i.e. {key,value,key,value,key,value} + if (arr.IsSingleSegment) + { + var span = arr.FirstSpan; + int offset = 0; + for (int i = 0; i < count; i++) + { + pairs[i] = Parse(span[offset++], span[offset++], state); + } + } + else + { + var iter = arr.GetEnumerator(); // simplest way of getting successive values + for (int i = 0; i < count; i++) + { + pairs[i] = Parse(iter.GetNext(), iter.GetNext(), state); + } + } + } + else + { + // jagged elements i.e. {{key,value},{key,value},{key,value}} + // to get here, we've already asserted that all elements are arrays with length 2 + if (arr.IsSingleSegment) + { + int i = 0; + foreach (var el in arr.FirstSpan) + { + var inner = el.GetItems(); + pairs[i++] = Parse(inner[0], inner[1], state); + } + } + else + { + var iter = arr.GetEnumerator(); // simplest way of getting successive values + for (int i = 0; i < count; i++) + { + var inner = iter.GetNext().GetItems(); + pairs[i] = Parse(inner[0], inner[1], state); + } + } + } + return pairs; + + static bool IsAllJaggedPairs(in Sequence arr) + { + return arr.IsSingleSegment ? CheckSpan(arr.FirstSpan) : CheckSpans(arr); + + static bool CheckSpans(in Sequence arr) + { + foreach (var chunk in arr.Spans) + { + if (!CheckSpan(chunk)) return false; + } + return true; + } + static bool CheckSpan(ReadOnlySpan chunk) + { + // check whether each value is actually an array of length 2 + foreach (ref readonly RawResult el in chunk) + { + if (el is not { Resp2TypeArray: ResultType.Array, ItemsCount: 2 }) return false; + } + return true; + } + } + } + + public bool TryParse(in RawResult result, out T[]? pairs, bool allowOversized, out int count) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + pairs = ParseArray(in result, allowOversized, out count, null); + return true; + default: + count = 0; + pairs = null; + return false; + } + } + + protected abstract T Parse(in RawResult first, in RawResult second, object? state); + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (TryParse(result, out T[]? arr)) + { + SetResult(message, arr!); + return true; + } + return false; + } + } + + internal sealed class AutoConfigureProcessor : ResultProcessor + { + private ILogger? Log { get; } + public AutoConfigureProcessor(ILogger? log = null) => Log = log; + + public override bool SetResult(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.IsError && result.StartsWith(CommonReplies.READONLY)) + { + var bridge = connection.BridgeCouldBeNull; + if (bridge != null) + { + var server = bridge.ServerEndPoint; + Log?.LogInformationAutoConfiguredRoleReplica(new(server)); + server.IsReplica = true; + } + } + + return base.SetResult(connection, message, result); + } + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + var server = connection.BridgeCouldBeNull?.ServerEndPoint; + if (server == null) return false; + + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + if (message?.Command == RedisCommand.CLIENT) + { + if (result.TryGetInt64(out long clientId)) + { + connection.ConnectionId = clientId; + Log?.LogInformationAutoConfiguredClientConnectionId(new(server), clientId); + + SetResult(message, true); + return true; + } + } + break; + case ResultType.BulkString: + if (message?.Command == RedisCommand.INFO) + { + string? info = result.GetString(); + if (string.IsNullOrWhiteSpace(info)) + { + SetResult(message, true); + return true; + } + string? primaryHost = null, primaryPort = null; + bool roleSeen = false; + using (var reader = new StringReader(info)) + { + while (reader.ReadLine() is string line) + { + if (string.IsNullOrWhiteSpace(line) || line.StartsWith("# ")) + { + continue; + } + + string? val; + if ((val = Extract(line, "role:")) != null) + { + roleSeen = true; + if (TryParseRole(val, out bool isReplica)) + { + server.IsReplica = isReplica; + Log?.LogInformationAutoConfiguredInfoRole(new(server), isReplica ? "replica" : "primary"); + } + } + else if ((val = Extract(line, "master_host:")) != null) + { + primaryHost = val; + } + else if ((val = Extract(line, "master_port:")) != null) + { + primaryPort = val; + } + else if ((val = Extract(line, "redis_version:")) != null) + { + if (Format.TryParseVersion(val, out Version? version)) + { + server.Version = version; + Log?.LogInformationAutoConfiguredInfoVersion(new(server), version); + } + } + else if ((val = Extract(line, "redis_mode:")) != null) + { + if (TryParseServerType(val, out var serverType)) + { + server.ServerType = serverType; + Log?.LogInformationAutoConfiguredInfoServerType(new(server), serverType); + } + } + else if ((val = Extract(line, "run_id:")) != null) + { + server.RunId = val; + } + } + if (roleSeen && Format.TryParseEndPoint(primaryHost!, primaryPort, out var sep)) + { + // These are in the same section, if present + server.PrimaryEndPoint = sep; + } + } + } + else if (message?.Command == RedisCommand.SENTINEL) + { + server.ServerType = ServerType.Sentinel; + Log?.LogInformationAutoConfiguredSentinelServerType(new(server)); + } + SetResult(message, true); + return true; + case ResultType.Array: + if (message?.Command == RedisCommand.CONFIG) + { + var iter = result.GetItems().GetEnumerator(); + while (iter.MoveNext()) + { + ref RawResult key = ref iter.Current; + if (!iter.MoveNext()) break; + ref RawResult val = ref iter.Current; + + if (key.IsEqual(CommonReplies.timeout) && val.TryGetInt64(out long i64)) + { + // note the configuration is in seconds + int timeoutSeconds = checked((int)i64), targetSeconds; + if (timeoutSeconds > 0) + { + if (timeoutSeconds >= 60) + { + targetSeconds = timeoutSeconds - 20; // time to spare... + } + else + { + targetSeconds = (timeoutSeconds * 3) / 4; + } + Log?.LogInformationAutoConfiguredConfigTimeout(new(server), targetSeconds); + server.WriteEverySeconds = targetSeconds; + } + } + else if (key.IsEqual(CommonReplies.databases) && val.TryGetInt64(out i64)) + { + int dbCount = checked((int)i64); + Log?.LogInformationAutoConfiguredConfigDatabases(new(server), dbCount); + server.Databases = dbCount; + if (dbCount > 1) + { + connection.MultiDatabasesOverride = true; + } + } + else if (key.IsEqual(CommonReplies.slave_read_only) || key.IsEqual(CommonReplies.replica_read_only)) + { + if (val.IsEqual(CommonReplies.yes)) + { + server.ReplicaReadOnly = true; + Log?.LogInformationAutoConfiguredConfigReadOnlyReplica(new(server), true); + } + else if (val.IsEqual(CommonReplies.no)) + { + server.ReplicaReadOnly = false; + Log?.LogInformationAutoConfiguredConfigReadOnlyReplica(new(server), false); + } + } + } + } + else if (message?.Command == RedisCommand.HELLO) + { + var iter = result.GetItems().GetEnumerator(); + while (iter.MoveNext()) + { + ref RawResult key = ref iter.Current; + if (!iter.MoveNext()) break; + ref RawResult val = ref iter.Current; + + if (key.IsEqual(CommonReplies.version) && Format.TryParseVersion(val.GetString(), out var version)) + { + server.Version = version; + Log?.LogInformationAutoConfiguredHelloServerVersion(new(server), version); + } + else if (key.IsEqual(CommonReplies.proto) && val.TryGetInt64(out var i64)) + { + connection.SetProtocol(i64 >= 3 ? RedisProtocol.Resp3 : RedisProtocol.Resp2); + Log?.LogInformationAutoConfiguredHelloProtocol(new(server), connection.Protocol ?? RedisProtocol.Resp2); + } + else if (key.IsEqual(CommonReplies.id) && val.TryGetInt64(out i64)) + { + connection.ConnectionId = i64; + Log?.LogInformationAutoConfiguredHelloConnectionId(new(server), i64); + } + else if (key.IsEqual(CommonReplies.mode) && TryParseServerType(val.GetString(), out var serverType)) + { + server.ServerType = serverType; + Log?.LogInformationAutoConfiguredHelloServerType(new(server), serverType); + } + else if (key.IsEqual(CommonReplies.role) && TryParseRole(val.GetString(), out bool isReplica)) + { + server.IsReplica = isReplica; + Log?.LogInformationAutoConfiguredHelloRole(new(server), isReplica ? "replica" : "primary"); + } + } + } + else if (message?.Command == RedisCommand.SENTINEL) + { + server.ServerType = ServerType.Sentinel; + Log?.LogInformationAutoConfiguredSentinelServerType(new(server)); + } + SetResult(message, true); + return true; + } + return false; + } + + private static string? Extract(string line, string prefix) + { + if (line.StartsWith(prefix)) return line.Substring(prefix.Length).Trim(); + return null; + } + + private static bool TryParseServerType(string? val, out ServerType serverType) + { + switch (val) + { + case "standalone": + serverType = ServerType.Standalone; + return true; + case "cluster": + serverType = ServerType.Cluster; + return true; + case "sentinel": + serverType = ServerType.Sentinel; + return true; + default: + serverType = default; + return false; + } + } + + private static bool TryParseRole(string? val, out bool isReplica) + { + switch (val) + { + case "primary": + case "master": + isReplica = false; + return true; + case "replica": + case "slave": + isReplica = true; + return true; + default: + isReplica = default; + return false; + } + } + + internal static ResultProcessor Create(ILogger? log) => log is null ? AutoConfigure : new AutoConfigureProcessor(log); + } + + private sealed class BooleanProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.IsNull) + { + SetResult(message, false); // lots of ops return (nil) when they mean "no" + return true; + } + switch (result.Resp2TypeBulkString) + { + case ResultType.SimpleString: + if (result.IsEqual(CommonReplies.OK)) + { + SetResult(message, true); + } + else + { + SetResult(message, result.GetBoolean()); + } + return true; + case ResultType.Integer: + case ResultType.BulkString: + SetResult(message, result.GetBoolean()); + return true; + case ResultType.Array: + var items = result.GetItems(); + if (items.Length == 1) + { // treat an array of 1 like a single reply (for example, SCRIPT EXISTS) + SetResult(message, items[0].GetBoolean()); + return true; + } + break; + } + return false; + } + } + + private sealed class ByteArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.BulkString: + SetResult(message, result.GetBlob()); + return true; + } + return false; + } + } + + private sealed class ClusterNodesProcessor : ResultProcessor + { + internal static ClusterConfiguration Parse(PhysicalConnection connection, string nodes) + { + var bridge = connection.BridgeCouldBeNull ?? throw new ObjectDisposedException(connection.ToString()); + var server = bridge.ServerEndPoint; + var config = new ClusterConfiguration(bridge.Multiplexer.ServerSelectionStrategy, nodes, server.EndPoint); + server.SetClusterConfiguration(config); + return config; + } + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.BulkString: + string nodes = result.GetString()!; + var bridge = connection.BridgeCouldBeNull; + var config = Parse(connection, nodes); + + // re multi-db: https://github.com/StackExchange/StackExchange.Redis/issues/2642 + if (bridge != null && !connection.MultiDatabasesOverride) + { + bridge.ServerEndPoint.ServerType = ServerType.Cluster; + } + SetResult(message, config); + return true; + } + return false; + } + } + + private sealed class ClusterNodesRawProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + string nodes = result.GetString()!; + try + { + ClusterNodesProcessor.Parse(connection, nodes); + } + catch + { + /* tralalalala */ + } + SetResult(message, nodes); + return true; + } + return false; + } + } + + private sealed class ConnectionIdentityProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (connection.BridgeCouldBeNull is PhysicalBridge bridge) + { + SetResult(message, bridge.ServerEndPoint.EndPoint); + return true; + } + return false; + } + } + + private sealed class DateTimeProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + long unixTime; + switch (result.Resp2TypeArray) + { + case ResultType.Integer: + if (result.TryGetInt64(out unixTime)) + { + var time = RedisBase.UnixEpoch.AddSeconds(unixTime); + SetResult(message, time); + return true; + } + break; + case ResultType.Array: + var arr = result.GetItems(); + switch (arr.Length) + { + case 1: + if (arr.FirstSpan[0].TryGetInt64(out unixTime)) + { + var time = RedisBase.UnixEpoch.AddSeconds(unixTime); + SetResult(message, time); + return true; + } + break; + case 2: + if (arr[0].TryGetInt64(out unixTime) && arr[1].TryGetInt64(out long micros)) + { + var time = RedisBase.UnixEpoch.AddSeconds(unixTime).AddTicks(micros * 10); // DateTime ticks are 100ns + SetResult(message, time); + return true; + } + break; + } + break; + } + return false; + } + } + + public sealed class NullableDateTimeProcessor : ResultProcessor + { + private readonly bool isMilliseconds; + public NullableDateTimeProcessor(bool fromMilliseconds) => isMilliseconds = fromMilliseconds; + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer when result.TryGetInt64(out var duration): + DateTime? expiry = duration switch + { + // -1 means no expiry and -2 means key does not exist + < 0 => null, + _ when isMilliseconds => RedisBase.UnixEpoch.AddMilliseconds(duration), + _ => RedisBase.UnixEpoch.AddSeconds(duration), + }; + SetResult(message, expiry); + return true; + + case ResultType.BulkString when result.IsNull: + SetResult(message, null); + return true; + } + return false; + } + } + + private sealed class DoubleProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + if (result.TryGetInt64(out long i64)) + { + SetResult(message, i64); + return true; + } + break; + case ResultType.SimpleString: + case ResultType.BulkString: + if (result.TryGetDouble(out double val)) + { + SetResult(message, val); + return true; + } + break; + } + return false; + } + } + + private sealed class ExpectBasicStringProcessor : ResultProcessor + { + private readonly CommandBytes _expected; + private readonly bool _startsWith; + public ExpectBasicStringProcessor(CommandBytes expected, bool startsWith = false) + { + _expected = expected; + _startsWith = startsWith; + } + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (_startsWith ? result.StartsWith(_expected) : result.IsEqual(_expected)) + { + SetResult(message, true); + return true; + } + if (message.Command == RedisCommand.AUTH) connection?.BridgeCouldBeNull?.Multiplexer?.SetAuthSuspect(new RedisException("Unknown AUTH exception")); + return false; + } + } + + private sealed class InfoProcessor : ResultProcessor>[]> + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeBulkString == ResultType.BulkString) + { + string category = Normalize(null); + var list = new List>>(); + var raw = result.GetString(); + if (raw is not null) + { + using var reader = new StringReader(raw); + while (reader.ReadLine() is string line) + { + if (string.IsNullOrWhiteSpace(line)) continue; + if (line.StartsWith("# ")) + { + category = Normalize(line.Substring(2)); + continue; + } + int idx = line.IndexOf(':'); + if (idx < 0) continue; + var pair = new KeyValuePair( + line.Substring(0, idx).Trim(), + line.Substring(idx + 1).Trim()); + list.Add(Tuple.Create(category, pair)); + } + } + var final = list.GroupBy(x => x.Item1, x => x.Item2).ToArray(); + SetResult(message, final); + return true; + } + return false; + } + + private static string Normalize(string? category) => + category.IsNullOrWhiteSpace() ? "miscellaneous" : category.Trim(); + } + + private sealed class Int64DefaultValueProcessor : ResultProcessor + { + private readonly long _defaultValue; + + public Int64DefaultValueProcessor(long defaultValue) => _defaultValue = defaultValue; + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.IsNull) + { + SetResult(message, _defaultValue); + return true; + } + if (result.Resp2TypeBulkString == ResultType.Integer && result.TryGetInt64(out var i64)) + { + SetResult(message, i64); + return true; + } + return false; + } + } + + private class Int64Processor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + if (result.TryGetInt64(out long i64)) + { + SetResult(message, i64); + return true; + } + break; + } + return false; + } + } + + private class Int32Processor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + if (result.TryGetInt64(out long i64)) + { + SetResult(message, checked((int)i64)); + return true; + } + break; + } + return false; + } + } + + internal static ResultProcessor StreamTrimResult => + Int32EnumProcessor.Instance; + + internal static ResultProcessor StreamTrimResultArray => + Int32EnumArrayProcessor.Instance; + + private sealed class Int32EnumProcessor : ResultProcessor where T : unmanaged, Enum + { + private Int32EnumProcessor() { } + public static readonly Int32EnumProcessor Instance = new(); + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + if (result.TryGetInt64(out long i64)) + { + Debug.Assert(Unsafe.SizeOf() == sizeof(int)); + int i32 = (int)i64; + SetResult(message, Unsafe.As(ref i32)); + return true; + } + break; + case ResultType.Array when result.ItemsCount == 1: // pick a single element from a unit vector + if (result.GetItems()[0].TryGetInt64(out i64)) + { + Debug.Assert(Unsafe.SizeOf() == sizeof(int)); + int i32 = (int)i64; + SetResult(message, Unsafe.As(ref i32)); + return true; + } + break; + } + return false; + } + } + + private sealed class Int32EnumArrayProcessor : ResultProcessor where T : unmanaged, Enum + { + private Int32EnumArrayProcessor() { } + public static readonly Int32EnumArrayProcessor Instance = new(); + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + T[] arr; + if (result.IsNull) + { + arr = null!; + } + else + { + Debug.Assert(Unsafe.SizeOf() == sizeof(int)); + arr = result.ToArray(static (in RawResult x) => + { + int i32 = (int)x.AsRedisValue(); + return Unsafe.As(ref i32); + })!; + } + SetResult(message, arr); + return true; + } + return false; + } + } + + private sealed class PubSubNumSubProcessor : Int64Processor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray == ResultType.Array) + { + var arr = result.GetItems(); + if (arr.Length == 2 && arr[1].TryGetInt64(out long val)) + { + SetResult(message, val); + return true; + } + } + return base.SetResultCore(connection, message, result); + } + } + + private sealed class NullableDoubleArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray == ResultType.Array && !result.IsNull) + { + var arr = result.GetItemsAsDoubles()!; + SetResult(message, arr); + return true; + } + return false; + } + } + + private sealed class NullableDoubleProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + if (result.IsNull) + { + SetResult(message, null); + return true; + } + if (result.TryGetDouble(out double val)) + { + SetResult(message, val); + return true; + } + break; + } + return false; + } + } + + private sealed class NullableInt64Processor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + if (result.IsNull) + { + SetResult(message, null); + return true; + } + if (result.TryGetInt64(out long i64)) + { + SetResult(message, i64); + return true; + } + break; + case ResultType.Array: + var items = result.GetItems(); + if (items.Length == 1) + { // treat an array of 1 like a single reply + if (items[0].TryGetInt64(out long value)) + { + SetResult(message, value); + return true; + } + } + break; + } + return false; + } + } + + private sealed class ExpireResultArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray == ResultType.Array || result.IsNull) + { + var arr = result.ToArray((in RawResult x) => (ExpireResult)(long)x.AsRedisValue())!; + + SetResult(message, arr); + return true; + } + return false; + } + } + + private sealed class PersistResultArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray == ResultType.Array || result.IsNull) + { + var arr = result.ToArray((in RawResult x) => (PersistResult)(long)x.AsRedisValue())!; + + SetResult(message, arr); + return true; + } + return false; + } + } + + private sealed class RedisChannelArrayProcessor : ResultProcessor + { + private readonly RedisChannel.RedisChannelOptions options; + public RedisChannelArrayProcessor(RedisChannel.RedisChannelOptions options) + { + this.options = options; + } + + private readonly struct ChannelState // I would use a value-tuple here, but that is binding hell + { + public readonly byte[]? Prefix; + public readonly RedisChannel.RedisChannelOptions Options; + public ChannelState(byte[]? prefix, RedisChannel.RedisChannelOptions options) + { + Prefix = prefix; + Options = options; + } + } + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var final = result.ToArray( + (in RawResult item, in ChannelState state) => item.AsRedisChannel(state.Prefix, state.Options), + new ChannelState(connection.ChannelPrefix, options))!; + + SetResult(message, final); + return true; + } + return false; + } + } + + private sealed class RedisKeyArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var arr = result.GetItemsAsKeys()!; + SetResult(message, arr); + return true; + } + return false; + } + } + + private sealed class RedisKeyProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + SetResult(message, result.AsRedisKey()); + return true; + } + return false; + } + } + + private sealed class RedisTypeProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.SimpleString: + case ResultType.BulkString: + string s = result.GetString()!; + RedisType value; + if (string.Equals(s, "zset", StringComparison.OrdinalIgnoreCase)) value = Redis.RedisType.SortedSet; + else if (!Enum.TryParse(s, true, out value)) value = global::StackExchange.Redis.RedisType.Unknown; + SetResult(message, value); + return true; + } + return false; + } + } + + private sealed class RedisValueArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + // allow a single item to pass explicitly pretending to be an array; example: SPOP {key} 1 + case ResultType.BulkString: + // If the result is nil, the result should be an empty array + var arr = result.IsNull + ? Array.Empty() + : new[] { result.AsRedisValue() }; + SetResult(message, arr); + return true; + case ResultType.Array: + arr = result.GetItemsAsValues()!; + SetResult(message, arr); + return true; + } + return false; + } + } + + private sealed class Int64ArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray == ResultType.Array && !result.IsNull) + { + var arr = result.ToArray((in RawResult x) => (long)x.AsRedisValue())!; + SetResult(message, arr); + return true; + } + + return false; + } + } + + private sealed class NullableStringArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var arr = result.GetItemsAsStrings()!; + + SetResult(message, arr); + return true; + } + return false; + } + } + + private sealed class StringArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var arr = result.GetItemsAsStringsNotNullable()!; + SetResult(message, arr); + return true; + } + return false; + } + } + + private sealed class BooleanArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray == ResultType.Array && !result.IsNull) + { + var arr = result.GetItemsAsBooleans()!; + SetResult(message, arr); + return true; + } + return false; + } + } + + private sealed class RedisValueGeoPositionProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var pos = result.GetItemsAsGeoPosition(); + + SetResult(message, pos); + return true; + } + return false; + } + } + + private sealed class RedisValueGeoPositionArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var arr = result.GetItemsAsGeoPositionArray()!; + + SetResult(message, arr); + return true; + } + return false; + } + } + + private sealed class GeoRadiusResultArrayProcessor : ResultProcessor + { + private static readonly GeoRadiusResultArrayProcessor[] instances; + private readonly GeoRadiusOptions options; + + static GeoRadiusResultArrayProcessor() + { + instances = new GeoRadiusResultArrayProcessor[8]; + for (int i = 0; i < 8; i++) instances[i] = new GeoRadiusResultArrayProcessor((GeoRadiusOptions)i); + } + + public static GeoRadiusResultArrayProcessor Get(GeoRadiusOptions options) + { + int i = (int)options; + if (i < 0 || i >= instances.Length) throw new ArgumentOutOfRangeException(nameof(options)); + return instances[i]; + } + + private GeoRadiusResultArrayProcessor(GeoRadiusOptions options) + { + this.options = options; + } + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var typed = result.ToArray( + (in RawResult item, in GeoRadiusOptions radiusOptions) => Parse(item, radiusOptions), options)!; + SetResult(message, typed); + return true; + } + return false; + } + + private static GeoRadiusResult Parse(in RawResult item, GeoRadiusOptions options) + { + if (options == GeoRadiusOptions.None) + { + // Without any WITH option specified, the command just returns a linear array like ["New York","Milan","Paris"]. + return new GeoRadiusResult(item.AsRedisValue(), null, null, null); + } + // If WITHCOORD, WITHDIST or WITHHASH options are specified, the command returns an array of arrays, where each sub-array represents a single item. + var iter = item.GetItems().GetEnumerator(); + + // the first item in the sub-array is always the name of the returned item. + var member = iter.GetNext().AsRedisValue(); + + /* The other information is returned in the following order as successive elements of the sub-array. +The distance from the center as a floating point number, in the same unit specified in the radius. +The geohash integer. +The coordinates as a two items x,y array (longitude,latitude). + */ + double? distance = null; + GeoPosition? position = null; + long? hash = null; + if ((options & GeoRadiusOptions.WithDistance) != 0) { distance = (double?)iter.GetNext().AsRedisValue(); } + if ((options & GeoRadiusOptions.WithGeoHash) != 0) { hash = (long?)iter.GetNext().AsRedisValue(); } + if ((options & GeoRadiusOptions.WithCoordinates) != 0) + { + var coords = iter.GetNext().GetItems(); + double longitude = (double)coords[0].AsRedisValue(), latitude = (double)coords[1].AsRedisValue(); + position = new GeoPosition(longitude, latitude); + } + return new GeoRadiusResult(member, distance, hash, position); + } + } + + /// + /// Parser for the https://redis.io/commands/lcs/ format with the and arguments. + /// + /// + /// Example response: + /// 1) "matches" + /// 2) 1) 1) 1) (integer) 4 + /// 2) (integer) 7 + /// 2) 1) (integer) 5 + /// 2) (integer) 8 + /// 3) (integer) 4 + /// 3) "len" + /// 4) (integer) 6 + /// ... + /// + private sealed class LongestCommonSubsequenceProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + SetResult(message, Parse(result)); + return true; + } + return false; + } + + private static LCSMatchResult Parse(in RawResult result) + { + var topItems = result.GetItems(); + var matches = new LCSMatchResult.LCSMatch[topItems[1].GetItems().Length]; + int i = 0; + var matchesRawArray = topItems[1]; // skip the first element (title "matches") + foreach (var match in matchesRawArray.GetItems()) + { + var matchItems = match.GetItems(); + + matches[i++] = new LCSMatchResult.LCSMatch( + firstStringIndex: (long)matchItems[0].GetItems()[0].AsRedisValue(), + secondStringIndex: (long)matchItems[1].GetItems()[0].AsRedisValue(), + length: (long)matchItems[2].AsRedisValue()); + } + var len = (long)topItems[3].AsRedisValue(); + + return new LCSMatchResult(matches, len); + } + } + + private sealed class RedisValueProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + SetResult(message, result.AsRedisValue()); + return true; + } + return false; + } + } + + private sealed class RedisValueFromArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Array: + var items = result.GetItems(); + if (items.Length == 1) + { // treat an array of 1 like a single reply + SetResult(message, items[0].AsRedisValue()); + return true; + } + break; + } + return false; + } + } + + private sealed class RoleProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + var items = result.GetItems(); + if (items.IsEmpty) + { + return false; + } + + ref var val = ref items[0]; + Role? role; + if (val.IsEqual(RedisLiterals.master)) role = ParsePrimary(items); + else if (val.IsEqual(RedisLiterals.slave)) role = ParseReplica(items, RedisLiterals.slave!); + else if (val.IsEqual(RedisLiterals.replica)) role = ParseReplica(items, RedisLiterals.replica!); // for when "slave" is deprecated + else if (val.IsEqual(RedisLiterals.sentinel)) role = ParseSentinel(items); + else role = new Role.Unknown(val.GetString()!); + + if (role is null) return false; + SetResult(message, role); + return true; + } + + private static Role? ParsePrimary(in Sequence items) + { + if (items.Length < 3) + { + return null; + } + + if (!items[1].TryGetInt64(out var offset)) + { + return null; + } + + var replicaItems = items[2].GetItems(); + ICollection replicas; + if (replicaItems.IsEmpty) + { + replicas = Array.Empty(); + } + else + { + replicas = new List((int)replicaItems.Length); + for (int i = 0; i < replicaItems.Length; i++) + { + if (TryParsePrimaryReplica(replicaItems[i].GetItems(), out var replica)) + { + replicas.Add(replica); + } + else + { + return null; + } + } + } + + return new Role.Master(offset, replicas); + } + + private static bool TryParsePrimaryReplica(in Sequence items, out Role.Master.Replica replica) + { + if (items.Length < 3) + { + replica = default; + return false; + } + + var primaryIp = items[0].GetString()!; + + if (!items[1].TryGetInt64(out var primaryPort) || primaryPort > int.MaxValue) + { + replica = default; + return false; + } + + if (!items[2].TryGetInt64(out var replicationOffset)) + { + replica = default; + return false; + } + + replica = new Role.Master.Replica(primaryIp, (int)primaryPort, replicationOffset); + return true; + } + + private static Role? ParseReplica(in Sequence items, string role) + { + if (items.Length < 5) + { + return null; + } + + var primaryIp = items[1].GetString()!; + + if (!items[2].TryGetInt64(out var primaryPort) || primaryPort > int.MaxValue) + { + return null; + } + + ref var val = ref items[3]; + string replicationState; + if (val.IsEqual(RedisLiterals.connect)) replicationState = RedisLiterals.connect!; + else if (val.IsEqual(RedisLiterals.connecting)) replicationState = RedisLiterals.connecting!; + else if (val.IsEqual(RedisLiterals.sync)) replicationState = RedisLiterals.sync!; + else if (val.IsEqual(RedisLiterals.connected)) replicationState = RedisLiterals.connected!; + else if (val.IsEqual(RedisLiterals.none)) replicationState = RedisLiterals.none!; + else if (val.IsEqual(RedisLiterals.handshake)) replicationState = RedisLiterals.handshake!; + else replicationState = val.GetString()!; + + if (!items[4].TryGetInt64(out var replicationOffset)) + { + return null; + } + + return new Role.Replica(role, primaryIp, (int)primaryPort, replicationState, replicationOffset); + } + + private static Role? ParseSentinel(in Sequence items) + { + if (items.Length < 2) + { + return null; + } + var primaries = items[1].GetItemsAsStrings()!; + return new Role.Sentinel(primaries); + } + } + + private sealed class ScriptResultProcessor : ResultProcessor + { + public override bool SetResult(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.IsError && result.StartsWith(CommonReplies.NOSCRIPT)) + { // scripts are not flushed individually, so assume the entire script cache is toast ("SCRIPT FLUSH") + connection.BridgeCouldBeNull?.ServerEndPoint?.FlushScriptCache(); + message.SetScriptUnavailable(); + } + // and apply usual processing for the rest + return base.SetResult(connection, message, result); + } + + // note that top-level error messages still get handled by SetResult, but nested errors + // (is that a thing?) will be wrapped in the RedisResult + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (RedisResult.TryCreate(connection, result, out var value)) + { + SetResult(message, value); + return true; + } + return false; + } + } + + internal sealed class SingleStreamProcessor : StreamProcessorBase + { + private readonly bool skipStreamName; + + public SingleStreamProcessor(bool skipStreamName = false) + { + this.skipStreamName = skipStreamName; + } + + /// + /// Handles . + /// + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.IsNull) + { + // Server returns 'nil' if no entries are returned for the given stream. + SetResult(message, []); + return true; + } + + if (result.Resp2TypeArray != ResultType.Array) + { + return false; + } + + StreamEntry[] entries; + + if (skipStreamName) + { + /* + RESP 2: array element per stream; each element is an array of a name plus payload; payload is array of name/value pairs + + 127.0.0.1:6379> XREAD COUNT 2 STREAMS temperatures:us-ny:10007 0-0 + 1) 1) "temperatures:us-ny:10007" + 2) 1) 1) "1691504774593-0" + 2) 1) "temp_f" + 2) "87.2" + 3) "pressure" + 4) "29.69" + 5) "humidity" + 6) "46" + 2) 1) "1691504856705-0" + 2) 1) "temp_f" + 2) "87.2" + 3) "pressure" + 4) "29.69" + 5) "humidity" + 6) "46" + + RESP 3: map of element names with array of name plus payload; payload is array of name/value pairs + + 127.0.0.1:6379> XREAD COUNT 2 STREAMS temperatures:us-ny:10007 0-0 + 1# "temperatures:us-ny:10007" => 1) 1) "1691504774593-0" + 2) 1) "temp_f" + 2) "87.2" + 3) "pressure" + 4) "29.69" + 5) "humidity" + 6) "46" + 2) 1) "1691504856705-0" + 2) 1) "temp_f" + 2) "87.2" + 3) "pressure" + 4) "29.69" + 5) "humidity" + 6) "46" + */ + + ref readonly RawResult readResult = ref (result.Resp3Type == ResultType.Map ? ref result[1] : ref result[0][1]); + entries = ParseRedisStreamEntries(readResult); + } + else + { + entries = ParseRedisStreamEntries(result); + } + + SetResult(message, entries); + return true; + } + } + + /// + /// Handles . + /// + internal sealed class MultiStreamProcessor : StreamProcessorBase + { + /* + The result is similar to the XRANGE result (see SingleStreamProcessor) + with the addition of the stream name as the first element of top level + Multibulk array. + + > XREAD COUNT 2 STREAMS mystream writers 0-0 0-0 + 1) 1) "mystream" + 2) 1) 1) 1526984818136-0 + 2) 1) "duration" + 2) "1532" + 3) "event-id" + 4) "5" + 2) 1) 1526999352406-0 + 2) 1) "duration" + 2) "812" + 3) "event-id" + 4) "9" + 2) 1) "writers" + 2) 1) 1) 1526985676425-0 + 2) 1) "name" + 2) "Virginia" + 3) "surname" + 4) "Woolf" + 2) 1) 1526985685298-0 + 2) 1) "name" + 2) "Jane" + 3) "surname" + 4) "Austen" + */ + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.IsNull) + { + // Nothing returned for any of the requested streams. The server returns 'nil'. + SetResult(message, []); + return true; + } + + if (result.Resp2TypeArray != ResultType.Array) + { + return false; + } + + RedisStream[] streams; + if (result.Resp3Type == ResultType.Map) // see SetResultCore for the shape delta between RESP2 and RESP3 + { + // root is a map of named inner-arrays + streams = RedisStreamInterleavedProcessor.Instance.ParseArray(result, false, out _, this)!; // null-checked + } + else + { + streams = result.GetItems().ToArray( + (in RawResult item, in MultiStreamProcessor obj) => + { + var details = item.GetItems(); + + // details[0] = Name of the Stream + // details[1] = Multibulk Array of Stream Entries + return new RedisStream(key: details[0].AsRedisKey(), entries: obj.ParseRedisStreamEntries(details[1])!); + }, + this); + } + + SetResult(message, streams); + return true; + } + } + + private sealed class RedisStreamInterleavedProcessor : ValuePairInterleavedProcessorBase + { + protected override bool AllowJaggedPairs => false; // we only use this on a flattened map + + public static readonly RedisStreamInterleavedProcessor Instance = new(); + private RedisStreamInterleavedProcessor() + { + } + + protected override RedisStream Parse(in RawResult first, in RawResult second, object? state) + => new(key: first.AsRedisKey(), entries: ((MultiStreamProcessor)state!).ParseRedisStreamEntries(second)); + } + + /// + /// This processor is for *without* the option. + /// + internal sealed class StreamAutoClaimProcessor : StreamProcessorBase + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + // See https://redis.io/commands/xautoclaim for command documentation. + // Note that the result should never be null, so intentionally treating it as a failure to parse here + if (result.Resp2TypeArray == ResultType.Array && !result.IsNull) + { + var items = result.GetItems(); + + // [0] The next start ID. + var nextStartId = items[0].AsRedisValue(); + // [1] The array of StreamEntry's. + var entries = ParseRedisStreamEntries(items[1]); + // [2] The array of message IDs deleted from the stream that were in the PEL. + // This is not available in 6.2 so we need to be defensive when reading this part of the response. + var deletedIds = (items.Length == 3 ? items[2].GetItemsAsValues() : null) ?? []; + + SetResult(message, new StreamAutoClaimResult(nextStartId, entries, deletedIds)); + return true; + } + + return false; + } + } + + /// + /// This processor is for *with* the option. + /// + internal sealed class StreamAutoClaimIdsOnlyProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + // See https://redis.io/commands/xautoclaim for command documentation. + // Note that the result should never be null, so intentionally treating it as a failure to parse here + if (result.Resp2TypeArray == ResultType.Array && !result.IsNull) + { + var items = result.GetItems(); + + // [0] The next start ID. + var nextStartId = items[0].AsRedisValue(); + // [1] The array of claimed message IDs. + var claimedIds = items[1].GetItemsAsValues() ?? []; + // [2] The array of message IDs deleted from the stream that were in the PEL. + // This is not available in 6.2 so we need to be defensive when reading this part of the response. + var deletedIds = (items.Length == 3 ? items[2].GetItemsAsValues() : null) ?? []; + + SetResult(message, new StreamAutoClaimIdsOnlyResult(nextStartId, claimedIds, deletedIds)); + return true; + } + + return false; + } + } + + internal sealed class StreamConsumerInfoProcessor : InterleavedStreamInfoProcessorBase + { + protected override StreamConsumerInfo ParseItem(in RawResult result) + { + // Note: the base class passes a single consumer from the response into this method. + + // Response format: + // > XINFO CONSUMERS mystream mygroup + // 1) 1) name + // 2) "Alice" + // 3) pending + // 4) (integer)1 + // 5) idle + // 6) (integer)9104628 + // 2) 1) name + // 2) "Bob" + // 3) pending + // 4) (integer)1 + // 5) idle + // 6) (integer)83841983 + var arr = result.GetItems(); + string? name = default; + int pendingMessageCount = default; + long idleTimeInMilliseconds = default; + + KeyValuePairParser.TryRead(arr, KeyValuePairParser.Name, ref name); + KeyValuePairParser.TryRead(arr, KeyValuePairParser.Pending, ref pendingMessageCount); + KeyValuePairParser.TryRead(arr, KeyValuePairParser.Idle, ref idleTimeInMilliseconds); + + return new StreamConsumerInfo(name!, pendingMessageCount, idleTimeInMilliseconds); + } + } + + private static class KeyValuePairParser + { + internal static readonly CommandBytes + Name = "name", + Consumers = "consumers", + Pending = "pending", + Idle = "idle", + LastDeliveredId = "last-delivered-id", + EntriesRead = "entries-read", + Lag = "lag", + IP = "ip", + Port = "port"; + + internal static bool TryRead(Sequence pairs, in CommandBytes key, ref long value) + { + var len = pairs.Length / 2; + for (int i = 0; i < len; i++) + { + if (pairs[i * 2].IsEqual(key) && pairs[(i * 2) + 1].TryGetInt64(out var tmp)) + { + value = tmp; + return true; + } + } + return false; + } + internal static bool TryRead(Sequence pairs, in CommandBytes key, ref long? value) + { + var len = pairs.Length / 2; + for (int i = 0; i < len; i++) + { + if (pairs[i * 2].IsEqual(key) && pairs[(i * 2) + 1].TryGetInt64(out var tmp)) + { + value = tmp; + return true; + } + } + return false; + } + + internal static bool TryRead(Sequence pairs, in CommandBytes key, ref int value) + { + long tmp = default; + if (TryRead(pairs, key, ref tmp)) + { + value = checked((int)tmp); + return true; + } + return false; + } + + internal static bool TryRead(Sequence pairs, in CommandBytes key, [NotNullWhen(true)] ref string? value) + { + var len = pairs.Length / 2; + for (int i = 0; i < len; i++) + { + if (pairs[i * 2].IsEqual(key)) + { + value = pairs[(i * 2) + 1].GetString()!; + return true; + } + } + return false; + } + } + + internal sealed class StreamGroupInfoProcessor : InterleavedStreamInfoProcessorBase + { + protected override StreamGroupInfo ParseItem(in RawResult result) + { + // Note: the base class passes a single item from the response into this method. + + // Response format: + // > XINFO GROUPS mystream + // 1) 1) name + // 2) "mygroup" + // 3) consumers + // 4) (integer)2 + // 5) pending + // 6) (integer)2 + // 7) last-delivered-id + // 8) "1588152489012-0" + // 9) "entries-read" + // 10) (integer)2 + // 11) "lag" + // 12) (integer)0 + // 2) 1) name + // 2) "some-other-group" + // 3) consumers + // 4) (integer)1 + // 5) pending + // 6) (integer)0 + // 7) last-delivered-id + // 8) "1588152498034-0" + // 9) "entries-read" + // 10) (integer)1 + // 11) "lag" + // 12) (integer)1 + var arr = result.GetItems(); + string? name = default, lastDeliveredId = default; + int consumerCount = default, pendingMessageCount = default; + long entriesRead = default; + long? lag = default; + + KeyValuePairParser.TryRead(arr, KeyValuePairParser.Name, ref name); + KeyValuePairParser.TryRead(arr, KeyValuePairParser.Consumers, ref consumerCount); + KeyValuePairParser.TryRead(arr, KeyValuePairParser.Pending, ref pendingMessageCount); + KeyValuePairParser.TryRead(arr, KeyValuePairParser.LastDeliveredId, ref lastDeliveredId); + KeyValuePairParser.TryRead(arr, KeyValuePairParser.EntriesRead, ref entriesRead); + KeyValuePairParser.TryRead(arr, KeyValuePairParser.Lag, ref lag); + + return new StreamGroupInfo(name!, consumerCount, pendingMessageCount, lastDeliveredId, entriesRead, lag); + } + } + + internal abstract class InterleavedStreamInfoProcessorBase : ResultProcessor + { + protected abstract T ParseItem(in RawResult result); + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray != ResultType.Array) + { + return false; + } + + var arr = result.GetItems(); + var parsedItems = arr.ToArray((in RawResult item, in InterleavedStreamInfoProcessorBase obj) => obj.ParseItem(item), this); + + SetResult(message, parsedItems); + return true; + } + } + + internal sealed class StreamInfoProcessor : StreamProcessorBase + { + // Parse the following format: + // > XINFO mystream + // 1) length + // 2) (integer) 13 + // 3) radix-tree-keys + // 4) (integer) 1 + // 5) radix-tree-nodes + // 6) (integer) 2 + // 7) groups + // 8) (integer) 2 + // 9) first-entry + // 10) 1) 1524494395530-0 + // 2) 1) "a" + // 2) "1" + // 3) "b" + // 4) "2" + // 11) last-entry + // 12) 1) 1526569544280-0 + // 2) 1) "message" + // 2) "banana" + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray != ResultType.Array) + { + return false; + } + + var arr = result.GetItems(); + var max = arr.Length / 2; + + long length = -1, radixTreeKeys = -1, radixTreeNodes = -1, groups = -1; + var lastGeneratedId = Redis.RedisValue.Null; + StreamEntry firstEntry = StreamEntry.Null, lastEntry = StreamEntry.Null; + var iter = arr.GetEnumerator(); + for (int i = 0; i < max; i++) + { + ref RawResult key = ref iter.GetNext(), value = ref iter.GetNext(); + if (key.Payload.Length > CommandBytes.MaxLength) continue; + + var keyBytes = new CommandBytes(key.Payload); + if (keyBytes.Equals(CommonReplies.length)) + { + if (!value.TryGetInt64(out length)) return false; + } + else if (keyBytes.Equals(CommonReplies.radixTreeKeys)) + { + if (!value.TryGetInt64(out radixTreeKeys)) return false; + } + else if (keyBytes.Equals(CommonReplies.radixTreeNodes)) + { + if (!value.TryGetInt64(out radixTreeNodes)) return false; + } + else if (keyBytes.Equals(CommonReplies.groups)) + { + if (!value.TryGetInt64(out groups)) return false; + } + else if (keyBytes.Equals(CommonReplies.lastGeneratedId)) + { + lastGeneratedId = value.AsRedisValue(); + } + else if (keyBytes.Equals(CommonReplies.firstEntry)) + { + firstEntry = ParseRedisStreamEntry(value); + } + else if (keyBytes.Equals(CommonReplies.lastEntry)) + { + lastEntry = ParseRedisStreamEntry(value); + } + } + + var streamInfo = new StreamInfo( + length: checked((int)length), + radixTreeKeys: checked((int)radixTreeKeys), + radixTreeNodes: checked((int)radixTreeNodes), + groups: checked((int)groups), + firstEntry: firstEntry, + lastEntry: lastEntry, + lastGeneratedId: lastGeneratedId); + + SetResult(message, streamInfo); + return true; + } + } + + internal sealed class StreamPendingInfoProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + // Example: + // > XPENDING mystream mygroup + // 1) (integer)2 + // 2) 1526569498055 - 0 + // 3) 1526569506935 - 0 + // 4) 1) 1) "Bob" + // 2) "2" + // 5) 1) 1) "Joe" + // 2) "8" + if (result.Resp2TypeArray != ResultType.Array) + { + return false; + } + + var arr = result.GetItems(); + + if (arr.Length != 4) + { + return false; + } + + StreamConsumer[]? consumers = null; + + // If there are no consumers as of yet for the given group, the last + // item in the response array will be null. + ref RawResult third = ref arr[3]; + if (!third.IsNull) + { + consumers = third.ToArray((in RawResult item) => + { + var details = item.GetItems(); + return new StreamConsumer( + name: details[0].AsRedisValue(), + pendingMessageCount: (int)details[1].AsRedisValue()); + }); + } + + var pendingInfo = new StreamPendingInfo( + pendingMessageCount: (int)arr[0].AsRedisValue(), + lowestId: arr[1].AsRedisValue(), + highestId: arr[2].AsRedisValue(), + consumers: consumers ?? []); + + SetResult(message, pendingInfo); + return true; + } + } + + internal sealed class StreamPendingMessagesProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray != ResultType.Array) + { + return false; + } + + var messageInfoArray = result.GetItems().ToArray((in RawResult item) => + { + var details = item.GetItems().GetEnumerator(); + + return new StreamPendingMessageInfo( + messageId: details.GetNext().AsRedisValue(), + consumerName: details.GetNext().AsRedisValue(), + idleTimeInMs: (long)details.GetNext().AsRedisValue(), + deliveryCount: (int)details.GetNext().AsRedisValue()); + }); + + SetResult(message, messageInfoArray); + return true; + } + } + + internal sealed class StreamNameValueEntryProcessor : ValuePairInterleavedProcessorBase + { + public static readonly StreamNameValueEntryProcessor Instance = new(); + private StreamNameValueEntryProcessor() + { + } + + protected override NameValueEntry Parse(in RawResult first, in RawResult second, object? state) + => new NameValueEntry(first.AsRedisValue(), second.AsRedisValue()); + } + + /// + /// Handles stream responses. For formats, see . + /// + /// The type of the stream result. + internal abstract class StreamProcessorBase : ResultProcessor + { + protected static StreamEntry ParseRedisStreamEntry(in RawResult item) + { + if (item.IsNull || item.Resp2TypeArray != ResultType.Array) + { + return StreamEntry.Null; + } + // Process the Multibulk array for each entry. The entry contains the following elements: + // [0] = SimpleString (the ID of the stream entry) + // [1] = Multibulk array of the name/value pairs of the stream entry's data + var entryDetails = item.GetItems(); + + return new StreamEntry( + id: entryDetails[0].AsRedisValue(), + values: ParseStreamEntryValues(entryDetails[1])); + } + protected internal StreamEntry[] ParseRedisStreamEntries(in RawResult result) => + result.GetItems().ToArray((in RawResult item, in StreamProcessorBase _) => ParseRedisStreamEntry(item), this); + + protected static NameValueEntry[] ParseStreamEntryValues(in RawResult result) + { + // The XRANGE, XREVRANGE, XREAD commands return stream entries + // in the following format. The name/value pairs are interleaved + // in the same fashion as the HGETALL response. + // + // 1) 1) 1518951480106-0 + // 2) 1) "sensor-id" + // 2) "1234" + // 3) "temperature" + // 4) "19.8" + // 2) 1) 1518951482479-0 + // 2) 1) "sensor-id" + // 2) "9999" + // 3) "temperature" + // 4) "18.2" + if (result.Resp2TypeArray != ResultType.Array || result.IsNull) + { + return []; + } + return StreamNameValueEntryProcessor.Instance.ParseArray(result, false, out _, null)!; // ! because we checked null above + } + } + + private sealed class StringPairInterleavedProcessor : ValuePairInterleavedProcessorBase> + { + protected override KeyValuePair Parse(in RawResult first, in RawResult second, object? state) => + new KeyValuePair(first.GetString()!, second.GetString()!); + } + + private sealed class StringProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.Integer: + case ResultType.SimpleString: + case ResultType.BulkString: + SetResult(message, result.GetString()); + return true; + case ResultType.Array: + var arr = result.GetItems(); + if (arr.Length == 1) + { + SetResult(message, arr[0].GetString()); + return true; + } + break; + } + return false; + } + } + + private sealed class TieBreakerProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + case ResultType.SimpleString: + case ResultType.BulkString: + var tieBreaker = result.GetString()!; + SetResult(message, tieBreaker); + + try + { + if (connection.BridgeCouldBeNull?.ServerEndPoint is ServerEndPoint endpoint) + { + endpoint.TieBreakerResult = tieBreaker; + } + } + catch { } + + return true; + } + return false; + } + } + + private sealed class TracerProcessor : ResultProcessor + { + private readonly bool establishConnection; + + public TracerProcessor(bool establishConnection) + { + this.establishConnection = establishConnection; + } + + public override bool SetResult(PhysicalConnection connection, Message message, in RawResult result) + { + connection.BridgeCouldBeNull?.Multiplexer.OnInfoMessage($"got '{result}' for '{message.CommandAndKey}' on '{connection}'"); + var final = base.SetResult(connection, message, result); + if (result.IsError) + { + if (result.StartsWith(CommonReplies.authFail_trimmed) || result.StartsWith(CommonReplies.NOAUTH)) + { + connection.RecordConnectionFailed(ConnectionFailureType.AuthenticationFailure, new Exception(result.ToString() + " Verify if the Redis password provided is correct. Attempted command: " + message.Command)); + } + else if (result.StartsWith(CommonReplies.loading)) + { + connection.RecordConnectionFailed(ConnectionFailureType.Loading); + } + else + { + connection.RecordConnectionFailed(ConnectionFailureType.ProtocolFailure, new RedisServerException(result.ToString())); + } + } + + if (connection.Protocol is null) + { + // if we didn't get a valid response from HELLO, then we have to assume RESP2 at some point + connection.SetProtocol(RedisProtocol.Resp2); + } + + return final; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0071:Simplify interpolation", Justification = "Allocations (string.Concat vs. string.Format)")] + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + bool happy; + switch (message.Command) + { + case RedisCommand.ECHO: + happy = result.Resp2TypeBulkString == ResultType.BulkString && (!establishConnection || result.IsEqual(connection.BridgeCouldBeNull?.Multiplexer?.UniqueId)); + break; + case RedisCommand.PING: + // there are two different PINGs; "interactive" is a +PONG or +{your message}, + // but subscriber returns a bulk-array of [ "pong", {your message} ] + switch (result.Resp2TypeArray) + { + case ResultType.SimpleString: + happy = result.IsEqual(CommonReplies.PONG); + break; + case ResultType.Array when result.ItemsCount == 2: + var items = result.GetItems(); + happy = items[0].IsEqual(CommonReplies.PONG) && items[1].Payload.IsEmpty; + break; + default: + happy = false; + break; + } + break; + case RedisCommand.TIME: + happy = result.Resp2TypeArray == ResultType.Array && result.ItemsCount == 2; + break; + case RedisCommand.EXISTS: + happy = result.Resp2TypeBulkString == ResultType.Integer; + break; + default: + happy = false; + break; + } + if (happy) + { + if (establishConnection) + { + // This is what ultimately brings us to complete a connection, by advancing the state forward from a successful tracer after connection. + connection.BridgeCouldBeNull?.OnFullyEstablished(connection, $"From command: {message.Command}"); + } + SetResult(message, happy); + return true; + } + else + { + connection.RecordConnectionFailed( + ConnectionFailureType.ProtocolFailure, + new InvalidOperationException($"unexpected tracer reply to {message.Command}: {result.ToString()}")); + return false; + } + } + } + + private sealed class SentinelGetPrimaryAddressByNameProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var items = result.GetItems(); + if (result.IsNull) + { + return true; + } + else if (items.Length == 2 && items[1].TryGetInt64(out var port)) + { + SetResult(message, Format.ParseEndPoint(items[0].GetString()!, checked((int)port))); + return true; + } + else if (items.Length == 0) + { + SetResult(message, null); + return true; + } + break; + } + return false; + } + } + + private sealed class SentinelGetSentinelAddressesProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + List endPoints = []; + + switch (result.Resp2TypeArray) + { + case ResultType.Array: + foreach (RawResult item in result.GetItems()) + { + var pairs = item.GetItems(); + string? ip = null; + int port = default; + if (KeyValuePairParser.TryRead(pairs, in KeyValuePairParser.IP, ref ip) + && KeyValuePairParser.TryRead(pairs, in KeyValuePairParser.Port, ref port)) + { + endPoints.Add(Format.ParseEndPoint(ip, port)); + } + } + SetResult(message, endPoints.ToArray()); + return true; + + case ResultType.SimpleString: + // We don't want to blow up if the primary is not found + if (result.IsNull) + return true; + break; + } + + return false; + } + } + + private sealed class SentinelGetReplicaAddressesProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + List endPoints = []; + + switch (result.Resp2TypeArray) + { + case ResultType.Array: + foreach (RawResult item in result.GetItems()) + { + var pairs = item.GetItems(); + string? ip = null; + int port = default; + if (KeyValuePairParser.TryRead(pairs, in KeyValuePairParser.IP, ref ip) + && KeyValuePairParser.TryRead(pairs, in KeyValuePairParser.Port, ref port)) + { + endPoints.Add(Format.ParseEndPoint(ip, port)); + } + } + break; + + case ResultType.SimpleString: + // We don't want to blow up if the primary is not found + if (result.IsNull) + return true; + break; + } + + if (endPoints.Count > 0) + { + SetResult(message, endPoints.ToArray()); + return true; + } + + return false; + } + } + + private sealed class SentinelArrayOfArraysProcessor : ResultProcessor[][]> + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (StringPairInterleaved is not StringPairInterleavedProcessor innerProcessor) + { + return false; + } + + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var arrayOfArrays = result.GetItems(); + + var returnArray = result.ToArray[], StringPairInterleavedProcessor>( + (in RawResult rawInnerArray, in StringPairInterleavedProcessor proc) => + { + if (proc.TryParse(rawInnerArray, out KeyValuePair[]? kvpArray)) + { + return kvpArray!; + } + else + { + throw new ArgumentOutOfRangeException(nameof(rawInnerArray), $"Error processing {message.CommandAndKey}, could not decode array '{rawInnerArray}'"); + } + }, + innerProcessor)!; + + SetResult(message, returnArray); + return true; + } + return false; + } + } + } + + internal abstract class ResultProcessor : ResultProcessor + { + protected static void SetResult(Message? message, T value) + { + if (message == null) return; + var box = message.ResultBox as IResultBox; + message.SetResponseReceived(); + + box?.SetResult(value); + } + } + + internal abstract class ArrayResultProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeArray) + { + case ResultType.Array: + var items = result.GetItems(); + T[] arr; + if (items.IsEmpty) + { + arr = []; + } + else + { + arr = new T[checked((int)items.Length)]; + int index = 0; + foreach (ref RawResult inner in items) + { + if (!TryParse(inner, out arr[index++])) + return false; + } + } + SetResult(message, arr); + return true; + default: + return false; + } + } + + protected abstract bool TryParse(in RawResult raw, out T parsed); + } +} diff --git a/src/StackExchange.Redis/ResultTypeExtensions.cs b/src/StackExchange.Redis/ResultTypeExtensions.cs new file mode 100644 index 000000000..e2f941f00 --- /dev/null +++ b/src/StackExchange.Redis/ResultTypeExtensions.cs @@ -0,0 +1,11 @@ +namespace StackExchange.Redis +{ + internal static class ResultTypeExtensions + { + public static bool IsError(this ResultType value) + => (value & (ResultType)0b111) == ResultType.Error; + + public static ResultType ToResp2(this ResultType value) + => value & (ResultType)0b111; // just keep the last 3 bits + } +} diff --git a/src/StackExchange.Redis/Role.cs b/src/StackExchange.Redis/Role.cs new file mode 100644 index 000000000..587194026 --- /dev/null +++ b/src/StackExchange.Redis/Role.cs @@ -0,0 +1,137 @@ +using System.Collections.Generic; + +namespace StackExchange.Redis +{ + /// + /// Result of the ROLE command. Values depend on the role: master, replica, or sentinel. + /// + /// + public abstract class Role + { + internal static Unknown Null { get; } = new Unknown(""); + + /// + /// One of "master", "slave" (aka replica), or "sentinel". + /// + public string Value { get; } + + /// + public override string ToString() => Value; + + private Role(string role) => Value = role; + + /// + /// Result of the ROLE command for a primary node. + /// + /// + public sealed class Master : Role + { + /// + /// The replication offset. To be consumed by replica nodes. + /// + public long ReplicationOffset { get; } + + /// + /// Connected replica nodes. + /// + public ICollection Replicas { get; } + + /// + /// A connected replica node. + /// + public new readonly struct Replica + { + /// + /// The IP address of this replica node. + /// + public string Ip { get; } + + /// + /// The port number of this replica node. + /// + public int Port { get; } + + /// + /// The last replication offset acked by this replica node. + /// + public long ReplicationOffset { get; } + + internal Replica(string ip, int port, long offset) + { + Ip = ip; + Port = port; + ReplicationOffset = offset; + } + + /// + public override string ToString() => $"{Ip}:{Port} - {ReplicationOffset}"; + } + + internal Master(long offset, ICollection replicas) : base(RedisLiterals.master!) + { + ReplicationOffset = offset; + Replicas = replicas; + } + } + + /// + /// Result of the ROLE command for a replica node. + /// + /// + public sealed class Replica : Role + { + /// + /// The IP address of the primary node for this replica. + /// + public string MasterIp { get; } + + /// + /// The port number of the primary node for this replica. + /// + public int MasterPort { get; } + + /// + /// This replica's replication state. + /// + public string State { get; } + + /// + /// The last replication offset received by this replica. + /// + public long ReplicationOffset { get; } + + internal Replica(string role, string ip, int port, string state, long offset) : base(role) + { + MasterIp = ip; + MasterPort = port; + State = state; + ReplicationOffset = offset; + } + } + + /// + /// Result of the ROLE command for a sentinel node. + /// + /// + public sealed class Sentinel : Role + { + /// + /// Primary names monitored by this sentinel node. + /// + public ICollection MonitoredMasters { get; } + + internal Sentinel(ICollection primaries) : base(RedisLiterals.sentinel!) + { + MonitoredMasters = primaries; + } + } + + /// + /// An unexpected result of the ROLE command. + /// + public sealed class Unknown : Role + { + internal Unknown(string role) : base(role) { } + } + } +} diff --git a/src/StackExchange.Redis/Runtime.cs b/src/StackExchange.Redis/Runtime.cs new file mode 100644 index 000000000..879c9c325 --- /dev/null +++ b/src/StackExchange.Redis/Runtime.cs @@ -0,0 +1,9 @@ +using System; +using System.Runtime.InteropServices; + +namespace StackExchange.Redis; + +internal static class Runtime +{ + public static readonly bool IsMono = RuntimeInformation.FrameworkDescription.StartsWith("Mono ", StringComparison.OrdinalIgnoreCase); +} diff --git a/src/StackExchange.Redis/ScriptParameterMapper.cs b/src/StackExchange.Redis/ScriptParameterMapper.cs new file mode 100644 index 000000000..10dccd5ff --- /dev/null +++ b/src/StackExchange.Redis/ScriptParameterMapper.cs @@ -0,0 +1,299 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; + +namespace StackExchange.Redis +{ + internal static class ScriptParameterMapper + { + public readonly struct ScriptParameters + { + public readonly RedisKey[] Keys; + public readonly RedisValue[] Arguments; + + public static readonly ConstructorInfo Cons = typeof(ScriptParameters).GetConstructor(new[] { typeof(RedisKey[]), typeof(RedisValue[]) })!; + public ScriptParameters(RedisKey[] keys, RedisValue[] args) + { + Keys = keys; + Arguments = args; + } + } + + private static readonly Regex ParameterExtractor = new Regex(@"@(? ([a-z]|_) ([a-z]|_|\d)*)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.CultureInvariant); + + private static string[] ExtractParameters(string script) + { + var ps = ParameterExtractor.Matches(script); + if (ps.Count == 0) + { + return Array.Empty(); + } + + var ret = new HashSet(); + + for (var i = 0; i < ps.Count; i++) + { + var c = ps[i]; + var ix = c.Index - 1; + if (ix >= 0) + { + var prevChar = script[ix]; + + // don't consider this a parameter if it's in the middle of word (i.e. if it's preceded by a letter) + if (char.IsLetterOrDigit(prevChar) || prevChar == '_') continue; + + // this is an escape, ignore it + if (prevChar == '@') continue; + } + + var n = c.Groups["paramName"].Value; + if (!ret.Contains(n)) ret.Add(n); + } + + return ret.ToArray(); + } + + private static string MakeOrdinalScriptWithoutKeys(string rawScript, string[] args) + { + var ps = ParameterExtractor.Matches(rawScript); + if (ps.Count == 0) return rawScript; + + var ret = new StringBuilder(); + var upTo = 0; + + for (var i = 0; i < ps.Count; i++) + { + var capture = ps[i]; + var name = capture.Groups["paramName"].Value; + + var ix = capture.Index; + ret.Append(rawScript, upTo, ix - upTo); + + var argIx = Array.IndexOf(args, name); + + if (argIx != -1) + { + ret.Append("ARGV["); + ret.Append(argIx + 1); + ret.Append(']'); + } + else + { + var isEscape = false; + var prevIx = capture.Index - 1; + if (prevIx >= 0) + { + var prevChar = rawScript[prevIx]; + isEscape = prevChar == '@'; + } + + if (isEscape) + { + // strip the @ off, so just the one triggering the escape exists + ret.Append(capture.Groups["paramName"].Value); + } + else + { + ret.Append(capture.Value); + } + } + + upTo = capture.Index + capture.Length; + } + + ret.Append(rawScript, upTo, rawScript.Length - upTo); + + return ret.ToString(); + } + + private static readonly Dictionary _conversionOperators; + static ScriptParameterMapper() + { + var tmp = new Dictionary(); + foreach (var method in typeof(RedisValue).GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (method.ReturnType == typeof(RedisValue) && (method.Name == "op_Implicit" || method.Name == "op_Explicit")) + { + var p = method.GetParameters(); + if (p?.Length == 1) + { + tmp[p[0].ParameterType] = method; + } + } + } + _conversionOperators = tmp; + } + + /// + /// Turns a script with @namedParameters into a LuaScript that can be executed against a given IDatabase(Async) object. + /// + /// The script to prepare. + public static LuaScript PrepareScript(string script) + { + var ps = ExtractParameters(script); + var ordinalScript = MakeOrdinalScriptWithoutKeys(script, ps); + return new LuaScript(script, ordinalScript, ps); + } + + private static readonly HashSet ConvertableTypes = new() + { + typeof(int), + typeof(int?), + typeof(long), + typeof(long?), + typeof(double), + typeof(double?), + typeof(string), + typeof(byte[]), + typeof(ReadOnlyMemory), + typeof(bool), + typeof(bool?), + + typeof(RedisKey), + typeof(RedisValue), + }; + + /// + /// Determines whether or not the given type can be used to provide parameters for the given . + /// + /// The type of the parameter. + /// The script to match against. + /// The first missing member, if any. + /// The first type mismatched member, if any. + public static bool IsValidParameterHash(Type t, LuaScript script, out string? missingMember, out string? badTypeMember) + { + for (var i = 0; i < script.Arguments.Length; i++) + { + var argName = script.Arguments[i]; + var member = t.GetMember(argName).SingleOrDefault(m => m is PropertyInfo || m is FieldInfo); + if (member == null) + { + missingMember = argName; + badTypeMember = null; + return false; + } + + var memberType = member is FieldInfo memberFieldInfo ? memberFieldInfo.FieldType : ((PropertyInfo)member).PropertyType; + if (!ConvertableTypes.Contains(memberType)) + { + missingMember = null; + badTypeMember = argName; + return false; + } + } + + missingMember = badTypeMember = null; + return true; + } + + /// + /// Creates a Func that extracts parameters from the given type for use by a LuaScript. + /// + /// Members that are RedisKey's get extracted to be passed in as keys to redis; all members that + /// appear in the script get extracted as RedisValue arguments to be sent up as args. + /// + /// + /// We send all values as arguments so we don't have to prepare the same script for different parameter + /// types. + /// + /// + /// The created Func takes a RedisKey, which will be prefixed to all keys (and arguments of type RedisKey) for + /// keyspace isolation. + /// + /// + /// The type to extract for. + /// The script to extract for. + public static Func GetParameterExtractor(Type t, LuaScript script) + { + if (!IsValidParameterHash(t, script, out _, out _)) throw new Exception("Shouldn't be possible"); + + static Expression GetMember(Expression root, MemberInfo member) => member.MemberType switch + { + MemberTypes.Property => Expression.Property(root, (PropertyInfo)member), + MemberTypes.Field => Expression.Field(root, (FieldInfo)member), + _ => throw new ArgumentException($"Member type '{member.MemberType}' isn't recognized", nameof(member)), + }; + var keys = new List(); + var args = new List(); + + for (var i = 0; i < script.Arguments.Length; i++) + { + var argName = script.Arguments[i]; + var member = t.GetMember(argName).SingleOrDefault(m => m is PropertyInfo || m is FieldInfo) ?? throw new ArgumentException($"There was no member found for {argName}"); + var memberType = member is FieldInfo memberFieldInfo ? memberFieldInfo.FieldType : ((PropertyInfo)member).PropertyType; + + if (memberType == typeof(RedisKey)) + { + keys.Add(member); + } + else if (memberType != typeof(RedisValue) && !_conversionOperators.ContainsKey(memberType)) + { + throw new InvalidCastException($"There is no conversion available from {memberType.Name} to {nameof(RedisValue)}"); + } + args.Add(member); + } + + var objUntyped = Expression.Parameter(typeof(object), "obj"); + var objTyped = Expression.Convert(objUntyped, t); + var keyPrefix = Expression.Parameter(typeof(RedisKey?), "keyPrefix"); + + Expression keysResult, valuesResult; + MethodInfo? asRedisValue = null; + Expression[]? keysResultArr = null; + if (keys.Count == 0) + { + // if there are no keys, don't allocate + keysResult = Expression.Constant(null, typeof(RedisKey[])); + } + else + { + var needsKeyPrefix = Expression.Property(keyPrefix, nameof(Nullable.HasValue)); + var keyPrefixValueArr = new[] + { + Expression.Call(keyPrefix, nameof(Nullable.GetValueOrDefault), null, null), + }; + var prepend = typeof(RedisKey).GetMethod(nameof(RedisKey.Prepend), BindingFlags.Public | BindingFlags.Instance)!; + asRedisValue = typeof(RedisKey).GetMethod(nameof(RedisKey.AsRedisValue), BindingFlags.NonPublic | BindingFlags.Instance)!; + + keysResultArr = new Expression[keys.Count]; + for (int i = 0; i < keysResultArr.Length; i++) + { + var member = GetMember(objTyped, keys[i]); + keysResultArr[i] = Expression.Condition(needsKeyPrefix, Expression.Call(member, prepend, keyPrefixValueArr), member); + } + keysResult = Expression.NewArrayInit(typeof(RedisKey), keysResultArr); + } + + if (args.Count == 0) + { + // if there are no args, don't allocate + valuesResult = Expression.Constant(null, typeof(RedisValue[])); + } + else + { + valuesResult = Expression.NewArrayInit(typeof(RedisValue), args.Select(arg => + { + var member = GetMember(objTyped, arg); + if (member.Type == typeof(RedisValue)) return member; // pass-through + if (member.Type == typeof(RedisKey)) + { // need to apply prefix (note we can re-use the body from earlier) + var val = keysResultArr![keys.IndexOf(arg)]; + return Expression.Call(val, asRedisValue!); + } + + // otherwise: use the conversion operator + var conversion = _conversionOperators[member.Type]; + return Expression.Call(conversion, member); + })); + } + + var body = Expression.Lambda>( + Expression.New(ScriptParameters.Cons, keysResult, valuesResult), objUntyped, keyPrefix); + return body.Compile(); + } + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/ServerCounters.cs b/src/StackExchange.Redis/ServerCounters.cs similarity index 76% rename from StackExchange.Redis/StackExchange/Redis/ServerCounters.cs rename to src/StackExchange.Redis/ServerCounters.cs index 9b9848e7a..b661f27d7 100644 --- a/StackExchange.Redis/StackExchange/Redis/ServerCounters.cs +++ b/src/StackExchange.Redis/ServerCounters.cs @@ -4,11 +4,15 @@ namespace StackExchange.Redis { /// - /// Illustrates the queues associates with this server + /// Illustrates the queues associates with this server. /// public class ServerCounters { - internal ServerCounters(EndPoint endpoint) + /// + /// Creates a instance for an . + /// + /// The to create counters for. + public ServerCounters(EndPoint? endpoint) { EndPoint = endpoint; Interactive = new ConnectionCounters(ConnectionType.Interactive); @@ -17,31 +21,32 @@ internal ServerCounters(EndPoint endpoint) } /// - /// The endpoint to which this data relates (this can be null if the data represents all servers) + /// The endpoint to which this data relates (this can be null if the data represents all servers). /// - public EndPoint EndPoint { get; } + public EndPoint? EndPoint { get; } /// - /// Counters associated with the interactive (non pub-sub) connection + /// Counters associated with the interactive (non pub-sub) connection. /// public ConnectionCounters Interactive { get; } /// - /// Counters associated with other ambient activity + /// Counters associated with other ambient activity. /// public ConnectionCounters Other { get; } /// - /// Counters associated with the subscription (pub-sub) connection + /// Counters associated with the subscription (pub-sub) connection. /// public ConnectionCounters Subscription { get; } + /// - /// Indicates the total number of outstanding items against this server + /// Indicates the total number of outstanding items against this server. /// public long TotalOutstanding => Interactive.TotalOutstanding + Subscription.TotalOutstanding + Other.TotalOutstanding; /// - /// See Object.ToString(); + /// See . /// public override string ToString() { @@ -65,4 +70,4 @@ internal void Add(ServerCounters other) Subscription.Add(other.Subscription); } } -} \ No newline at end of file +} diff --git a/src/StackExchange.Redis/ServerEndPoint.cs b/src/StackExchange.Redis/ServerEndPoint.cs new file mode 100644 index 000000000..f856a5b21 --- /dev/null +++ b/src/StackExchange.Redis/ServerEndPoint.cs @@ -0,0 +1,1126 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using static StackExchange.Redis.PhysicalBridge; + +namespace StackExchange.Redis +{ + [Flags] + internal enum UnselectableFlags + { + None = 0, + RedundantPrimary = 1, + DidNotRespond = 2, + ServerType = 4, + } + + internal sealed partial class ServerEndPoint : IDisposable + { + internal volatile ServerEndPoint? Primary; + internal volatile ServerEndPoint[] Replicas = Array.Empty(); + private static readonly Regex nameSanitizer = new Regex("[^!-~]+", RegexOptions.Compiled); + + private readonly Hashtable knownScripts = new Hashtable(StringComparer.Ordinal); + + private int databases, writeEverySeconds; + private PhysicalBridge? interactive, subscription; + private bool isDisposed, replicaReadOnly, isReplica, allowReplicaWrites; + private bool? supportsDatabases, supportsPrimaryWrites; + private ServerType serverType; + private volatile UnselectableFlags unselectableReasons; + private Version version; + + internal void ResetNonConnected() + { + interactive?.ResetNonConnected(); + subscription?.ResetNonConnected(); + } + + public ServerEndPoint(ConnectionMultiplexer multiplexer, EndPoint endpoint) + { + Multiplexer = multiplexer; + EndPoint = endpoint; + var config = multiplexer.RawConfig; + version = config.DefaultVersion; + replicaReadOnly = true; + isReplica = false; + databases = 0; + writeEverySeconds = config.KeepAlive > 0 ? config.KeepAlive : 60; + serverType = ServerType.Standalone; + ConfigCheckSeconds = Multiplexer.RawConfig.ConfigCheckSeconds; + + // overrides for twemproxy/envoyproxy + switch (multiplexer.RawConfig.Proxy) + { + case Proxy.Twemproxy: + databases = 1; + serverType = ServerType.Twemproxy; + break; + case Proxy.Envoyproxy: + databases = 1; + serverType = ServerType.Envoyproxy; + break; + } + } + + private RedisServer? _defaultServer; + public RedisServer GetRedisServer(object? asyncState) + => asyncState is null + ? (_defaultServer ??= new RedisServer(this, null)) // reuse and memoize + : new RedisServer(this, asyncState); + + public EndPoint EndPoint { get; } + + public ClusterConfiguration? ClusterConfiguration { get; private set; } + + /// + /// Whether this endpoint supports databases at all. + /// Note that some servers are cluster but present as standalone (e.g. Redis Enterprise), so we respect + /// being disabled here as a performance workaround. + /// + /// + /// This is memoized because it's accessed on hot paths inside the write lock. + /// + public bool SupportsDatabases => + supportsDatabases ??= serverType == ServerType.Standalone && Multiplexer.CommandMap.IsAvailable(RedisCommand.SELECT); + + public int Databases + { + get => databases; + set => SetConfig(ref databases, value); + } + + public bool IsConnecting => interactive?.IsConnecting == true; + public bool IsConnected => interactive?.IsConnected == true; + public bool IsSubscriberConnected => KnowOrAssumeResp3() ? IsConnected : subscription?.IsConnected == true; + + public bool KnowOrAssumeResp3() + { + var protocol = interactive?.Protocol; + return protocol is not null + ? protocol.GetValueOrDefault() >= RedisProtocol.Resp3 // <= if we've completed handshake, use what we *know for sure* + : Multiplexer.RawConfig.TryResp3(); // otherwise, use what we *expect* + } + + public bool SupportsSubscriptions => Multiplexer.CommandMap.IsAvailable(RedisCommand.SUBSCRIBE); + public bool SupportsPrimaryWrites => supportsPrimaryWrites ??= !IsReplica || !ReplicaReadOnly || AllowReplicaWrites; + + private readonly List> _pendingConnectionMonitors = new List>(); + + /// + /// Awaitable state seeing if this endpoint is connected. + /// + public Task OnConnectedAsync(ILogger? log = null, bool sendTracerIfConnected = false, bool autoConfigureIfConnected = false) + { + async Task IfConnectedAsync(ILogger? log, bool sendTracerIfConnected, bool autoConfigureIfConnected) + { + log?.LogInformationOnConnectedAsyncAlreadyConnectedStart(new(this)); + if (autoConfigureIfConnected) + { + await AutoConfigureAsync(null, log).ForAwait(); + } + if (sendTracerIfConnected) + { + await SendTracerAsync(log).ForAwait(); + } + log?.LogInformationOnConnectedAsyncAlreadyConnectedEnd(new(this)); + return "Already connected"; + } + + if (!IsConnected) + { + log?.LogInformationOnConnectedAsyncInit(new(this), interactive?.ConnectionState); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _ = tcs.Task.ContinueWith(t => log?.LogInformationOnConnectedAsyncCompleted(new(this), t.Result)); + lock (_pendingConnectionMonitors) + { + _pendingConnectionMonitors.Add(tcs); + // In case we complete in a race above, before attaching + if (IsConnected) + { + tcs.TrySetResult("Connection race"); + _pendingConnectionMonitors.Remove(tcs); + } + } + return tcs.Task; + } + return IfConnectedAsync(log, sendTracerIfConnected, autoConfigureIfConnected); + } + + internal Exception? LastException + { + get + { + var snapshot = interactive; + var subEx = subscription?.LastException; + var subExData = subEx?.Data; + + // check if subscription endpoint has a better last exception + if (subExData != null && subExData.Contains("Redis-FailureType") && subExData["Redis-FailureType"]?.ToString() != nameof(ConnectionFailureType.UnableToConnect)) + { + return subEx; + } + return snapshot?.LastException; + } + } + + internal State InteractiveConnectionState => interactive?.ConnectionState ?? State.Disconnected; + internal State SubscriptionConnectionState => KnowOrAssumeResp3() ? InteractiveConnectionState : subscription?.ConnectionState ?? State.Disconnected; + + public long OperationCount => interactive?.OperationCount ?? 0 + subscription?.OperationCount ?? 0; + + public bool RequiresReadMode => serverType == ServerType.Cluster && IsReplica; + + public ServerType ServerType + { + get => serverType; + set => SetConfig(ref serverType, value); + } + + public bool IsReplica + { + get => isReplica; + set => SetConfig(ref isReplica, value); + } + + public bool ReplicaReadOnly + { + get => replicaReadOnly; + set => SetConfig(ref replicaReadOnly, value); + } + + public bool AllowReplicaWrites + { + get => allowReplicaWrites; + set + { + allowReplicaWrites = value; + ClearMemoized(); + } + } + + public Version Version + { + get => version; + set => SetConfig(ref version, value); + } + + /// + /// If we have a connection (interactive), report the protocol being used. + /// + public RedisProtocol? Protocol => interactive?.Protocol; + + public int WriteEverySeconds + { + get => writeEverySeconds; + set => SetConfig(ref writeEverySeconds, value); + } + + internal ConnectionMultiplexer Multiplexer { get; } + + public void Dispose() + { + isDisposed = true; + var tmp = interactive; + interactive = null; + tmp?.Dispose(); + + tmp = subscription; + subscription = null; + tmp?.Dispose(); + } + + public PhysicalBridge? GetBridge(ConnectionType type, bool create = true, ILogger? log = null) + { + if (isDisposed) return null; + switch (type) + { + case ConnectionType.Interactive: + case ConnectionType.Subscription when KnowOrAssumeResp3(): + return interactive ?? (create ? interactive = CreateBridge(ConnectionType.Interactive, log) : null); + case ConnectionType.Subscription: + return subscription ?? (create ? subscription = CreateBridge(ConnectionType.Subscription, log) : null); + default: + return null; + } + } + + public PhysicalBridge? GetBridge(Message message) + { + if (isDisposed) return null; + + // Subscription commands go to a specific bridge - so we need to set that up. + // There are other commands we need to send to the right connection (e.g. subscriber PING with an explicit SetForSubscriptionBridge call), + // but these always go subscriber. + switch (message.Command) + { + case RedisCommand.SUBSCRIBE: + case RedisCommand.UNSUBSCRIBE: + case RedisCommand.PSUBSCRIBE: + case RedisCommand.PUNSUBSCRIBE: + case RedisCommand.SSUBSCRIBE: + case RedisCommand.SUNSUBSCRIBE: + message.SetForSubscriptionBridge(); + break; + } + + return (message.IsForSubscriptionBridge && !KnowOrAssumeResp3()) + ? subscription ??= CreateBridge(ConnectionType.Subscription, null) + : interactive ??= CreateBridge(ConnectionType.Interactive, null); + } + + public PhysicalBridge? GetBridge(RedisCommand command, bool create = true) + { + if (isDisposed) return null; + switch (command) + { + case RedisCommand.SUBSCRIBE: + case RedisCommand.UNSUBSCRIBE: + case RedisCommand.PSUBSCRIBE: + case RedisCommand.PUNSUBSCRIBE: + case RedisCommand.SSUBSCRIBE: + case RedisCommand.SUNSUBSCRIBE: + if (!KnowOrAssumeResp3()) + { + return subscription ?? (create ? subscription = CreateBridge(ConnectionType.Subscription, null) : null); + } + break; + } + return interactive ?? (create ? interactive = CreateBridge(ConnectionType.Interactive, null) : null); + } + + public RedisFeatures GetFeatures() => new RedisFeatures(version); + + public void SetClusterConfiguration(ClusterConfiguration configuration) + { + ClusterConfiguration = configuration; + + if (configuration != null) + { + Multiplexer.Trace("Updating cluster ranges..."); + Multiplexer.UpdateClusterRange(configuration); + Multiplexer.Trace("Resolving genealogy..."); + UpdateNodeRelations(configuration); + Multiplexer.Trace("Cluster configured"); + } + } + + public void UpdateNodeRelations(ClusterConfiguration configuration) + { + var thisNode = configuration.Nodes.FirstOrDefault(x => x.EndPoint?.Equals(EndPoint) == true); + if (thisNode != null) + { + Multiplexer.Trace($"Updating node relations for {Format.ToString(thisNode.EndPoint)}..."); + List? replicas = null; + ServerEndPoint? primary = null; + foreach (var node in configuration.Nodes) + { + if (node.NodeId == thisNode.ParentNodeId) + { + primary = Multiplexer.GetServerEndPoint(node.EndPoint); + } + else if (node.ParentNodeId == thisNode.NodeId && node.EndPoint is not null) + { + (replicas ??= new List()).Add(Multiplexer.GetServerEndPoint(node.EndPoint)); + } + } + Primary = primary; + Replicas = replicas?.ToArray() ?? Array.Empty(); + } + } + + public void SetUnselectable(UnselectableFlags flags) + { + if (flags != 0) + { + var oldFlags = unselectableReasons; + unselectableReasons |= flags; + if (unselectableReasons != oldFlags) + { + Multiplexer.Trace(unselectableReasons == 0 ? "Now usable" : ("Now unusable: " + flags), ToString()); + } + } + } + + public void ClearUnselectable(UnselectableFlags flags) + { + var oldFlags = unselectableReasons; + if (oldFlags != 0) + { + unselectableReasons &= ~flags; + if (unselectableReasons != oldFlags) + { + Multiplexer.Trace(unselectableReasons == 0 ? "Now usable" : ("Now unusable: " + flags), ToString()); + } + } + } + + public override string ToString() => Format.ToString(EndPoint); + + [Obsolete("prefer async")] + public WriteResult TryWriteSync(Message message) => GetBridge(message)?.TryWriteSync(message, isReplica) ?? WriteResult.NoConnectionAvailable; + + public ValueTask TryWriteAsync(Message message) => GetBridge(message)?.TryWriteAsync(message, isReplica) ?? new ValueTask(WriteResult.NoConnectionAvailable); + + internal void Activate(ConnectionType type, ILogger? log) => GetBridge(type, true, log); + + internal void AddScript(string script, byte[] hash) + { + lock (knownScripts) + { + knownScripts[script] = hash; + } + } + + internal async Task AutoConfigureAsync(PhysicalConnection? connection, ILogger? log = null) + { + if (!serverType.SupportsAutoConfigure()) + { + // Don't try to detect configuration. + // All the config commands are disabled and the fallback primary/replica detection won't help + return; + } + + log?.LogInformationAutoConfiguring(new(this)); + + var commandMap = Multiplexer.CommandMap; + const CommandFlags flags = CommandFlags.FireAndForget | CommandFlags.NoRedirect; + var features = GetFeatures(); + Message msg; + + var autoConfigProcessor = ResultProcessor.AutoConfigureProcessor.Create(log); + + if (commandMap.IsAvailable(RedisCommand.CONFIG)) + { + if (Multiplexer.RawConfig.KeepAlive <= 0) + { + msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, RedisLiterals.timeout); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); + } + msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, features.ReplicaCommands ? RedisLiterals.replica_read_only : RedisLiterals.slave_read_only); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); + msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, RedisLiterals.databases); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); + } + if (commandMap.IsAvailable(RedisCommand.SENTINEL)) + { + msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTERS); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); + } + if (commandMap.IsAvailable(RedisCommand.INFO)) + { + lastInfoReplicationCheckTicks = Environment.TickCount; + if (features.InfoSections) + { + // note: Redis 7.0 has a multi-section usage, but we don't know + // the server version at this point; we *could* use the optional + // value on the config, but let's keep things simple: these + // commands are suitably cheap + msg = Message.Create(-1, flags, RedisCommand.INFO, RedisLiterals.replication); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); + + msg = Message.Create(-1, flags, RedisCommand.INFO, RedisLiterals.server); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); + } + else + { + msg = Message.Create(-1, flags, RedisCommand.INFO); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); + } + } + else if (commandMap.IsAvailable(RedisCommand.SET)) + { + // This is a nasty way to find if we are a replica, and it will only work on up-level servers, but... + RedisKey key = Multiplexer.UniqueId; + // The actual value here doesn't matter (we detect the error code if it fails). + // The value here is to at least give some indication to anyone watching via "monitor", + // but we could send two GUIDs (key/value) and it would work the same. + msg = Message.Create(0, flags, RedisCommand.SET, key, RedisLiterals.replica_read_only, RedisLiterals.PX, 1, RedisLiterals.NX); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); + } + if (commandMap.IsAvailable(RedisCommand.CLUSTER)) + { + msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.ClusterNodes).ForAwait(); + } + // If we are going to fetch a tie breaker, do so last and we'll get it in before the tracer fires completing the connection + // But if GETs are disabled on this, do not fail the connection - we just don't get tiebreaker benefits + if (Multiplexer.RawConfig.TryGetTieBreaker(out var tieBreakerKey) && Multiplexer.CommandMap.IsAvailable(RedisCommand.GET)) + { + log?.LogInformationRequestingTieBreak(new(EndPoint), tieBreakerKey); + msg = Message.Create(0, flags, RedisCommand.GET, tieBreakerKey); + msg.SetInternalCall(); + msg = LoggingMessage.Create(log, msg); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.TieBreaker).ForAwait(); + } + } + + private int _nextReplicaOffset; + + /// + /// Used to round-robin between multiple replicas. + /// + internal uint NextReplicaOffset() + => (uint)Interlocked.Increment(ref _nextReplicaOffset); + + internal Task Close(ConnectionType connectionType) + { + try + { + var tmp = GetBridge(connectionType, create: false); + if (tmp == null || !tmp.IsConnected || !Multiplexer.CommandMap.IsAvailable(RedisCommand.QUIT)) + { + return Task.CompletedTask; + } + else + { + return WriteDirectAsync(Message.Create(-1, CommandFlags.None, RedisCommand.QUIT), ResultProcessor.DemandOK, bridge: tmp); + } + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + internal void FlushScriptCache() + { + lock (knownScripts) + { + knownScripts.Clear(); + } + } + + private string? runId; + internal string? RunId + { + get => runId; + set + { + // We only care about changes + if (value != runId) + { + // If we had an old run-id, and it has changed, then the server has been restarted + // ...which means the script cache is toast + if (runId != null) + { + FlushScriptCache(); + } + runId = value; + } + } + } + + internal ServerCounters GetCounters() + { + var counters = new ServerCounters(EndPoint); + interactive?.GetCounters(counters.Interactive); + subscription?.GetCounters(counters.Subscription); + return counters; + } + + internal BridgeStatus GetBridgeStatus(ConnectionType connectionType) + { + try + { + return GetBridge(connectionType, false)?.GetStatus() ?? BridgeStatus.Zero; + } + catch (Exception ex) + { + // only needs to be best efforts + System.Diagnostics.Debug.WriteLine(ex.Message); + } + + return BridgeStatus.Zero; + } + + internal string GetProfile() + { + var sb = new StringBuilder(Format.ToString(EndPoint)).Append(": "); + sb.Append("Circular op-count snapshot; int:"); + interactive?.AppendProfile(sb); + sb.Append("; sub:"); + subscription?.AppendProfile(sb); + return sb.ToString(); + } + + internal byte[]? GetScriptHash(string script, RedisCommand command) + { + var found = (byte[]?)knownScripts[script]; + if (found == null && command == RedisCommand.EVALSHA) + { + // The script provided is a hex SHA - store and re-use the ASCii for that + found = Encoding.ASCII.GetBytes(script); + lock (knownScripts) + { + knownScripts[script] = found; + } + } + return found; + } + + internal string? GetStormLog(Message message) => GetBridge(message)?.GetStormLog(); + + internal Message GetTracerMessage(bool checkResponse) + { + // Different configurations block certain commands, as can ad-hoc local configurations, so + // we'll do the best with what we have available. + // Note: muxer-ctor asserts that one of ECHO, PING, TIME of GET is available + // See also: TracerProcessor + var map = Multiplexer.CommandMap; + Message msg; + const CommandFlags flags = CommandFlags.NoRedirect | CommandFlags.FireAndForget; + if (checkResponse && map.IsAvailable(RedisCommand.ECHO)) + { + msg = Message.Create(-1, flags, RedisCommand.ECHO, (RedisValue)Multiplexer.UniqueId); + } + else if (map.IsAvailable(RedisCommand.PING)) + { + msg = Message.Create(-1, flags, RedisCommand.PING); + } + else if (map.IsAvailable(RedisCommand.TIME)) + { + msg = Message.Create(-1, flags, RedisCommand.TIME); + } + else if (!checkResponse && map.IsAvailable(RedisCommand.ECHO)) + { + // We'll use echo as a PING substitute if it is all we have (in preference to EXISTS) + msg = Message.Create(-1, flags, RedisCommand.ECHO, (RedisValue)Multiplexer.UniqueId); + } + else + { + map.AssertAvailable(RedisCommand.EXISTS); + msg = Message.Create(0, flags, RedisCommand.EXISTS, (RedisValue)Multiplexer.UniqueId); + } + msg.SetInternalCall(); + return msg; + } + + internal UnselectableFlags GetUnselectableFlags() => unselectableReasons; + + internal bool IsSelectable(RedisCommand command, bool allowDisconnected = false) + { + // Until we've connected at least once, we're going to have a DidNotRespond unselectable reason present + var bridge = unselectableReasons == 0 || (allowDisconnected && unselectableReasons == UnselectableFlags.DidNotRespond) + ? GetBridge(command, false) + : null; + + return bridge != null && (allowDisconnected || bridge.IsConnected); + } + + private void CompletePendingConnectionMonitors(string source) + { + lock (_pendingConnectionMonitors) + { + foreach (var tcs in _pendingConnectionMonitors) + { + tcs.TrySetResult(source); + } + _pendingConnectionMonitors.Clear(); + } + } + + internal void OnDisconnected(PhysicalBridge bridge) + { + if (bridge == interactive) + { + CompletePendingConnectionMonitors("Disconnected"); + if (Protocol is RedisProtocol.Resp3) + { + Multiplexer.UpdateSubscriptions(); + } + } + else if (bridge == subscription) + { + Multiplexer.UpdateSubscriptions(); + } + } + + internal Task OnEstablishingAsync(PhysicalConnection connection, ILogger? log) + { + static async Task OnEstablishingAsyncAwaited(PhysicalConnection connection, Task handshake) + { + try + { + await handshake.ForAwait(); + } + catch (Exception ex) + { + connection.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); + } + } + + try + { + if (connection == null) return Task.CompletedTask; + + var handshake = HandshakeAsync(connection, log); + + if (handshake.Status != TaskStatus.RanToCompletion) + { + return OnEstablishingAsyncAwaited(connection, handshake); + } + } + catch (Exception ex) + { + connection.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); + } + return Task.CompletedTask; + } + + internal void OnFullyEstablished(PhysicalConnection connection, string source) + { + try + { + var bridge = connection?.BridgeCouldBeNull; + if (bridge != null) + { + // Clear the unselectable flag ASAP since we are open for business + ClearUnselectable(UnselectableFlags.DidNotRespond); + + if (bridge == subscription) + { + // Note: this MUST be fire and forget, because we might be in the middle of a Sync processing + // TracerProcessor which is executing this line inside a SetResultCore(). + // Since we're issuing commands inside a SetResult path in a message, we'd create a deadlock by waiting. + Multiplexer.EnsureSubscriptions(CommandFlags.FireAndForget); + } + if (IsConnected && (IsSubscriberConnected || !SupportsSubscriptions || KnowOrAssumeResp3())) + { + // Only connect on the second leg - we can accomplish this by checking both + // Or the first leg, if we're only making 1 connection because subscriptions aren't supported + CompletePendingConnectionMonitors(source); + } + + Multiplexer.OnConnectionRestored(EndPoint, bridge.ConnectionType, connection?.ToString()); + } + } + catch (Exception ex) + { + connection?.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); + } + } + + internal int LastInfoReplicationCheckSecondsAgo => + unchecked(Environment.TickCount - Volatile.Read(ref lastInfoReplicationCheckTicks)) / 1000; + + private EndPoint? primaryEndPoint; + public EndPoint? PrimaryEndPoint + { + get => primaryEndPoint; + set => SetConfig(ref primaryEndPoint, value); + } + + /// + /// Result of the latest tie breaker (from the last reconfigure). + /// + internal string? TieBreakerResult { get; set; } + + internal bool CheckInfoReplication() + { + lastInfoReplicationCheckTicks = Environment.TickCount; + ResetExponentiallyReplicationCheck(); + + if (version.IsAtLeast(RedisFeatures.v2_8_0) && Multiplexer.CommandMap.IsAvailable(RedisCommand.INFO) + && GetBridge(ConnectionType.Interactive, false) is PhysicalBridge bridge) + { + var msg = Message.Create(-1, CommandFlags.FireAndForget | CommandFlags.NoRedirect, RedisCommand.INFO, RedisLiterals.replication); + msg.SetInternalCall(); + msg.SetSource(ResultProcessor.AutoConfigure, null); +#pragma warning disable CS0618 // Type or member is obsolete + bridge.TryWriteSync(msg, isReplica); +#pragma warning restore CS0618 + return true; + } + return false; + } + + private int lastInfoReplicationCheckTicks; + internal volatile int ConfigCheckSeconds; + [ThreadStatic] + private static Random? r; + + /// + /// Forces frequent replication check starting from 1 second up to max ConfigCheckSeconds with an exponential increment. + /// + internal void ForceExponentialBackoffReplicationCheck() + { + ConfigCheckSeconds = 1; + } + + private void ResetExponentiallyReplicationCheck() + { + if (ConfigCheckSeconds < Multiplexer.RawConfig.ConfigCheckSeconds) + { + r ??= new Random(); + var newExponentialConfigCheck = ConfigCheckSeconds * 2; + var jitter = r.Next(ConfigCheckSeconds + 1, newExponentialConfigCheck); + ConfigCheckSeconds = Math.Min(jitter, Multiplexer.RawConfig.ConfigCheckSeconds); + } + } + + private int _heartBeatActive; + internal void OnHeartbeat() + { + // Don't overlap heartbeat operations on an endpoint + if (Interlocked.CompareExchange(ref _heartBeatActive, 1, 0) == 0) + { + try + { + interactive?.OnHeartbeat(false); + subscription?.OnHeartbeat(false); + } + catch (Exception ex) + { + Multiplexer.OnInternalError(ex, EndPoint); + } + finally + { + Interlocked.Exchange(ref _heartBeatActive, 0); + } + } + } + + internal Task WriteDirectAsync(Message message, ResultProcessor processor, PhysicalBridge? bridge = null) + { + static async Task Awaited(ServerEndPoint @this, Message message, ValueTask write, TaskCompletionSource tcs) + { + var result = await write.ForAwait(); + if (result != WriteResult.Success) + { + var ex = @this.Multiplexer.GetException(result, message, @this); + ConnectionMultiplexer.ThrowFailed(tcs, ex); + } + return await tcs.Task.ForAwait(); + } + + var source = TaskResultBox.Create(out var tcs, null); + message.SetSource(processor, source); + bridge ??= GetBridge(message); + + WriteResult result; + if (bridge == null) + { + result = WriteResult.NoConnectionAvailable; + } + else + { + var write = bridge.TryWriteAsync(message, isReplica); + if (!write.IsCompletedSuccessfully) + { + return Awaited(this, message, write, tcs); + } + result = write.Result; + } + + if (result != WriteResult.Success) + { + var ex = Multiplexer.GetException(result, message, this); + ConnectionMultiplexer.ThrowFailed(tcs, ex); + } + return tcs.Task; + } + + internal void ReportNextFailure() + { + interactive?.ReportNextFailure(); + subscription?.ReportNextFailure(); + } + + internal Task SendTracerAsync(ILogger? log = null) + { + var msg = GetTracerMessage(false); + msg = LoggingMessage.Create(log, msg); + return WriteDirectAsync(msg, ResultProcessor.Tracer); + } + + internal string Summary() + { + var sb = new StringBuilder(Format.ToString(EndPoint)) + .Append(": ").Append(serverType).Append(" v").Append(version).Append(", ").Append(isReplica ? "replica" : "primary"); + + if (databases > 0) sb.Append("; ").Append(databases).Append(" databases"); + if (writeEverySeconds > 0) + sb.Append("; keep-alive: ").Append(TimeSpan.FromSeconds(writeEverySeconds)); + var tmp = interactive; + sb.Append("; int: ").Append(tmp?.ConnectionState.ToString() ?? "n/a"); + tmp = subscription; + if (tmp == null) + { + sb.Append("; sub: n/a"); + } + else + { + var state = tmp.ConnectionState; + sb.Append("; sub: ").Append(state); + if (state == PhysicalBridge.State.ConnectedEstablished) + { + sb.Append(", ").Append(tmp.SubscriptionCount).Append(" active"); + } + } + + var flags = unselectableReasons; + if (flags != 0) + { + sb.Append("; not in use: ").Append(flags); + } + return sb.ToString(); + } + + /// + /// Write the message directly to the pipe or fail...will not queue. + /// + /// The type of the result processor. + internal ValueTask WriteDirectOrQueueFireAndForgetAsync(PhysicalConnection? connection, Message message, ResultProcessor processor) + { + static async ValueTask Awaited(ValueTask l_result) => await l_result.ForAwait(); + + if (message != null) + { + message.SetSource(processor, null); + ValueTask result; + if (connection == null) + { + Multiplexer.Trace($"{Format.ToString(this)}: Enqueue (async): " + message); + // A bridge will be created if missing, so not nullable here + result = GetBridge(message)!.TryWriteAsync(message, isReplica); + } + else + { + Multiplexer.Trace($"{Format.ToString(this)}: Writing direct (async): " + message); + var bridge = connection.BridgeCouldBeNull; + if (bridge == null) + { + throw new ObjectDisposedException(connection.ToString()); + } + else + { + result = bridge.WriteMessageTakingWriteLockAsync(connection, message, bypassBacklog: true); + } + } + + if (!result.IsCompletedSuccessfully) + { + return Awaited(result); + } + } + return default; + } + + private PhysicalBridge? CreateBridge(ConnectionType type, ILogger? log) + { + if (Multiplexer.IsDisposed) return null; + Multiplexer.Trace(type.ToString()); + var bridge = new PhysicalBridge(this, type, Multiplexer.TimeoutMilliseconds); + bridge.TryConnect(log); + return bridge; + } + + private async Task HandshakeAsync(PhysicalConnection connection, ILogger? log) + { + log?.LogInformationServerHandshake(new(this)); + if (connection == null) + { + Multiplexer.Trace("No connection!?"); + return; + } + Message msg; + // Note that we need "" (not null) for password in the case of 'nopass' logins + var config = Multiplexer.RawConfig; + string? user = config.User; + string password = config.Password ?? ""; + + string clientName = Multiplexer.ClientName; + if (!string.IsNullOrWhiteSpace(clientName)) + { + clientName = nameSanitizer.Replace(clientName, ""); + } + + // NOTE: + // we might send the auth and client-name *twice* in RESP3 mode; this is intentional: + // - we don't know for sure which commands are available; HELLO is not always available, + // even on v6 servers, and we don't usually even know the server version yet; likewise, + // CLIENT could be disabled/renamed + // - on an authenticated server, you MUST issue HELLO with AUTH, so we can't avoid it there + // - but if the HELLO with AUTH isn't recognized, we might still need to auth; the following is + // legal in all scenarios, and results in a consistent state: + // + // (auth enabled) + // + // HELLO 3 AUTH {user} {password} SETNAME {client} + // AUTH {user} {password} + // CLIENT SETNAME {client} + // + // (auth disabled) + // + // HELLO 3 SETNAME {client} + // CLIENT SETNAME {client} + // + // this might look a little redundant, but: we only do it once per connection, and it isn't + // many bytes different; this allows us to pipeline the entire handshake without having to + // add latency + + // note on the use of FireAndForget here; in F+F, the result processor is still invoked, which + // is what we need for things to work; what *doesn't* happen is the result-box activation etc; + // that's fine and doesn't cause a problem; if we wanted we could probably just discard (`_ =`) + // the various tasks and just `return connection.FlushAsync();` - however, since handshake is low + // volume, we can afford to optimize for a good stack-trace rather than avoiding state machines. + ResultProcessor? autoConfig = null; + if (Multiplexer.RawConfig.TryResp3()) // note this includes an availability check on HELLO + { + log?.LogInformationAuthenticatingViaHello(new(this)); + var hello = Message.CreateHello(3, user, password, clientName, CommandFlags.FireAndForget); + hello.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, hello, autoConfig ??= ResultProcessor.AutoConfigureProcessor.Create(log)).ForAwait(); + + // note that the server can reject RESP3 via either an -ERR response (HELLO not understood), or by simply saying "nope", + // so we don't set the actual .Protocol until we process the result of the HELLO request + } + else + { + // if we're not even issuing HELLO, we're RESP2 + connection.SetProtocol(RedisProtocol.Resp2); + } + + // note: we auth EVEN IF we have used HELLO to AUTH; because otherwise the fallback/detection path is pure hell, + // and: we're pipelined here, so... meh + if (!string.IsNullOrWhiteSpace(user) && Multiplexer.CommandMap.IsAvailable(RedisCommand.AUTH)) + { + log?.LogInformationAuthenticatingUserPassword(new(this)); + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.AUTH, (RedisValue)user, (RedisValue)password); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); + } + else if (!string.IsNullOrWhiteSpace(password) && Multiplexer.CommandMap.IsAvailable(RedisCommand.AUTH)) + { + log?.LogInformationAuthenticatingPassword(new(this)); + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.AUTH, (RedisValue)password); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); + } + + if (Multiplexer.CommandMap.IsAvailable(RedisCommand.CLIENT)) + { + if (!string.IsNullOrWhiteSpace(clientName)) + { + log?.LogInformationSettingClientName(new(this), clientName); + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETNAME, (RedisValue)clientName); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); + } + + if (config.SetClientLibrary) + { + // note that this is a relatively new feature, but usually we won't know the + // server version, so we will use this speculatively and hope for the best + log?.LogInformationSettingClientLibVer(new(this)); + + var libName = Multiplexer.GetFullLibraryName(); + if (!string.IsNullOrWhiteSpace(libName)) + { + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETINFO, RedisLiterals.lib_name, libName); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); + } + + var version = ClientInfoSanitize(Utils.GetLibVersion()); + if (!string.IsNullOrWhiteSpace(version)) + { + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETINFO, RedisLiterals.lib_ver, version); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); + } + } + + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.ID); + msg.SetInternalCall(); + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfig ??= ResultProcessor.AutoConfigureProcessor.Create(log)).ForAwait(); + } + + var bridge = connection.BridgeCouldBeNull; + if (bridge is null) + { + return; + } + + var connType = bridge.ConnectionType; + if (connType == ConnectionType.Interactive) + { + await AutoConfigureAsync(connection, log).ForAwait(); + } + + var tracer = GetTracerMessage(true); + tracer = LoggingMessage.Create(log, tracer); + log?.LogInformationSendingCriticalTracer(new(this), tracer.CommandAndKey); + await WriteDirectOrQueueFireAndForgetAsync(connection, tracer, ResultProcessor.EstablishConnection).ForAwait(); + + // Note: this **must** be the last thing on the subscription handshake, because after this + // we will be in subscriber mode: regular commands cannot be sent + if (connType == ConnectionType.Subscription) + { + var configChannel = Multiplexer.ConfigurationChangedChannel; + if (configChannel != null) + { + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.SUBSCRIBE, RedisChannel.Literal(configChannel)); + // Note: this is NOT internal, we want it to queue in a backlog for sending when ready if necessary + await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.TrackSubscriptions).ForAwait(); + } + } + log?.LogInformationFlushingOutboundBuffer(new(this)); + await connection.FlushAsync().ForAwait(); + } + + private void SetConfig(ref T field, T value, [CallerMemberName] string? caller = null) + { + if (!EqualityComparer.Default.Equals(field, value)) + { + // multiplexer might be null here in some test scenarios; just roll with it... + Multiplexer?.Trace(caller + " changed from " + field + " to " + value, "Configuration"); + field = value; + ClearMemoized(); + Multiplexer?.ReconfigureIfNeeded(EndPoint, false, caller!); + } + } + internal static string ClientInfoSanitize(string? value) + => string.IsNullOrWhiteSpace(value) ? "" : nameSanitizer.Replace(value!.Trim(), "-"); + + private void ClearMemoized() + { + supportsDatabases = null; + supportsPrimaryWrites = null; + } + + /// + /// For testing only. + /// + internal void SimulateConnectionFailure(SimulatedFailureType failureType) + { + interactive?.SimulateConnectionFailure(failureType); + subscription?.SimulateConnectionFailure(failureType); + } + + internal bool HasPendingCallerFacingItems() + { + // check whichever bridges exist + if (interactive?.HasPendingCallerFacingItems() == true) return true; + return subscription?.HasPendingCallerFacingItems() ?? false; + } + } +} diff --git a/src/StackExchange.Redis/ServerSelectionStrategy.cs b/src/StackExchange.Redis/ServerSelectionStrategy.cs new file mode 100644 index 000000000..ca247c38b --- /dev/null +++ b/src/StackExchange.Redis/ServerSelectionStrategy.cs @@ -0,0 +1,364 @@ +using System; +using System.Buffers; +using System.Diagnostics; +using System.Net; +using System.Threading; + +namespace StackExchange.Redis +{ + internal sealed class ServerSelectionStrategy + { + public const int NoSlot = -1, MultipleSlots = -2; + private const int RedisClusterSlotCount = 16384; + private static readonly ushort[] Crc16tab = new ushort[] + { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, + }; + + private readonly ConnectionMultiplexer multiplexer; + private int anyStartOffset = SharedRandom.Next(); // initialize to a random value so routing isn't uniform + + #if NET6_0_OR_GREATER + private static Random SharedRandom => Random.Shared; + #else + private static Random SharedRandom { get; } = new(); + #endif + + private ServerEndPoint[]? map; + + public ServerSelectionStrategy(ConnectionMultiplexer multiplexer) => this.multiplexer = multiplexer; + + public ServerType ServerType { get; set; } = ServerType.Standalone; + internal static int TotalSlots => RedisClusterSlotCount; + + /// + /// Computes the hash-slot that would be used by the given key. + /// + /// The to determine a slot ID for. + public int HashSlot(in RedisKey key) + { + if (ServerType == ServerType.Standalone || key.IsNull) return NoSlot; + if (key.TryGetSimpleBuffer(out var arr)) // key was constructed from a byte[] + { + return GetClusterSlot(arr); + } + else + { + var length = key.TotalLength(); + if (length <= 256) + { + Span span = stackalloc byte[length]; + var written = key.CopyTo(span); + Debug.Assert(written == length, "key length/write error"); + return GetClusterSlot(span); + } + else + { + arr = ArrayPool.Shared.Rent(length); + var span = new Span(arr, 0, length); + var written = key.CopyTo(span); + Debug.Assert(written == length, "key length/write error"); + var result = GetClusterSlot(span); + ArrayPool.Shared.Return(arr); + return result; + } + } + } + + /// + /// Computes the hash-slot that would be used by the given channel. + /// + /// The to determine a slot ID for. + public int HashSlot(in RedisChannel channel) + // note that the RedisChannel->byte[] converter is always direct, so this is not an alloc + // (we deal with channels far less frequently, so pay the encoding cost up-front) + => ServerType == ServerType.Standalone || channel.IsNull ? NoSlot : GetClusterSlot((byte[])channel!); + + /// + /// Gets the hashslot for a given byte sequence. + /// + /// + /// HASH_SLOT = CRC16(key) mod 16384. + /// + private static unsafe int GetClusterSlot(ReadOnlySpan blob) + { + unchecked + { + fixed (byte* ptr = blob) + { + fixed (ushort* crc16tab = ServerSelectionStrategy.Crc16tab) + { + int offset = 0, count = blob.Length, start, end; + if ((start = IndexOf(ptr, (byte)'{', 0, count - 1)) >= 0 + && (end = IndexOf(ptr, (byte)'}', start + 1, count)) >= 0 + && --end != start) + { + offset = start + 1; + count = end - start; // note we already subtracted one via --end + } + + uint crc = 0; + for (int i = 0; i < count; i++) + crc = ((crc << 8) ^ crc16tab[((crc >> 8) ^ ptr[offset++]) & 0x00FF]) & 0x0000FFFF; + return (int)(crc % RedisClusterSlotCount); + } + } + } + } + + public ServerEndPoint? Select(Message message, bool allowDisconnected = false) + { + int slot = NoSlot; + switch (ServerType) + { + case ServerType.Cluster: + // strictly speaking some proxies use a different hashing algorithm, but the hash-tag behavior is + // the same, so this does a pretty good job of spotting illegal commands before sending them + case ServerType.Twemproxy: + slot = message.GetHashSlot(this); + if (slot == MultipleSlots) throw ExceptionFactory.MultiSlot(multiplexer.RawConfig.IncludeDetailInExceptions, message); + break; + /* just shown for completeness + case ServerType.Standalone: // don't use sharding + case ServerType.Envoyproxy: // defer to the proxy; see #2426 + default: // unknown scenario; defer to the server + break; + */ + } + return Select(slot, message.Command, message.Flags, allowDisconnected); + } + + public ServerEndPoint? Select(RedisCommand command, in RedisKey key, CommandFlags flags, bool allowDisconnected = false) + { + int slot = ServerType == ServerType.Cluster ? HashSlot(key) : NoSlot; + return Select(slot, command, flags, allowDisconnected); + } + + public ServerEndPoint? Select(RedisCommand command, in RedisChannel channel, CommandFlags flags, bool allowDisconnected = false) + { + int slot = ServerType == ServerType.Cluster ? HashSlot(channel) : NoSlot; + return Select(slot, command, flags, allowDisconnected); + } + + public bool TryResend(int hashSlot, Message message, EndPoint endpoint, bool isMoved) + { + try + { + if (ServerType == ServerType.Standalone || hashSlot < 0 || hashSlot >= RedisClusterSlotCount) return false; + + ServerEndPoint server = multiplexer.GetServerEndPoint(endpoint); + if (server != null) + { + bool retry = false; + if ((message.Flags & CommandFlags.NoRedirect) == 0) + { + message.SetAsking(!isMoved); + message.SetNoRedirect(); // once is enough + if (isMoved) message.SetInternalCall(); + + // Note that everything so far is talking about PRIMARY nodes + // We might be wanting a REPLICA, so we'll check + ServerEndPoint? resendVia = null; + var command = message.Command; + switch (Message.GetPrimaryReplicaFlags(message.Flags)) + { + case CommandFlags.DemandMaster: + resendVia = server.IsSelectable(command, isMoved) ? server : null; + break; + case CommandFlags.PreferMaster: + resendVia = server.IsSelectable(command, isMoved) ? server : FindReplica(server, command); + break; + case CommandFlags.PreferReplica: + resendVia = FindReplica(server, command, isMoved) ?? (server.IsSelectable(command, isMoved) ? server : null); + break; + case CommandFlags.DemandReplica: + resendVia = FindReplica(server, command, isMoved); + break; + } + if (resendVia == null) + { + multiplexer.Trace("Unable to resend to " + endpoint); + } + else + { + message.PrepareToResend(resendVia, isMoved); +#pragma warning disable CS0618 // Type or member is obsolete + retry = resendVia.TryWriteSync(message) == WriteResult.Success; +#pragma warning restore CS0618 + } + } + + if (isMoved) // update map; note we can still update the map even if we aren't actually going to resend + { + var arr = MapForMutation(); + var oldServer = arr[hashSlot]; + arr[hashSlot] = server; + if (oldServer != server) + { + multiplexer.OnHashSlotMoved(hashSlot, oldServer?.EndPoint, endpoint); + } + } + + return retry; + } + return false; + } + catch + { + return false; + } + } + + internal static int CombineSlot(int oldSlot, int newSlot) + { + if (oldSlot == MultipleSlots || newSlot == NoSlot) return oldSlot; + if (oldSlot == NoSlot) return newSlot; + return oldSlot == newSlot ? oldSlot : MultipleSlots; + } + + internal int CombineSlot(int oldSlot, in RedisKey key) + { + if (oldSlot == MultipleSlots || key.IsNull) return oldSlot; + + int newSlot = HashSlot(key); + if (oldSlot == NoSlot) return newSlot; + return oldSlot == newSlot ? oldSlot : MultipleSlots; + } + + internal int CountCoveredSlots() + { + var arr = map; + if (arr == null) return 0; + int count = 0; + for (int i = 0; i < arr.Length; i++) + if (arr[i] != null) count++; + return count; + } + + internal void UpdateClusterRange(int fromInclusive, int toInclusive, ServerEndPoint server) + { + var arr = MapForMutation(); + for (int i = fromInclusive; i <= toInclusive; i++) + { + arr[i] = server; + } + } + + private static unsafe int IndexOf(byte* ptr, byte value, int start, int end) + { + for (int offset = start; offset < end; offset++) + if (ptr[offset] == value) return offset; + return -1; + } + + private ServerEndPoint? Any(RedisCommand command, CommandFlags flags, bool allowDisconnected) => + multiplexer.AnyServer(ServerType, (uint)Interlocked.Increment(ref anyStartOffset), command, flags, allowDisconnected); + + private static ServerEndPoint? FindPrimary(ServerEndPoint endpoint, RedisCommand command) + { + ServerEndPoint? cursor = endpoint; + int max = 5; + do + { + if (!cursor.IsReplica && cursor.IsSelectable(command)) return cursor; + + cursor = cursor.Primary; + } + while (cursor != null && --max != 0); + return null; + } + + private static ServerEndPoint? FindReplica(ServerEndPoint endpoint, RedisCommand command, bool allowDisconnected = false) + { + if (endpoint.IsReplica && endpoint.IsSelectable(command, allowDisconnected)) return endpoint; + + var replicas = endpoint.Replicas; + var len = replicas.Length; + uint startOffset = len <= 1 ? 0 : endpoint.NextReplicaOffset(); + for (int i = 0; i < len; i++) + { + endpoint = replicas[(int)(((uint)i + startOffset) % len)]; + if (endpoint.IsReplica && endpoint.IsSelectable(command, allowDisconnected)) return endpoint; + } + return null; + } + + private ServerEndPoint[] MapForMutation() + { + var arr = map; + if (arr == null) + { + lock (this) + { + if (map == null) map = new ServerEndPoint[RedisClusterSlotCount]; + arr = map; + } + } + return arr; + } + + internal ServerEndPoint? Select(int slot, RedisCommand command, CommandFlags flags, bool allowDisconnected) + { + // Only interested in primary/replica preferences + flags = Message.GetPrimaryReplicaFlags(flags); + + ServerEndPoint[]? arr; + if (slot == NoSlot || (arr = map) == null) return Any(command, flags, allowDisconnected); + + ServerEndPoint endpoint = arr[slot]; + ServerEndPoint? testing; // but: ^^^ is the PRIMARY slots; if we want a replica, we need to do some thinking + + if (endpoint != null) + { + switch (flags) + { + case CommandFlags.DemandReplica: + return FindReplica(endpoint, command) ?? Any(command, flags, allowDisconnected); + case CommandFlags.PreferReplica: + testing = FindReplica(endpoint, command); + if (testing is not null) return testing; + break; + case CommandFlags.DemandMaster: + return FindPrimary(endpoint, command) ?? Any(command, flags, allowDisconnected); + case CommandFlags.PreferMaster: + testing = FindPrimary(endpoint, command); + if (testing is not null) return testing; + break; + } + if (endpoint.IsSelectable(command, allowDisconnected)) return endpoint; + } + return Any(command, flags, allowDisconnected); + } + } +} diff --git a/src/StackExchange.Redis/SkipLocalsInit.cs b/src/StackExchange.Redis/SkipLocalsInit.cs new file mode 100644 index 000000000..353b00142 --- /dev/null +++ b/src/StackExchange.Redis/SkipLocalsInit.cs @@ -0,0 +1,14 @@ +// turn off ".locals init"; this gives a small perf boost, but is particularly relevant when stackalloc is used +// side-effects: locals don't have defined zero values; normally this doesn't matter, due to "definite assignment", +// but it *can* be observed when using unsafe code, any "out" method that cheats, or "stackalloc" - the last is +// the most relevant to us, so we have audited that no "stackalloc" use expects the buffers to be zero'd initially +[module:System.Runtime.CompilerServices.SkipLocalsInit] + +#if !NET5_0_OR_GREATER +// when not available, we can spoof it in a private type +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)] + internal sealed class SkipLocalsInitAttribute : Attribute { } +} +#endif diff --git a/src/StackExchange.Redis/SocketManager.cs b/src/StackExchange.Redis/SocketManager.cs new file mode 100644 index 000000000..146e576ff --- /dev/null +++ b/src/StackExchange.Redis/SocketManager.cs @@ -0,0 +1,238 @@ +using System; +using System.IO.Pipelines; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using Pipelines.Sockets.Unofficial; + +namespace StackExchange.Redis +{ + /// + /// A SocketManager monitors multiple sockets for availability of data; this is done using + /// the Socket.Select API and a dedicated reader-thread, which allows for fast responses + /// even when the system is under ambient load. + /// + public sealed partial class SocketManager : IDisposable + { + /// + /// Gets the name of this SocketManager instance. + /// + public string Name { get; } + + /// + /// Creates a new instance. + /// + /// The name for this . + public SocketManager(string name) + : this(name, DEFAULT_WORKERS, SocketManagerOptions.None) { } + + /// + /// Creates a new instance. + /// + /// The name for this . + /// Whether this should use high priority sockets. + public SocketManager(string name, bool useHighPrioritySocketThreads) + : this(name, DEFAULT_WORKERS, UseHighPrioritySocketThreads(useHighPrioritySocketThreads)) { } + + /// + /// Creates a new (optionally named) instance. + /// + /// The name for this . + /// the number of dedicated workers for this . + /// Whether this should use high priority sockets. + public SocketManager(string name, int workerCount, bool useHighPrioritySocketThreads) + : this(name, workerCount, UseHighPrioritySocketThreads(useHighPrioritySocketThreads)) { } + + private static SocketManagerOptions UseHighPrioritySocketThreads(bool value) + => value ? SocketManagerOptions.UseHighPrioritySocketThreads : SocketManagerOptions.None; + + /// + /// Additional options for configuring the socket manager. + /// + [Flags] + public enum SocketManagerOptions + { + /// + /// No additional options. + /// + None = 0, + + /// + /// Whether the should use high priority sockets. + /// + UseHighPrioritySocketThreads = 1 << 0, + + /// + /// Use the regular thread-pool for all scheduling. + /// + UseThreadPool = 1 << 1, + } + + /// + /// Creates a new (optionally named) instance. + /// + /// The name for this . + /// The number of dedicated workers for this . + /// Options to use when creating the socket manager. + public SocketManager(string? name = null, int workerCount = 0, SocketManagerOptions options = SocketManagerOptions.None) + { + if (name.IsNullOrWhiteSpace()) name = GetType().Name; + if (workerCount <= 0) workerCount = DEFAULT_WORKERS; + Name = name; + bool useHighPrioritySocketThreads = (options & SocketManagerOptions.UseHighPrioritySocketThreads) != 0, + useThreadPool = (options & SocketManagerOptions.UseThreadPool) != 0; + + const long Receive_PauseWriterThreshold = 4L * 1024 * 1024 * 1024; // receive: let's give it up to 4GiB of buffer for now + const long Receive_ResumeWriterThreshold = 3L * 1024 * 1024 * 1024; // (large replies get crazy big) + + var defaultPipeOptions = PipeOptions.Default; + + long send_PauseWriterThreshold = Math.Max( + 512 * 1024, // send: let's give it up to 0.5MiB + defaultPipeOptions.PauseWriterThreshold); // or the default, whichever is bigger + long send_ResumeWriterThreshold = Math.Max( + send_PauseWriterThreshold / 2, + defaultPipeOptions.ResumeWriterThreshold); + + Scheduler = PipeScheduler.ThreadPool; + if (!useThreadPool) + { + Scheduler = new DedicatedThreadPoolPipeScheduler( + name: name + ":IO", + workerCount: workerCount, + priority: useHighPrioritySocketThreads ? ThreadPriority.AboveNormal : ThreadPriority.Normal); + } + SendPipeOptions = new PipeOptions( + pool: defaultPipeOptions.Pool, + readerScheduler: Scheduler, + writerScheduler: Scheduler, + pauseWriterThreshold: send_PauseWriterThreshold, + resumeWriterThreshold: send_ResumeWriterThreshold, + minimumSegmentSize: Math.Max(defaultPipeOptions.MinimumSegmentSize, MINIMUM_SEGMENT_SIZE), + useSynchronizationContext: false); + ReceivePipeOptions = new PipeOptions( + pool: defaultPipeOptions.Pool, + readerScheduler: Scheduler, + writerScheduler: Scheduler, + pauseWriterThreshold: Receive_PauseWriterThreshold, + resumeWriterThreshold: Receive_ResumeWriterThreshold, + minimumSegmentSize: Math.Max(defaultPipeOptions.MinimumSegmentSize, MINIMUM_SEGMENT_SIZE), + useSynchronizationContext: false); + } + + /// + /// Default / shared socket manager using a dedicated thread-pool. + /// + public static SocketManager Shared + { + get + { + var shared = s_shared; + if (shared != null) return shared; + try + { + // note: we'll allow a higher max thread count on the shared one + shared = new SocketManager("DefaultSocketManager", DEFAULT_WORKERS * 2, false); + if (Interlocked.CompareExchange(ref s_shared, shared, null) == null) + shared = null; + } + finally { shared?.Dispose(); } + return Volatile.Read(ref s_shared); + } + } + + /// + /// Shared socket manager using the main thread-pool. + /// + public static SocketManager ThreadPool + { + get + { + var shared = s_threadPool; + if (shared != null) return shared; + try + { + // note: we'll allow a higher max thread count on the shared one + shared = new SocketManager("ThreadPoolSocketManager", options: SocketManagerOptions.UseThreadPool); + if (Interlocked.CompareExchange(ref s_threadPool, shared, null) == null) + shared = null; + } + finally { shared?.Dispose(); } + return Volatile.Read(ref s_threadPool); + } + } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + var scheduler = SchedulerPool; + if (scheduler == null) return Name; + return $"{Name} - queue: {scheduler?.TotalServicedByQueue}, pool: {scheduler?.TotalServicedByPool}"; + } + + private static SocketManager? s_shared, s_threadPool; + + private const int DEFAULT_WORKERS = 5, MINIMUM_SEGMENT_SIZE = 8 * 1024; + + internal readonly PipeOptions SendPipeOptions, ReceivePipeOptions; + + internal PipeScheduler Scheduler { get; private set; } + + internal DedicatedThreadPoolPipeScheduler? SchedulerPool => Scheduler as DedicatedThreadPoolPipeScheduler; + + private enum CallbackOperation + { + Read, + Error, + } + + /// + /// Releases all resources associated with this instance. + /// + public void Dispose() + { + DisposeRefs(); + GC.SuppressFinalize(this); + OnDispose(); + } + + private void DisposeRefs() + { + // note: the scheduler *can't* be collected by itself - there will + // be threads, and those threads will be rooting the DedicatedThreadPool; + // but: we can lend a hand! We need to do this even in the finalizer + var tmp = SchedulerPool; + Scheduler = PipeScheduler.ThreadPool; + try { tmp?.Dispose(); } catch { } + } + + /// + /// Releases *appropriate* resources associated with this instance. + /// + ~SocketManager() => DisposeRefs(); + + internal static Socket CreateSocket(EndPoint endpoint) + { + var addressFamily = endpoint.AddressFamily; + var protocolType = addressFamily == AddressFamily.Unix ? ProtocolType.Unspecified : ProtocolType.Tcp; + + var socket = addressFamily == AddressFamily.Unspecified + ? new Socket(SocketType.Stream, protocolType) + : new Socket(addressFamily, SocketType.Stream, protocolType); + SocketConnection.SetRecommendedClientOptions(socket); + // socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, false); + return socket; + } + + partial void OnDispose(); + + internal string? GetState() + { + var s = SchedulerPool; + return s == null ? null : $"{s.AvailableCount} of {s.WorkerCount} available"; + } + } +} diff --git a/src/StackExchange.Redis/StackExchange.Redis.csproj b/src/StackExchange.Redis/StackExchange.Redis.csproj new file mode 100644 index 000000000..b03103656 --- /dev/null +++ b/src/StackExchange.Redis/StackExchange.Redis.csproj @@ -0,0 +1,53 @@ + + + enable + + net461;netstandard2.0;net472;netcoreapp3.1;net6.0;net8.0 + High performance Redis client, incorporating both synchronous and asynchronous usage. + StackExchange.Redis + StackExchange.Redis + StackExchange.Redis + Async;Redis;Cache;PubSub;Messaging + true + $(DefineConstants);VECTOR_SAFE + $(DefineConstants);UNIX_SOCKET + README.md + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/StackExchange.Redis/StreamConstants.cs b/src/StackExchange.Redis/StreamConstants.cs new file mode 100644 index 000000000..929398e4b --- /dev/null +++ b/src/StackExchange.Redis/StreamConstants.cs @@ -0,0 +1,86 @@ +using System; + +namespace StackExchange.Redis +{ + /// + /// Constants representing values used in Redis Stream commands. + /// + internal static class StreamConstants + { + /// + /// The "~" value used with the MAXLEN option. + /// + internal static readonly RedisValue ApproximateMaxLen = "~"; + + /// + /// The "*" value used with the XADD command. + /// + internal static readonly RedisValue AutoGeneratedId = "*"; + + /// + /// The "$" value used in the XGROUP command. Indicates reading only new messages from the stream. + /// + internal static readonly RedisValue NewMessages = "$"; + + /// + /// The "0" value used in the XGROUP command. Indicates reading all messages from the stream. + /// + internal static readonly RedisValue AllMessages = "0"; + + /// + /// The "-" value used in the XRANGE, XREAD, and XREADGROUP commands. Indicates the minimum message ID from the stream. + /// + internal static readonly RedisValue ReadMinValue = "-"; + + /// + /// The "+" value used in the XRANGE, XREAD, and XREADGROUP commands. Indicates the maximum message ID from the stream. + /// + internal static readonly RedisValue ReadMaxValue = "+"; + + /// + /// The ">" value used in the XREADGROUP command. Use this to read messages that have not been delivered to a consumer group. + /// + internal static readonly RedisValue UndeliveredMessages = ">"; + + internal static readonly RedisValue Consumers = "CONSUMERS"; + + internal static readonly RedisValue Count = "COUNT"; + + internal static readonly RedisValue Create = "CREATE"; + + internal static readonly RedisValue DeleteConsumer = "DELCONSUMER"; + + internal static readonly RedisValue Destroy = "DESTROY"; + + internal static readonly RedisValue Group = "GROUP"; + + internal static readonly RedisValue Groups = "GROUPS"; + + internal static readonly RedisValue JustId = "JUSTID"; + + internal static readonly RedisValue SetId = "SETID"; + + internal static readonly RedisValue MaxLen = "MAXLEN"; + internal static readonly RedisValue MinId = "MINID"; + + internal static readonly RedisValue MkStream = "MKSTREAM"; + + internal static readonly RedisValue NoAck = "NOACK"; + + internal static readonly RedisValue Stream = "STREAM"; + + internal static readonly RedisValue Streams = "STREAMS"; + + private static readonly RedisValue KeepRef = "KEEPREF", DelRef = "DELREF", Acked = "ACKED"; + + internal static readonly RedisValue Ids = "IDS"; + + internal static RedisValue GetMode(StreamTrimMode mode) => mode switch + { + StreamTrimMode.KeepReferences => KeepRef, + StreamTrimMode.DeleteReferences => DelRef, + StreamTrimMode.Acknowledged => Acked, + _ => throw new ArgumentOutOfRangeException(nameof(mode)), + }; + } +} diff --git a/StackExchange.Redis/StackExchange/Redis/StringSplits.cs b/src/StackExchange.Redis/StringSplits.cs similarity index 78% rename from StackExchange.Redis/StackExchange/Redis/StringSplits.cs rename to src/StackExchange.Redis/StringSplits.cs index ba52350cd..e8eb42b00 100644 --- a/StackExchange.Redis/StackExchange/Redis/StringSplits.cs +++ b/src/StackExchange.Redis/StringSplits.cs @@ -1,10 +1,9 @@ namespace StackExchange.Redis { - class StringSplits + internal static class StringSplits { public static readonly char[] Space = { ' ' }, Comma = { ',' }; - } } diff --git a/src/StackExchange.Redis/TaskExtensions.cs b/src/StackExchange.Redis/TaskExtensions.cs new file mode 100644 index 000000000..a0994a0b6 --- /dev/null +++ b/src/StackExchange.Redis/TaskExtensions.cs @@ -0,0 +1,97 @@ +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + internal static class TaskExtensions + { + private static readonly Action observeErrors = ObverveErrors; + private static void ObverveErrors(this Task task) + { + if (task != null) GC.KeepAlive(task.Exception); + } + + internal static Task ObserveErrors(this Task task) + { + task.ContinueWith(observeErrors, TaskContinuationOptions.OnlyOnFaulted); + return task; + } + + internal static Task ObserveErrors(this Task task) + { + task.ContinueWith(observeErrors, TaskContinuationOptions.OnlyOnFaulted); + return task; + } + +#if !NET6_0_OR_GREATER + // suboptimal polyfill version of the .NET 6+ API, but reasonable for light use + internal static Task WaitAsync(this Task task, CancellationToken cancellationToken) + { + if (task.IsCompleted || !cancellationToken.CanBeCanceled) return task; + return Wrap(task, cancellationToken); + + static async Task Wrap(Task task, CancellationToken cancellationToken) + { + var tcs = new TaskSourceWithToken(cancellationToken); + using var reg = cancellationToken.Register( + static state => ((TaskSourceWithToken)state!).Cancel(), tcs); + _ = task.ContinueWith( + static (t, state) => + { + var tcs = (TaskSourceWithToken)state!; + if (t.IsCanceled) tcs.TrySetCanceled(); + else if (t.IsFaulted) tcs.TrySetException(t.Exception!); + else tcs.TrySetResult(t.Result); + }, + tcs); + return await tcs.Task; + } + } + + // the point of this type is to combine TCS and CT so that we can use a static + // registration via Register + private sealed class TaskSourceWithToken : TaskCompletionSource + { + public TaskSourceWithToken(CancellationToken cancellationToken) + => _cancellationToken = cancellationToken; + + private readonly CancellationToken _cancellationToken; + + public void Cancel() => TrySetCanceled(_cancellationToken); + } +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ConfiguredTaskAwaitable ForAwait(this Task task) => task.ConfigureAwait(false); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ConfiguredValueTaskAwaitable ForAwait(this in ValueTask task) => task.ConfigureAwait(false); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ConfiguredTaskAwaitable ForAwait(this Task task) => task.ConfigureAwait(false); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ConfiguredValueTaskAwaitable ForAwait(this in ValueTask task) => task.ConfigureAwait(false); + + internal static void RedisFireAndForget(this Task task) => task?.ContinueWith(static t => GC.KeepAlive(t.Exception), TaskContinuationOptions.OnlyOnFaulted); + + /// + /// Licensed to the .NET Foundation under one or more agreements. + /// The .NET Foundation licenses this file to you under the MIT license. + /// + /// Inspired from . + internal static async Task TimeoutAfter(this Task task, int timeoutMs) + { + var cts = new CancellationTokenSource(); + if (task == await Task.WhenAny(task, Task.Delay(timeoutMs, cts.Token)).ForAwait()) + { + cts.Cancel(); + await task.ForAwait(); + return true; + } + else + { + return false; + } + } + } +} diff --git a/src/StackExchange.Redis/TaskSource.cs b/src/StackExchange.Redis/TaskSource.cs new file mode 100644 index 000000000..00f83cb04 --- /dev/null +++ b/src/StackExchange.Redis/TaskSource.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace StackExchange.Redis +{ + internal static class TaskSource + { + /// + /// Create a new TaskCompletion source. + /// + /// The type for the created . + /// The state for the created . + /// The options to apply to the task. + internal static TaskCompletionSource Create(object? asyncState, TaskCreationOptions options = TaskCreationOptions.None) + => new TaskCompletionSource(asyncState, options); + } +} diff --git a/src/StackExchange.Redis/TextWriterLogger.cs b/src/StackExchange.Redis/TextWriterLogger.cs new file mode 100644 index 000000000..4d8507b95 --- /dev/null +++ b/src/StackExchange.Redis/TextWriterLogger.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; +using Microsoft.Extensions.Logging; + +namespace StackExchange.Redis; + +internal sealed class TextWriterLogger : ILogger +{ + private TextWriter? _writer; + private readonly ILogger? _wrapped; + + internal static Action NullWriter = _ => { }; + + public TextWriterLogger(TextWriter writer, ILogger? wrapped) + { + _writer = writer; + _wrapped = wrapped; + } + +#if NET8_0_OR_GREATER + public IDisposable? BeginScope(TState state) where TState : notnull => NothingDisposable.Instance; +#else + public IDisposable BeginScope(TState state) => NothingDisposable.Instance; +#endif + + public bool IsEnabled(LogLevel logLevel) => _writer is not null || _wrapped is not null; + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + _wrapped?.Log(logLevel, eventId, state, exception, formatter); + if (_writer is TextWriter writer) + { + lock (writer) + { + // We check here again because it's possible we've released below, and never want to write past releasing. + if (_writer is TextWriter innerWriter) + { + innerWriter.Write($"{DateTime.UtcNow:HH:mm:ss.ffff}: "); + innerWriter.WriteLine(formatter(state, exception)); + } + } + } + } + + public void Release() + { + // We lock here because we may have piled up on a lock above and still be writing. + // We never want a write to go past the Release(), as many TextWriter implementations are not thread safe. + if (_writer is TextWriter writer) + { + lock (writer) + { + _writer = null; + } + } + } +} + +internal static class TextWriterLoggerExtensions +{ + internal static ILogger? With(this ILogger? logger, TextWriter? writer) => + writer is not null ? new TextWriterLogger(writer, logger) : logger; +} + +internal sealed class NothingDisposable : IDisposable +{ + public static readonly NothingDisposable Instance = new NothingDisposable(); + public void Dispose() { } +} diff --git a/src/StackExchange.Redis/Utils.cs b/src/StackExchange.Redis/Utils.cs new file mode 100644 index 000000000..a4beb0295 --- /dev/null +++ b/src/StackExchange.Redis/Utils.cs @@ -0,0 +1,19 @@ +using System; +using System.Reflection; + +namespace StackExchange.Redis; + +internal static class Utils +{ + private static string? _libVersion; + internal static string GetLibVersion() + { + if (_libVersion == null) + { + var assembly = typeof(ConnectionMultiplexer).Assembly; + _libVersion = ((AssemblyFileVersionAttribute)Attribute.GetCustomAttribute(assembly, typeof(AssemblyFileVersionAttribute))!)?.Version + ?? assembly.GetName().Version!.ToString(); + } + return _libVersion; + } +} diff --git a/src/StackExchange.Redis/ValueStopwatch.cs b/src/StackExchange.Redis/ValueStopwatch.cs new file mode 100644 index 000000000..e7f93b102 --- /dev/null +++ b/src/StackExchange.Redis/ValueStopwatch.cs @@ -0,0 +1,33 @@ +using System; +using System.Diagnostics; + +namespace StackExchange.Redis; + +/// +/// Optimization over . +/// +/// From . +internal struct ValueStopwatch +{ + private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; + private readonly long _startTimestamp; + public bool IsActive => _startTimestamp != 0; + + private ValueStopwatch(long startTimestamp) => _startTimestamp = startTimestamp; + public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp()); + + public int ElapsedMilliseconds => checked((int)GetElapsedTime().TotalMilliseconds); + + public TimeSpan GetElapsedTime() + { + if (!IsActive) + { + throw new InvalidOperationException("An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time."); + } + + var end = Stopwatch.GetTimestamp(); + var timestampDelta = end - _startTimestamp; + var ticks = (long)(TimestampToTicks * timestampDelta); + return new TimeSpan(ticks); + } +} diff --git a/src/StackExchange.Redis/VectorSetAddMessage.cs b/src/StackExchange.Redis/VectorSetAddMessage.cs new file mode 100644 index 000000000..0beb65205 --- /dev/null +++ b/src/StackExchange.Redis/VectorSetAddMessage.cs @@ -0,0 +1,168 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace StackExchange.Redis; + +internal abstract class VectorSetAddMessage( + int db, + CommandFlags flags, + RedisKey key, + int? reducedDimensions, + VectorSetQuantization quantization, + int? buildExplorationFactor, + int? maxConnections, + bool useCheckAndSet) : Message(db, flags, RedisCommand.VADD) +{ + public override int ArgCount => GetArgCount(UseFp32); + + private int GetArgCount(bool packed) + { + var count = 2 + GetElementArgCount(packed); // key, element and either "FP32 {vector}" or VALUES {num}" + if (reducedDimensions.HasValue) count += 2; // [REDUCE {dim}] + + if (useCheckAndSet) count++; // [CAS] + count += quantization switch + { + VectorSetQuantization.None or VectorSetQuantization.Binary => 1, // [NOQUANT] or [BIN] + VectorSetQuantization.Int8 => 0, // implicit + _ => throw new ArgumentOutOfRangeException(nameof(quantization)), + }; + + if (buildExplorationFactor.HasValue) count += 2; // [EF {build-exploration-factor}] + count += GetAttributeArgCount(); // [SETATTR {attributes}] + if (maxConnections.HasValue) count += 2; // [M {numlinks}] + return count; + } + + public abstract int GetElementArgCount(bool packed); + public abstract int GetAttributeArgCount(); + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + => serverSelectionStrategy.HashSlot(key); + + private static readonly bool CanUseFp32 = BitConverter.IsLittleEndian && CheckFp32(); + + private static bool CheckFp32() // check endianness with a known value + { + // ReSharper disable once CompareOfFloatsByEqualityOperator - expect exact + return MemoryMarshal.Cast("\0\0(B"u8)[0] == 42; + } + +#if DEBUG + private static int _fp32Disabled; + internal static bool UseFp32 => CanUseFp32 & Volatile.Read(ref _fp32Disabled) == 0; + internal static void SuppressFp32() => Interlocked.Increment(ref _fp32Disabled); + internal static void RestoreFp32() => Interlocked.Decrement(ref _fp32Disabled); +#else + internal static bool UseFp32 => CanUseFp32; + internal static void SuppressFp32() { } + internal static void RestoreFp32() { } +#endif + + protected abstract void WriteElement(bool packed, PhysicalConnection physical); + + protected override void WriteImpl(PhysicalConnection physical) + { + bool packed = UseFp32; // snapshot to avoid race in debug scenarios + physical.WriteHeader(Command, GetArgCount(packed)); + physical.Write(key); + if (reducedDimensions.HasValue) + { + physical.WriteBulkString("REDUCE"u8); + physical.WriteBulkString(reducedDimensions.GetValueOrDefault()); + } + + WriteElement(packed, physical); + if (useCheckAndSet) physical.WriteBulkString("CAS"u8); + + switch (quantization) + { + case VectorSetQuantization.Int8: + break; + case VectorSetQuantization.None: + physical.WriteBulkString("NOQUANT"u8); + break; + case VectorSetQuantization.Binary: + physical.WriteBulkString("BIN"u8); + break; + default: + throw new ArgumentOutOfRangeException(nameof(quantization)); + } + + if (buildExplorationFactor.HasValue) + { + physical.WriteBulkString("EF"u8); + physical.WriteBulkString(buildExplorationFactor.GetValueOrDefault()); + } + + WriteAttributes(physical); + + if (maxConnections.HasValue) + { + physical.WriteBulkString("M"u8); + physical.WriteBulkString(maxConnections.GetValueOrDefault()); + } + } + + protected abstract void WriteAttributes(PhysicalConnection physical); + + internal sealed class VectorSetAddMemberMessage( + int db, + CommandFlags flags, + RedisKey key, + int? reducedDimensions, + VectorSetQuantization quantization, + int? buildExplorationFactor, + int? maxConnections, + bool useCheckAndSet, + RedisValue element, + ReadOnlyMemory values, + string? attributesJson) : VectorSetAddMessage( + db, + flags, + key, + reducedDimensions, + quantization, + buildExplorationFactor, + maxConnections, + useCheckAndSet) + { + private readonly string? _attributesJson = string.IsNullOrWhiteSpace(attributesJson) ? null : attributesJson; + public override int GetElementArgCount(bool packed) + => 2 // "FP32 {vector}" or "VALUES {num}" + + (packed ? 0 : values.Length); // {vector...}" + + public override int GetAttributeArgCount() + => _attributesJson is null ? 0 : 2; // [SETATTR {attributes}] + + protected override void WriteElement(bool packed, PhysicalConnection physical) + { + if (packed) + { + physical.WriteBulkString("FP32"u8); + physical.WriteBulkString(MemoryMarshal.AsBytes(values.Span)); + } + else + { + physical.WriteBulkString("VALUES"u8); + physical.WriteBulkString(values.Length); + foreach (var val in values.Span) + { + physical.WriteBulkString(val); + } + } + + physical.WriteBulkString(element); + } + + protected override void WriteAttributes(PhysicalConnection physical) + { + if (_attributesJson is not null) + { + physical.WriteBulkString("SETATTR"u8); + physical.WriteBulkString(_attributesJson); + } + } + } +} diff --git a/src/StackExchange.Redis/VectorSetAddRequest.cs b/src/StackExchange.Redis/VectorSetAddRequest.cs new file mode 100644 index 000000000..987118c09 --- /dev/null +++ b/src/StackExchange.Redis/VectorSetAddRequest.cs @@ -0,0 +1,80 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace StackExchange.Redis; + +/// +/// Represents the request for a vectorset add operation. +/// +[Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] +public abstract class VectorSetAddRequest +{ + // polymorphism left open for future, but needs to be handled internally + internal VectorSetAddRequest() + { + } + + /// + /// Add a member to the vectorset. + /// + /// The element name. + /// The vector data. + /// Optional JSON attributes for the element (SETATTR parameter). + public static VectorSetAddRequest Member( + RedisValue element, + ReadOnlyMemory values, +#if NET7_0_OR_GREATER + [StringSyntax(StringSyntaxAttribute.Json)] +#endif + string? attributesJson = null) + => new VectorSetAddMemberRequest(element, values, attributesJson); + + /// + /// Optional check-and-set mode for partial threading (CAS parameter). + /// + public bool UseCheckAndSet { get; set; } + + /// + /// Optional dimension reduction using random projection (REDUCE parameter). + /// + public int? ReducedDimensions { get; set; } + + /// + /// Quantization type - Int8 (Q8), None (NOQUANT), or Binary (BIN). Default: Int8. + /// + public VectorSetQuantization Quantization { get; set; } = VectorSetQuantization.Int8; + + /// + /// Optional HNSW build exploration factor (EF parameter, default: 200). + /// + public int? BuildExplorationFactor { get; set; } + + /// + /// Optional maximum connections per HNSW node (M parameter, default: 16). + /// + public int? MaxConnections { get; set; } + + // snapshot the values; I don't trust people not to mutate the object behind my back + internal abstract VectorSetAddMessage ToMessage(RedisKey key, int db, CommandFlags flags); + + internal sealed class VectorSetAddMemberRequest( + RedisValue element, + ReadOnlyMemory values, + string? attributesJson) + : VectorSetAddRequest + { + internal override VectorSetAddMessage ToMessage(RedisKey key, int db, CommandFlags flags) + => new VectorSetAddMessage.VectorSetAddMemberMessage( + db, + flags, + key, + ReducedDimensions, + Quantization, + BuildExplorationFactor, + MaxConnections, + UseCheckAndSet, + element, + values, + attributesJson); + } +} diff --git a/src/StackExchange.Redis/VectorSetInfo.cs b/src/StackExchange.Redis/VectorSetInfo.cs new file mode 100644 index 000000000..c9277eae5 --- /dev/null +++ b/src/StackExchange.Redis/VectorSetInfo.cs @@ -0,0 +1,54 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace StackExchange.Redis; + +/// +/// Contains metadata information about a vectorset returned by VINFO command. +/// +[Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] +public readonly struct VectorSetInfo( + VectorSetQuantization quantization, + string? quantizationRaw, + int dimension, + long length, + int maxLevel, + long vectorSetUid, + long hnswMaxNodeUid) +{ + /// + /// The quantization type used for vectors in this vectorset. + /// + public VectorSetQuantization Quantization { get; } = quantization; + + /// + /// The raw representation of the quantization type used for vectors in this vectorset. This is only + /// populated if the is . + /// + public string? QuantizationRaw { get; } = quantizationRaw; + + /// + /// The number of dimensions in each vector. + /// + public int Dimension { get; } = dimension; + + /// + /// The number of elements (cardinality) in the vectorset. + /// + public long Length { get; } = length; + + /// + /// The maximum level in the HNSW graph structure. + /// + public int MaxLevel { get; } = maxLevel; + + /// + /// The unique identifier for this vectorset. + /// + public long VectorSetUid { get; } = vectorSetUid; + + /// + /// The maximum node unique identifier in the HNSW graph. + /// + public long HnswMaxNodeUid { get; } = hnswMaxNodeUid; +} diff --git a/src/StackExchange.Redis/VectorSetLink.cs b/src/StackExchange.Redis/VectorSetLink.cs new file mode 100644 index 000000000..c18e8a95f --- /dev/null +++ b/src/StackExchange.Redis/VectorSetLink.cs @@ -0,0 +1,24 @@ +using System.Diagnostics.CodeAnalysis; + +namespace StackExchange.Redis; + +/// +/// Represents a link/connection between members in a vectorset with similarity score. +/// Used by VLINKS command with WITHSCORES option. +/// +[Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] +public readonly struct VectorSetLink(RedisValue member, double score) +{ + /// + /// The linked member name/identifier. + /// + public RedisValue Member { get; } = member; + + /// + /// The similarity score between the queried member and this linked member. + /// + public double Score { get; } = score; + + /// + public override string ToString() => $"{Member}: {Score}"; +} diff --git a/src/StackExchange.Redis/VectorSetQuantization.cs b/src/StackExchange.Redis/VectorSetQuantization.cs new file mode 100644 index 000000000..d78f4b34b --- /dev/null +++ b/src/StackExchange.Redis/VectorSetQuantization.cs @@ -0,0 +1,30 @@ +using System.Diagnostics.CodeAnalysis; + +namespace StackExchange.Redis; + +/// +/// Specifies the quantization type for vectors in a vectorset. +/// +[Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] +public enum VectorSetQuantization +{ + /// + /// Unknown or unrecognized quantization type. + /// + Unknown = 0, + + /// + /// No quantization (full precision). This maps to "NOQUANT" or "f32". + /// + None = 1, + + /// + /// 8-bit integer quantization (default). This maps to "Q8" or "int8". + /// + Int8 = 2, + + /// + /// Binary quantization. This maps to "BIN" or "bin". + /// + Binary = 3, +} diff --git a/src/StackExchange.Redis/VectorSetSimilaritySearchMessage.cs b/src/StackExchange.Redis/VectorSetSimilaritySearchMessage.cs new file mode 100644 index 000000000..1bbc418d5 --- /dev/null +++ b/src/StackExchange.Redis/VectorSetSimilaritySearchMessage.cs @@ -0,0 +1,263 @@ +using System; + +namespace StackExchange.Redis; + +internal abstract class VectorSetSimilaritySearchMessage( + int db, + CommandFlags flags, + VectorSetSimilaritySearchMessage.VsimFlags vsimFlags, + RedisKey key, + int count, + double epsilon, + int searchExplorationFactor, + string? filterExpression, + int maxFilteringEffort) : Message(db, flags, RedisCommand.VSIM) +{ + // For "FP32" and "VALUES" scenarios; in the future we might want other vector sizes / encodings - for + // example, there could be some "FP16" or "FP8" transport that requires a ROM-short or ROM-sbyte from + // the calling code. Or, as a convenience, we might want to allow ROM-double input, but transcode that + // to FP32 on the way out. + internal sealed class VectorSetSimilaritySearchBySingleVectorMessage( + int db, + CommandFlags flags, + VsimFlags vsimFlags, + RedisKey key, + ReadOnlyMemory vector, + int count, + double epsilon, + int searchExplorationFactor, + string? filterExpression, + int maxFilteringEffort) : VectorSetSimilaritySearchMessage(db, flags, vsimFlags, key, count, epsilon, + searchExplorationFactor, filterExpression, maxFilteringEffort) + { + internal override int GetSearchTargetArgCount(bool packed) => + packed ? 2 : 2 + vector.Length; // FP32 {vector} or VALUES {num} {vector} + + internal override void WriteSearchTarget(bool packed, PhysicalConnection physical) + { + if (packed) + { + physical.WriteBulkString("FP32"u8); + physical.WriteBulkString(System.Runtime.InteropServices.MemoryMarshal.AsBytes(vector.Span)); + } + else + { + physical.WriteBulkString("VALUES"u8); + physical.WriteBulkString(vector.Length); + foreach (var val in vector.Span) + { + physical.WriteBulkString(val); + } + } + } + } + + // for "ELE" scenarios + internal sealed class VectorSetSimilaritySearchByMemberMessage( + int db, + CommandFlags flags, + VsimFlags vsimFlags, + RedisKey key, + RedisValue member, + int count, + double epsilon, + int searchExplorationFactor, + string? filterExpression, + int maxFilteringEffort) : VectorSetSimilaritySearchMessage(db, flags, vsimFlags, key, count, epsilon, + searchExplorationFactor, filterExpression, maxFilteringEffort) + { + internal override int GetSearchTargetArgCount(bool packed) => 2; // ELE {member} + + internal override void WriteSearchTarget(bool packed, PhysicalConnection physical) + { + physical.WriteBulkString("ELE"u8); + physical.WriteBulkString(member); + } + } + + internal abstract int GetSearchTargetArgCount(bool packed); + internal abstract void WriteSearchTarget(bool packed, PhysicalConnection physical); + + public ResultProcessor?> GetResultProcessor() => + VectorSetSimilaritySearchProcessor.Instance; + + private sealed class VectorSetSimilaritySearchProcessor : ResultProcessor?> + { + // keep local, since we need to know what flags were being sent + public static readonly VectorSetSimilaritySearchProcessor Instance = new(); + private VectorSetSimilaritySearchProcessor() { } + + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray == ResultType.Array && message is VectorSetSimilaritySearchMessage vssm) + { + if (result.IsNull) + { + SetResult(message, null); + return true; + } + + bool withScores = vssm.HasFlag(VsimFlags.WithScores); + bool withAttribs = vssm.HasFlag(VsimFlags.WithAttributes); + + // in RESP3 mode (only), when both are requested, we get a sub-array per item; weird, but true + bool internalNesting = withScores && withAttribs && connection.Protocol is RedisProtocol.Resp3; + + int rowsPerItem = internalNesting + ? 2 + : 1 + ((withScores ? 1 : 0) + (withAttribs ? 1 : 0)); // each value is separate root element + + var items = result.GetItems(); + var length = checked((int)items.Length) / rowsPerItem; + var lease = Lease.Create(length, clear: false); + var target = lease.Span; + int count = 0; + var iter = items.GetEnumerator(); + for (int i = 0; i < target.Length && iter.MoveNext(); i++) + { + var member = iter.Current.AsRedisValue(); + double score = double.NaN; + string? attributesJson = null; + + if (internalNesting) + { + if (!iter.MoveNext() || iter.Current.Resp2TypeArray != ResultType.Array) break; + if (!iter.Current.IsNull) + { + var subArray = iter.Current.GetItems(); + if (subArray.Length >= 1 && !subArray[0].TryGetDouble(out score)) break; + if (subArray.Length >= 2) attributesJson = subArray[1].GetString(); + } + } + else + { + if (withScores) + { + if (!iter.MoveNext() || !iter.Current.TryGetDouble(out score)) break; + } + + if (withAttribs) + { + if (!iter.MoveNext()) break; + attributesJson = iter.Current.GetString(); + } + } + + target[i] = new VectorSetSimilaritySearchResult(member, score, attributesJson); + count++; + } + + if (count == target.Length) + { + SetResult(message, lease); + return true; + } + + lease.Dispose(); // failed to fill? + } + + return false; + } + } + + [Flags] + internal enum VsimFlags + { + None = 0, + Count = 1 << 0, + WithScores = 1 << 1, + WithAttributes = 1 << 2, + UseExactSearch = 1 << 3, + DisableThreading = 1 << 4, + Epsilon = 1 << 5, + SearchExplorationFactor = 1 << 6, + MaxFilteringEffort = 1 << 7, + FilterExpression = 1 << 8, + } + + private bool HasFlag(VsimFlags flag) => (vsimFlags & flag) != 0; + + public override int ArgCount => GetArgCount(VectorSetAddMessage.UseFp32); + + private int GetArgCount(bool packed) + { + int argCount = 1 + GetSearchTargetArgCount(packed); // {key} and whatever we need for the vector/element portion + if (HasFlag(VsimFlags.WithScores)) argCount++; // [WITHSCORES] + if (HasFlag(VsimFlags.WithAttributes)) argCount++; // [WITHATTRIBS] + if (HasFlag(VsimFlags.Count)) argCount += 2; // [COUNT {count}] + if (HasFlag(VsimFlags.Epsilon)) argCount += 2; // [EPSILON {epsilon}] + if (HasFlag(VsimFlags.SearchExplorationFactor)) argCount += 2; // [EF {search-exploration-factor}] + if (HasFlag(VsimFlags.FilterExpression)) argCount += 2; // [FILTER {filterExpression}] + if (HasFlag(VsimFlags.MaxFilteringEffort)) argCount += 2; // [FILTER-EF {max-filtering-effort}] + if (HasFlag(VsimFlags.UseExactSearch)) argCount++; // [TRUTH] + if (HasFlag(VsimFlags.DisableThreading)) argCount++; // [NOTHREAD] + return argCount; + } + + protected override void WriteImpl(PhysicalConnection physical) + { + // snapshot to avoid race in debug scenarios + bool packed = VectorSetAddMessage.UseFp32; + physical.WriteHeader(Command, GetArgCount(packed)); + + // Write key + physical.Write(key); + + // Write search target: either "ELE {member}" or vector data + WriteSearchTarget(packed, physical); + + if (HasFlag(VsimFlags.WithScores)) + { + physical.WriteBulkString("WITHSCORES"u8); + } + + if (HasFlag(VsimFlags.WithAttributes)) + { + physical.WriteBulkString("WITHATTRIBS"u8); + } + + // Write optional parameters + if (HasFlag(VsimFlags.Count)) + { + physical.WriteBulkString("COUNT"u8); + physical.WriteBulkString(count); + } + + if (HasFlag(VsimFlags.Epsilon)) + { + physical.WriteBulkString("EPSILON"u8); + physical.WriteBulkString(epsilon); + } + + if (HasFlag(VsimFlags.SearchExplorationFactor)) + { + physical.WriteBulkString("EF"u8); + physical.WriteBulkString(searchExplorationFactor); + } + + if (HasFlag(VsimFlags.FilterExpression)) + { + physical.WriteBulkString("FILTER"u8); + physical.WriteBulkString(filterExpression); + } + + if (HasFlag(VsimFlags.MaxFilteringEffort)) + { + physical.WriteBulkString("FILTER-EF"u8); + physical.WriteBulkString(maxFilteringEffort); + } + + if (HasFlag(VsimFlags.UseExactSearch)) + { + physical.WriteBulkString("TRUTH"u8); + } + + if (HasFlag(VsimFlags.DisableThreading)) + { + physical.WriteBulkString("NOTHREAD"u8); + } + } + + public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) + => serverSelectionStrategy.HashSlot(key); +} diff --git a/src/StackExchange.Redis/VectorSetSimilaritySearchRequest.cs b/src/StackExchange.Redis/VectorSetSimilaritySearchRequest.cs new file mode 100644 index 000000000..d0c0fd4cc --- /dev/null +++ b/src/StackExchange.Redis/VectorSetSimilaritySearchRequest.cs @@ -0,0 +1,219 @@ +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using VsimFlags = StackExchange.Redis.VectorSetSimilaritySearchMessage.VsimFlags; + +namespace StackExchange.Redis; + +/// +/// Represents the request for a vector similarity search operation. +/// +[Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] +public abstract class VectorSetSimilaritySearchRequest +{ + internal VectorSetSimilaritySearchRequest() + { + } // polymorphism left open for future, but needs to be handled internally + + private sealed class VectorSetSimilarityByMemberSearchRequest(RedisValue member) : VectorSetSimilaritySearchRequest + { + internal override VectorSetSimilaritySearchMessage ToMessage(RedisKey key, int db, CommandFlags flags) + => new VectorSetSimilaritySearchMessage.VectorSetSimilaritySearchByMemberMessage( + db, + flags, + _vsimFlags, + key, + member, + _count, + _epsilon, + _searchExplorationFactor, + _filterExpression, + _maxFilteringEffort); + } + + private sealed class VectorSetSimilarityVectorSingleSearchRequest(ReadOnlyMemory vector) + : VectorSetSimilaritySearchRequest + { + internal override VectorSetSimilaritySearchMessage ToMessage(RedisKey key, int db, CommandFlags flags) + => new VectorSetSimilaritySearchMessage.VectorSetSimilaritySearchBySingleVectorMessage( + db, + flags, + _vsimFlags, + key, + vector, + _count, + _epsilon, + _searchExplorationFactor, + _filterExpression, + _maxFilteringEffort); + } + + // snapshot the values; I don't trust people not to mutate the object behind my back + internal abstract VectorSetSimilaritySearchMessage ToMessage(RedisKey key, int db, CommandFlags flags); + + /// + /// Create a request to search by an existing member in the index. + /// + /// The member to search for. + public static VectorSetSimilaritySearchRequest ByMember(RedisValue member) + => new VectorSetSimilarityByMemberSearchRequest(member); + + /// + /// Create a request to search by a vector value. + /// + /// The vector value to search for. + public static VectorSetSimilaritySearchRequest ByVector(ReadOnlyMemory vector) + => new VectorSetSimilarityVectorSingleSearchRequest(vector); + + private VsimFlags _vsimFlags; + + // use the flags to reduce storage from N*Nullable + private int _searchExplorationFactor, _maxFilteringEffort, _count; + private double _epsilon; + + private bool HasFlag(VsimFlags flag) => (_vsimFlags & flag) != 0; + + private void SetFlag(VsimFlags flag, bool value) + { + if (value) + { + _vsimFlags |= flag; + } + else + { + _vsimFlags &= ~flag; + } + } + + /// + /// The number of similar vectors to return (COUNT parameter). + /// + public int? Count + { + get => HasFlag(VsimFlags.Count) ? _count : null; + set + { + if (value.HasValue) + { + _count = value.GetValueOrDefault(); + SetFlag(VsimFlags.Count, true); + } + else + { + SetFlag(VsimFlags.Count, false); + } + } + } + + /// + /// Whether to include similarity scores in the results (WITHSCORES parameter). + /// + public bool WithScores + { + get => HasFlag(VsimFlags.WithScores); + set => SetFlag(VsimFlags.WithScores, value); + } + + /// + /// Whether to include JSON attributes in the results (WITHATTRIBS parameter). + /// + public bool WithAttributes + { + get => HasFlag(VsimFlags.WithAttributes); + set => SetFlag(VsimFlags.WithAttributes, value); + } + + /// + /// Optional similarity threshold - only return elements with similarity >= (1 - epsilon) (EPSILON parameter). + /// + public double? Epsilon + { + get => HasFlag(VsimFlags.Epsilon) ? _epsilon : null; + set + { + if (value.HasValue) + { + _epsilon = value.GetValueOrDefault(); + SetFlag(VsimFlags.Epsilon, true); + } + else + { + SetFlag(VsimFlags.Epsilon, false); + } + } + } + + /// + /// Optional search exploration factor for better recall (EF parameter). + /// + public int? SearchExplorationFactor + { + get => HasFlag(VsimFlags.SearchExplorationFactor) ? _searchExplorationFactor : null; + set + { + if (value.HasValue) + { + _searchExplorationFactor = value.GetValueOrDefault(); + SetFlag(VsimFlags.SearchExplorationFactor, true); + } + else + { + SetFlag(VsimFlags.SearchExplorationFactor, false); + } + } + } + + /// + /// Optional maximum filtering attempts (FILTER-EF parameter). + /// + public int? MaxFilteringEffort + { + get => HasFlag(VsimFlags.MaxFilteringEffort) ? _maxFilteringEffort : null; + set + { + if (value.HasValue) + { + _maxFilteringEffort = value.GetValueOrDefault(); + SetFlag(VsimFlags.MaxFilteringEffort, true); + } + else + { + SetFlag(VsimFlags.MaxFilteringEffort, false); + } + } + } + + private string? _filterExpression; + + /// + /// Optional filter expression to restrict results (FILTER parameter); . + /// + public string? FilterExpression + { + get => _filterExpression; + set + { + _filterExpression = value; + SetFlag(VsimFlags.FilterExpression, !string.IsNullOrWhiteSpace(value)); + } + } + + /// + /// Whether to use exact linear scan instead of HNSW (TRUTH parameter). + /// + public bool UseExactSearch + { + get => HasFlag(VsimFlags.UseExactSearch); + set => SetFlag(VsimFlags.UseExactSearch, value); + } + + /// + /// Whether to run search in main thread (NOTHREAD parameter). + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced)] + public bool DisableThreading + { + get => HasFlag(VsimFlags.DisableThreading); + set => SetFlag(VsimFlags.DisableThreading, value); + } +} diff --git a/src/StackExchange.Redis/VectorSetSimilaritySearchResult.cs b/src/StackExchange.Redis/VectorSetSimilaritySearchResult.cs new file mode 100644 index 000000000..fd912898b --- /dev/null +++ b/src/StackExchange.Redis/VectorSetSimilaritySearchResult.cs @@ -0,0 +1,44 @@ +using System.Diagnostics.CodeAnalysis; + +namespace StackExchange.Redis; + +/// +/// Represents a result from vector similarity search operations. +/// +[Experimental(Experiments.VectorSets, UrlFormat = Experiments.UrlFormat)] +public readonly struct VectorSetSimilaritySearchResult(RedisValue member, double score = double.NaN, string? attributesJson = null) +{ + /// + /// The member name/identifier in the vectorset. + /// + public RedisValue Member { get; } = member; + + /// + /// The similarity score (0-1) when WITHSCORES is used, NaN otherwise. + /// A score of 1 means identical vectors, 0 means opposite vectors. + /// + public double Score { get; } = score; + + /// + /// The JSON attributes associated with the member when WITHATTRIBS is used, null otherwise. + /// +#if NET7_0_OR_GREATER + [StringSyntax(StringSyntaxAttribute.Json)] +#endif + public string? AttributesJson { get; } = attributesJson; + + /// + public override string ToString() + { + if (double.IsNaN(Score)) + { + return AttributesJson is null + ? Member.ToString() + : $"{Member}: {AttributesJson}"; + } + + return AttributesJson is null + ? $"{Member} ({Score})" + : $"{Member} ({Score}): {AttributesJson}"; + } +} diff --git a/src/StackExchange.Redis/WriteResult.cs b/src/StackExchange.Redis/WriteResult.cs new file mode 100644 index 000000000..b7e87b915 --- /dev/null +++ b/src/StackExchange.Redis/WriteResult.cs @@ -0,0 +1,9 @@ +namespace StackExchange.Redis; + +internal enum WriteResult +{ + Success, + NoConnectionAvailable, + TimeoutBeforeWrite, + WriteFailure, +} diff --git a/tests/BasicTest/BasicTest.csproj b/tests/BasicTest/BasicTest.csproj new file mode 100644 index 000000000..593d26619 --- /dev/null +++ b/tests/BasicTest/BasicTest.csproj @@ -0,0 +1,20 @@ + + + + StackExchange.Redis.BasicTest .NET Core + net472;net8.0 + BasicTest + Exe + BasicTest + + + + + + + + + + + + diff --git a/tests/BasicTest/Program.cs b/tests/BasicTest/Program.cs new file mode 100644 index 000000000..2977c42c2 --- /dev/null +++ b/tests/BasicTest/Program.cs @@ -0,0 +1,276 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Validators; +using StackExchange.Redis; + +namespace BasicTest +{ + internal static class Program + { + private static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly).Run(args); + } + internal class CustomConfig : ManualConfig + { + protected virtual Job Configure(Job j) + => j.WithGcMode(new GcMode { Force = true }) + // .With(InProcessToolchain.Instance) + ; + + public CustomConfig() + { + AddDiagnoser(MemoryDiagnoser.Default); + AddColumn(StatisticColumn.OperationsPerSecond); + AddValidator(JitOptimizationsValidator.FailOnError); + + AddJob(Configure(Job.Default.WithRuntime(ClrRuntime.Net472))); + AddJob(Configure(Job.Default.WithRuntime(CoreRuntime.Core50))); + } + } + internal sealed class SlowConfig : CustomConfig + { + protected override Job Configure(Job j) + => j.WithLaunchCount(1) + .WithWarmupCount(1) + .WithIterationCount(5); + } + + [Config(typeof(CustomConfig))] + public class RedisBenchmarks : IDisposable + { + private SocketManager mgr; + private ConnectionMultiplexer connection; + private IDatabase db; + + [GlobalSetup] + public void Setup() + { + // Pipelines.Sockets.Unofficial.SocketConnection.AssertDependencies(); + var options = ConfigurationOptions.Parse("127.0.0.1:6379"); + connection = ConnectionMultiplexer.Connect(options); + db = connection.GetDatabase(3); + + db.KeyDelete(GeoKey); + db.GeoAdd(GeoKey, 13.361389, 38.115556, "Palermo "); + db.GeoAdd(GeoKey, 15.087269, 37.502669, "Catania"); + + db.KeyDelete(HashKey); + for (int i = 0; i < 1000; i++) + { + db.HashSet(HashKey, i, i); + } + } + + private static readonly RedisKey GeoKey = "GeoTest", IncrByKey = "counter", StringKey = "string", HashKey = "hash"; + void IDisposable.Dispose() + { + mgr?.Dispose(); + connection?.Dispose(); + mgr = null; + db = null; + connection = null; + GC.SuppressFinalize(this); + } + + private const int COUNT = 50; + + /// + /// Run INCRBY lots of times. + /// + // [Benchmark(Description = "INCRBY/s", OperationsPerInvoke = COUNT)] + public int ExecuteIncrBy() + { + var rand = new Random(12345); + + db.KeyDelete(IncrByKey, CommandFlags.FireAndForget); + int expected = 0; + for (int i = 0; i < COUNT; i++) + { + int x = rand.Next(50); + expected += x; + db.StringIncrement(IncrByKey, x, CommandFlags.FireAndForget); + } + int actual = (int)db.StringGet(IncrByKey); + if (actual != expected) throw new InvalidOperationException($"expected: {expected}, actual: {actual}"); + return actual; + } + + /// + /// Run INCRBY lots of times. + /// + // [Benchmark(Description = "INCRBY/a", OperationsPerInvoke = COUNT)] + public async Task ExecuteIncrByAsync() + { + var rand = new Random(12345); + + db.KeyDelete(IncrByKey, CommandFlags.FireAndForget); + int expected = 0; + for (int i = 0; i < COUNT; i++) + { + int x = rand.Next(50); + expected += x; + await db.StringIncrementAsync(IncrByKey, x, CommandFlags.FireAndForget).ConfigureAwait(false); + } + int actual = (int)await db.StringGetAsync(IncrByKey).ConfigureAwait(false); + if (actual != expected) throw new InvalidOperationException($"expected: {expected}, actual: {actual}"); + return actual; + } + + /// + /// Run GEORADIUS lots of times. + /// + // [Benchmark(Description = "GEORADIUS/s", OperationsPerInvoke = COUNT)] + public int ExecuteGeoRadius() + { + int total = 0; + for (int i = 0; i < COUNT; i++) + { + var results = db.GeoRadius(GeoKey, 15, 37, 200, GeoUnit.Kilometers, options: GeoRadiusOptions.WithCoordinates | GeoRadiusOptions.WithDistance | GeoRadiusOptions.WithGeoHash); + total += results.Length; + } + return total; + } + + /// + /// Run GEORADIUS lots of times. + /// + // [Benchmark(Description = "GEORADIUS/a", OperationsPerInvoke = COUNT)] + public async Task ExecuteGeoRadiusAsync() + { + int total = 0; + for (int i = 0; i < COUNT; i++) + { + var results = await db.GeoRadiusAsync(GeoKey, 15, 37, 200, GeoUnit.Kilometers, options: GeoRadiusOptions.WithCoordinates | GeoRadiusOptions.WithDistance | GeoRadiusOptions.WithGeoHash).ConfigureAwait(false); + total += results.Length; + } + return total; + } + + /// + /// Run StringSet lots of times. + /// + [Benchmark(Description = "StringSet/s", OperationsPerInvoke = COUNT)] + public void StringSet() + { + for (int i = 0; i < COUNT; i++) + { + db.StringSet(StringKey, "hey"); + } + } + + /// + /// Run StringGet lots of times. + /// + [Benchmark(Description = "StringGet/s", OperationsPerInvoke = COUNT)] + public void StringGet() + { + for (int i = 0; i < COUNT; i++) + { + db.StringGet(StringKey); + } + } + + /// + /// Run HashGetAll lots of times. + /// + [Benchmark(Description = "HashGetAll F+F/s", OperationsPerInvoke = COUNT)] + public void HashGetAll_FAF() + { + for (int i = 0; i < COUNT; i++) + { + db.HashGetAll(HashKey, CommandFlags.FireAndForget); + db.Ping(); // to wait for response + } + } + + /// + /// Run HashGetAll lots of times. + /// + [Benchmark(Description = "HashGetAll F+F/a", OperationsPerInvoke = COUNT)] + + public async Task HashGetAllAsync_FAF() + { + for (int i = 0; i < COUNT; i++) + { + await db.HashGetAllAsync(HashKey, CommandFlags.FireAndForget); + await db.PingAsync(); // to wait for response + } + } + } + + [Config(typeof(SlowConfig))] + public class Issue898 : IDisposable + { + private readonly ConnectionMultiplexer mux; + private readonly IDatabase db; + + public void Dispose() + { + mux?.Dispose(); + GC.SuppressFinalize(this); + } + public Issue898() + { + mux = ConnectionMultiplexer.Connect("127.0.0.1:6379"); + db = mux.GetDatabase(); + } + + private const int Max = 100000; + [Benchmark(OperationsPerInvoke = Max)] + public void Load() + { + for (int i = 0; i < Max; ++i) + { + db.StringSet(i.ToString(), i); + } + } + [Benchmark(OperationsPerInvoke = Max)] + public async Task LoadAsync() + { + for (int i = 0; i < Max; ++i) + { + await db.StringSetAsync(i.ToString(), i).ConfigureAwait(false); + } + } + [Benchmark(OperationsPerInvoke = Max)] + public void Sample() + { + var rnd = new Random(); + + for (int i = 0; i < Max; ++i) + { + var r = rnd.Next(0, Max - 1); + + var rv = db.StringGet(r.ToString()); + if (rv != r) + { + throw new Exception($"Unexpected {rv}, expected {r}"); + } + } + } + + [Benchmark(OperationsPerInvoke = Max)] + public async Task SampleAsync() + { + var rnd = new Random(); + + for (int i = 0; i < Max; ++i) + { + var r = rnd.Next(0, Max - 1); + + var rv = await db.StringGetAsync(r.ToString()).ConfigureAwait(false); + if (rv != r) + { + throw new Exception($"Unexpected {rv}, expected {r}"); + } + } + } + } +} diff --git a/tests/BasicTestBaseline/BasicTestBaseline.csproj b/tests/BasicTestBaseline/BasicTestBaseline.csproj new file mode 100644 index 000000000..a9f75e441 --- /dev/null +++ b/tests/BasicTestBaseline/BasicTestBaseline.csproj @@ -0,0 +1,24 @@ + + + + StackExchange.Redis.BasicTest .NET Core + net472;net8.0 + BasicTestBaseline + Exe + BasicTestBaseline + + $(DefineConstants);TEST_BASELINE + + + + + + + + + + + + + + diff --git a/tests/ConsoleTest/ConsoleTest.csproj b/tests/ConsoleTest/ConsoleTest.csproj new file mode 100644 index 000000000..b3c1fd998 --- /dev/null +++ b/tests/ConsoleTest/ConsoleTest.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + Exe + enable + enable + + + + + + diff --git a/tests/ConsoleTest/Program.cs b/tests/ConsoleTest/Program.cs new file mode 100644 index 000000000..98d96f259 --- /dev/null +++ b/tests/ConsoleTest/Program.cs @@ -0,0 +1,188 @@ +using System.Diagnostics; +using System.Reflection; +using StackExchange.Redis; + +Stopwatch stopwatch = new Stopwatch(); +stopwatch.Start(); + +var options = ConfigurationOptions.Parse("127.0.0.1"); +#if !SEREDIS_BASELINE +options.HighIntegrity = false; // as needed +Console.WriteLine($"{nameof(options.HighIntegrity)}: {options.HighIntegrity}"); +#endif + +// options.SocketManager = SocketManager.ThreadPool; +Console.WriteLine("Connecting..."); +var connection = ConnectionMultiplexer.Connect(options); +Console.WriteLine("Connected"); +connection.ConnectionFailed += Connection_ConnectionFailed; + +void Connection_ConnectionFailed(object? sender, ConnectionFailedEventArgs e) +{ + Console.Error.WriteLine($"CONNECTION FAILED: {e.ConnectionType}, {e.FailureType}, {e.Exception}"); +} + +var startTime = DateTime.UtcNow; +var startCpuUsage = Process.GetCurrentProcess().TotalProcessorTime; + +var scenario = args?.Length > 0 ? args[0] : "mass-insert-async"; + +switch (scenario) +{ + case "parallel": + Console.WriteLine("Parallel task test..."); + ParallelTasks(connection); + break; + case "mass-insert": + Console.WriteLine("Mass insert test..."); + MassInsert(connection); + break; + case "mass-insert-async": + Console.WriteLine("Mass insert (async/pipelined) test..."); + await MassInsertAsync(connection); + break; + case "mass-publish": + Console.WriteLine("Mass publish test..."); + MassPublish(connection); + break; + default: + Console.WriteLine("Scenario " + scenario + " is not recognized"); + break; +} + +stopwatch.Stop(); + +Console.WriteLine(""); +Console.WriteLine($"Done. {stopwatch.ElapsedMilliseconds} ms"); + +var endTime = DateTime.UtcNow; +var endCpuUsage = Process.GetCurrentProcess().TotalProcessorTime; +var cpuUsedMs = (endCpuUsage - startCpuUsage).TotalMilliseconds; +var totalMsPassed = (endTime - startTime).TotalMilliseconds; +var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed); +Console.WriteLine("Avg CPU: " + (cpuUsageTotal * 100)); +Console.WriteLine("Lib Version: " + GetLibVersion()); + +static void MassInsert(ConnectionMultiplexer connection) +{ + const int NUM_INSERTIONS = 100_000; + const int BATCH = 5000; + int matchErrors = 0; + + var database = connection.GetDatabase(0); + + for (int i = 0; i < NUM_INSERTIONS; i++) + { + var key = $"StackExchange.Redis.Test.{i}"; + var value = i.ToString(); + + database.StringSet(key, value); + var retrievedValue = database.StringGet(key); + + if (retrievedValue != value) + { + matchErrors++; + } + + if (i > 0 && i % BATCH == 0) + { + Console.WriteLine(i); + } + } + + Console.WriteLine($"Match errors: {matchErrors}"); +} + +static async Task MassInsertAsync(ConnectionMultiplexer connection) +{ + const int NUM_INSERTIONS = 100_000; + const int BATCH = 5000; + int matchErrors = 0; + + var database = connection.GetDatabase(0); + + var outstanding = new List<(Task, Task, string)>(BATCH); + + for (int i = 0; i < NUM_INSERTIONS; i++) + { + var key = $"StackExchange.Redis.Test.{i}"; + var value = i.ToString(); + + var set = database.StringSetAsync(key, value); + var get = database.StringGetAsync(key); + + outstanding.Add((set, get, value)); + + if (i > 0 && i % BATCH == 0) + { + matchErrors += await ValidateAsync(outstanding); + Console.WriteLine(i); + } + } + + matchErrors += await ValidateAsync(outstanding); + + Console.WriteLine($"Match errors: {matchErrors}"); + + static async Task ValidateAsync(List<(Task, Task, string)> outstanding) + { + int matchErrors = 0; + foreach (var row in outstanding) + { + var s = await row.Item2; + await row.Item1; + if (s != row.Item3) + { + matchErrors++; + } + } + outstanding.Clear(); + return matchErrors; + } +} + +static void ParallelTasks(ConnectionMultiplexer connection) +{ + static void ParallelRun(int taskId, ConnectionMultiplexer connection) + { + Console.Write($"{taskId} Started, "); + var database = connection.GetDatabase(0); + + for (int i = 0; i < 100000; i++) + { + database.StringSet(i.ToString(), i.ToString()); + } + + Console.Write($"{taskId} Insert completed, "); + + for (int i = 0; i < 100000; i++) + { + var result = database.StringGet(i.ToString()); + } + Console.Write($"{taskId} Completed, "); + } + + var taskList = new List(); + for (int i = 0; i < 10; i++) + { + var i1 = i; + var task = new Task(() => ParallelRun(i1, connection)); + task.Start(); + taskList.Add(task); + } + Task.WaitAll(taskList.ToArray()); +} + +static void MassPublish(ConnectionMultiplexer connection) +{ + var subscriber = connection.GetSubscriber(); + Parallel.For(0, 1000, _ => subscriber.Publish(new RedisChannel("cache-events:cache-testing", RedisChannel.PatternMode.Literal), "hey")); +} + +static string GetLibVersion() +{ + var assembly = typeof(ConnectionMultiplexer).Assembly; + return (Attribute.GetCustomAttribute(assembly, typeof(AssemblyFileVersionAttribute)) as AssemblyFileVersionAttribute)?.Version + ?? assembly.GetName().Version?.ToString() + ?? "Unknown"; +} diff --git a/tests/ConsoleTestBaseline/ConsoleTestBaseline.csproj b/tests/ConsoleTestBaseline/ConsoleTestBaseline.csproj new file mode 100644 index 000000000..1a6a7149d --- /dev/null +++ b/tests/ConsoleTestBaseline/ConsoleTestBaseline.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + Exe + enable + enable + $(DefineConstants);SEREDIS_BASELINE + + + + + + + + + + diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets new file mode 100644 index 000000000..e1d7c8887 --- /dev/null +++ b/tests/Directory.Build.targets @@ -0,0 +1,6 @@ + + + false + + + diff --git a/tests/RedisConfigs/.docker/Envoy/Dockerfile b/tests/RedisConfigs/.docker/Envoy/Dockerfile new file mode 100644 index 000000000..5c20d350c --- /dev/null +++ b/tests/RedisConfigs/.docker/Envoy/Dockerfile @@ -0,0 +1,6 @@ +FROM envoyproxy/envoy:v1.31-latest + +COPY envoy.yaml /etc/envoy/envoy.yaml +RUN chmod go+r /etc/envoy/envoy.yaml + +EXPOSE 7015 diff --git a/tests/RedisConfigs/.docker/Envoy/envoy.yaml b/tests/RedisConfigs/.docker/Envoy/envoy.yaml new file mode 100644 index 000000000..fe57c8c1f --- /dev/null +++ b/tests/RedisConfigs/.docker/Envoy/envoy.yaml @@ -0,0 +1,35 @@ +admin: + address: { socket_address: { protocol: TCP, address: 0.0.0.0, port_value: 8001 } } +static_resources: + listeners: + - name: redis_listener + address: { socket_address: { protocol: TCP, address: 0.0.0.0, port_value: 7015 } } + filter_chains: + - filters: + - name: envoy.filters.network.redis_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy + stat_prefix: envoy_redis_stats + settings: + op_timeout: 3s + dns_cache_config: + name: dynamic_forward_proxy_cache_config + dns_lookup_family: V4_ONLY + prefix_routes: + catch_all_route: + cluster: redis_cluster + clusters: + - name: redis_cluster + connect_timeout: 3s + type: STRICT_DNS + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: redis_cluster + endpoints: + - lb_endpoints: + - endpoint: { address: { socket_address: { address: redis, port_value: 7000 } } } + - endpoint: { address: { socket_address: { address: redis, port_value: 7001 } } } + - endpoint: { address: { socket_address: { address: redis, port_value: 7002 } } } + - endpoint: { address: { socket_address: { address: redis, port_value: 7003 } } } + - endpoint: { address: { socket_address: { address: redis, port_value: 7004 } } } + - endpoint: { address: { socket_address: { address: redis, port_value: 7005 } } } \ No newline at end of file diff --git a/tests/RedisConfigs/.docker/Redis/Dockerfile b/tests/RedisConfigs/.docker/Redis/Dockerfile new file mode 100644 index 000000000..28bf78ebe --- /dev/null +++ b/tests/RedisConfigs/.docker/Redis/Dockerfile @@ -0,0 +1,23 @@ +FROM redislabs/client-libs-test:8.4-RC1-pre.2 + +COPY --from=configs ./Basic /data/Basic/ +COPY --from=configs ./Failover /data/Failover/ +COPY --from=configs ./Cluster /data/Cluster/ +COPY --from=configs ./Sentinel /data/Sentinel/ +COPY --from=configs ./Certs /Certs/ + +RUN chown -R redis:redis /data +RUN chown -R redis:redis /Certs + +COPY docker-entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +RUN apt-get -y update && apt-get install supervisor -y + +RUN apt-get clean + +ADD supervisord.conf /etc/ + +ENTRYPOINT ["docker-entrypoint.sh"] + +EXPOSE 6379 6380 6381 6382 6383 6384 7000 7001 7002 7003 7004 7005 7010 7011 26379 26380 26381 diff --git a/tests/RedisConfigs/.docker/Redis/docker-entrypoint.sh b/tests/RedisConfigs/.docker/Redis/docker-entrypoint.sh new file mode 100644 index 000000000..6e925d375 --- /dev/null +++ b/tests/RedisConfigs/.docker/Redis/docker-entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +if [ "$#" -ne 0 ]; then + exec "$@" +else + mkdir -p /var/log/supervisor + mkdir -p Temp/ + + supervisord -c /etc/supervisord.conf + sleep 3 + + tail -f /var/log/supervisor/*.log +fi \ No newline at end of file diff --git a/tests/RedisConfigs/.docker/Redis/supervisord.conf b/tests/RedisConfigs/.docker/Redis/supervisord.conf new file mode 100644 index 000000000..e0bd20571 --- /dev/null +++ b/tests/RedisConfigs/.docker/Redis/supervisord.conf @@ -0,0 +1,121 @@ +[supervisord] +nodaemon=false + +[program:primary-6379] +command=/usr/local/bin/redis-server /data/Basic/primary-6379.conf +directory=/data/Basic +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:replica-6380] +command=/usr/local/bin/redis-server /data/Basic/replica-6380.conf +directory=/data/Basic +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:secure-6381] +command=/usr/local/bin/redis-server /data/Basic/secure-6381.conf +directory=/data/Basic +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:tls-6384] +command=/usr/local/bin/redis-server /data/Basic/tls-ciphers-6384.conf +directory=/data/Basic +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:primary-6382] +command=/usr/local/bin/redis-server /data/Failover/primary-6382.conf +directory=/data/Failover +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:replica-6383] +command=/usr/local/bin/redis-server /data/Failover/replica-6383.conf +directory=/data/Failover +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:cluster-7000] +command=/usr/local/bin/redis-server /data/Cluster/cluster-7000.conf +directory=/data/Cluster +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:cluster-7001] +command=/usr/local/bin/redis-server /data/Cluster/cluster-7001.conf +directory=/data/Cluster +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:cluster-7002] +command=/usr/local/bin/redis-server /data/Cluster/cluster-7002.conf +directory=/data/Cluster +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:cluster-7003] +command=/usr/local/bin/redis-server /data/Cluster/cluster-7003.conf +directory=/data/Cluster +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:cluster-7004] +command=/usr/local/bin/redis-server /data/Cluster/cluster-7004.conf +directory=/data/Cluster +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:cluster-7005] +command=/usr/local/bin/redis-server /data/Cluster/cluster-7005.conf +directory=/data/Cluster +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:redis-7010] +command=/usr/local/bin/redis-server /data/Sentinel/redis-7010.conf +directory=/data/Sentinel +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:redis-7011] +command=/usr/local/bin/redis-server /data/Sentinel/redis-7011.conf +directory=/data/Sentinel +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:sentinel-26379] +command=/usr/local/bin/redis-server /data/Sentinel/sentinel-26379.conf --sentinel +directory=/data/Sentinel +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:sentinel-26380] +command=/usr/local/bin/redis-server /data/Sentinel/sentinel-26380.conf --sentinel +directory=/data/Sentinel +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true + +[program:sentinel-26381] +command=/usr/local/bin/redis-server /data/Sentinel/sentinel-26381.conf --sentinel +directory=/data/Sentinel +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +autorestart=true \ No newline at end of file diff --git a/packages/Redis-64.3.0.503/Redis on Windows Release Notes.docx b/tests/RedisConfigs/3.0.503/Redis on Windows Release Notes.docx similarity index 100% rename from packages/Redis-64.3.0.503/Redis on Windows Release Notes.docx rename to tests/RedisConfigs/3.0.503/Redis on Windows Release Notes.docx diff --git a/packages/Redis-64.3.0.503/Redis on Windows.docx b/tests/RedisConfigs/3.0.503/Redis on Windows.docx similarity index 100% rename from packages/Redis-64.3.0.503/Redis on Windows.docx rename to tests/RedisConfigs/3.0.503/Redis on Windows.docx diff --git a/packages/Redis-64.3.0.503/Windows Service Documentation.docx b/tests/RedisConfigs/3.0.503/Windows Service Documentation.docx similarity index 100% rename from packages/Redis-64.3.0.503/Windows Service Documentation.docx rename to tests/RedisConfigs/3.0.503/Windows Service Documentation.docx diff --git a/packages/Redis-64.3.0.503/redis-benchmark.exe b/tests/RedisConfigs/3.0.503/redis-benchmark.exe similarity index 100% rename from packages/Redis-64.3.0.503/redis-benchmark.exe rename to tests/RedisConfigs/3.0.503/redis-benchmark.exe diff --git a/packages/Redis-64.3.0.503/redis-benchmark.pdb b/tests/RedisConfigs/3.0.503/redis-benchmark.pdb similarity index 100% rename from packages/Redis-64.3.0.503/redis-benchmark.pdb rename to tests/RedisConfigs/3.0.503/redis-benchmark.pdb diff --git a/packages/Redis-64.3.0.503/redis-check-aof.exe b/tests/RedisConfigs/3.0.503/redis-check-aof.exe similarity index 100% rename from packages/Redis-64.3.0.503/redis-check-aof.exe rename to tests/RedisConfigs/3.0.503/redis-check-aof.exe diff --git a/packages/Redis-64.3.0.503/redis-check-aof.pdb b/tests/RedisConfigs/3.0.503/redis-check-aof.pdb similarity index 100% rename from packages/Redis-64.3.0.503/redis-check-aof.pdb rename to tests/RedisConfigs/3.0.503/redis-check-aof.pdb diff --git a/packages/Redis-64.3.0.503/redis-check-dump.exe b/tests/RedisConfigs/3.0.503/redis-check-dump.exe similarity index 100% rename from packages/Redis-64.3.0.503/redis-check-dump.exe rename to tests/RedisConfigs/3.0.503/redis-check-dump.exe diff --git a/packages/Redis-64.3.0.503/redis-check-dump.pdb b/tests/RedisConfigs/3.0.503/redis-check-dump.pdb similarity index 100% rename from packages/Redis-64.3.0.503/redis-check-dump.pdb rename to tests/RedisConfigs/3.0.503/redis-check-dump.pdb diff --git a/tests/RedisConfigs/3.0.503/redis-cli.exe b/tests/RedisConfigs/3.0.503/redis-cli.exe new file mode 100644 index 000000000..03e8b1720 Binary files /dev/null and b/tests/RedisConfigs/3.0.503/redis-cli.exe differ diff --git a/packages/Redis-64.3.0.503/redis-cli.pdb b/tests/RedisConfigs/3.0.503/redis-cli.pdb similarity index 100% rename from packages/Redis-64.3.0.503/redis-cli.pdb rename to tests/RedisConfigs/3.0.503/redis-cli.pdb diff --git a/packages/Redis-64.3.0.503/redis-server.exe b/tests/RedisConfigs/3.0.503/redis-server.exe similarity index 100% rename from packages/Redis-64.3.0.503/redis-server.exe rename to tests/RedisConfigs/3.0.503/redis-server.exe diff --git a/packages/Redis-64.3.0.503/redis-server.pdb b/tests/RedisConfigs/3.0.503/redis-server.pdb similarity index 100% rename from packages/Redis-64.3.0.503/redis-server.pdb rename to tests/RedisConfigs/3.0.503/redis-server.pdb diff --git a/packages/Redis-64.3.0.503/redis.windows-service.conf b/tests/RedisConfigs/3.0.503/redis.windows-service.conf similarity index 99% rename from packages/Redis-64.3.0.503/redis.windows-service.conf rename to tests/RedisConfigs/3.0.503/redis.windows-service.conf index 16f41ee5a..ed44371a3 100644 --- a/packages/Redis-64.3.0.503/redis.windows-service.conf +++ b/tests/RedisConfigs/3.0.503/redis.windows-service.conf @@ -516,7 +516,7 @@ slave-priority 100 # If the AOF is enabled on startup Redis will load the AOF, that is the file # with the better durability guarantees. # -# Please check http://redis.io/topics/persistence for more information. +# Please check https://redis.io/topics/persistence for more information. appendonly no @@ -738,7 +738,7 @@ lua-time-limit 5000 # cluster-require-full-coverage yes # In order to setup your cluster make sure to read the documentation -# available at http://redis.io web site. +# available at https://redis.io web site. ################################## SLOW LOG ################################### @@ -788,7 +788,7 @@ latency-monitor-threshold 0 ############################# Event notification ############################## # Redis can notify Pub/Sub clients about events happening in the key space. -# This feature is documented at http://redis.io/topics/notifications +# This feature is documented at https://redis.io/topics/notifications # # For instance if keyspace events notification is enabled, and a client # performs a DEL operation on key "foo" stored in the Database 0, two diff --git a/packages/Redis-64.3.0.503/redis.windows.conf b/tests/RedisConfigs/3.0.503/redis.windows.conf similarity index 99% rename from packages/Redis-64.3.0.503/redis.windows.conf rename to tests/RedisConfigs/3.0.503/redis.windows.conf index 21915cce1..c07a7e9ab 100644 --- a/packages/Redis-64.3.0.503/redis.windows.conf +++ b/tests/RedisConfigs/3.0.503/redis.windows.conf @@ -516,7 +516,7 @@ slave-priority 100 # If the AOF is enabled on startup Redis will load the AOF, that is the file # with the better durability guarantees. # -# Please check http://redis.io/topics/persistence for more information. +# Please check https://redis.io/topics/persistence for more information. appendonly no @@ -738,7 +738,7 @@ lua-time-limit 5000 # cluster-require-full-coverage yes # In order to setup your cluster make sure to read the documentation -# available at http://redis.io web site. +# available at https://redis.io web site. ################################## SLOW LOG ################################### @@ -788,7 +788,7 @@ latency-monitor-threshold 0 ############################# Event notification ############################## # Redis can notify Pub/Sub clients about events happening in the key space. -# This feature is documented at http://redis.io/topics/notifications +# This feature is documented at https://redis.io/topics/notifications # # For instance if keyspace events notification is enabled, and a client # performs a DEL operation on key "foo" stored in the Database 0, two diff --git a/tests/RedisConfigs/Basic/primary-6379-3.0.conf b/tests/RedisConfigs/Basic/primary-6379-3.0.conf new file mode 100644 index 000000000..1f4d96da5 --- /dev/null +++ b/tests/RedisConfigs/Basic/primary-6379-3.0.conf @@ -0,0 +1,9 @@ +port 6379 +repl-diskless-sync yes +repl-diskless-sync-delay 0 +databases 2000 +maxmemory 6gb +dir "../Temp" +appendonly no +dbfilename "primary-6379.rdb" +save "" \ No newline at end of file diff --git a/tests/RedisConfigs/Basic/primary-6379.conf b/tests/RedisConfigs/Basic/primary-6379.conf new file mode 100644 index 000000000..dee83828c --- /dev/null +++ b/tests/RedisConfigs/Basic/primary-6379.conf @@ -0,0 +1,10 @@ +port 6379 +repl-diskless-sync yes +repl-diskless-sync-delay 0 +databases 2000 +maxmemory 6gb +dir "../Temp" +appendonly no +dbfilename "primary-6379.rdb" +save "" +enable-debug-command yes \ No newline at end of file diff --git a/tests/RedisConfigs/Basic/replica-6380.conf b/tests/RedisConfigs/Basic/replica-6380.conf new file mode 100644 index 000000000..8d87e54c2 --- /dev/null +++ b/tests/RedisConfigs/Basic/replica-6380.conf @@ -0,0 +1,10 @@ +port 6380 +slaveof 127.0.0.1 6379 +repl-diskless-sync yes +repl-diskless-sync-delay 0 +databases 2000 +maxmemory 2gb +appendonly no +dir "../Temp" +dbfilename "replica-6380.rdb" +save "" \ No newline at end of file diff --git a/tests/RedisConfigs/Basic/secure-6381.conf b/tests/RedisConfigs/Basic/secure-6381.conf new file mode 100644 index 000000000..bd9359244 --- /dev/null +++ b/tests/RedisConfigs/Basic/secure-6381.conf @@ -0,0 +1,7 @@ +port 6381 +requirepass changeme +databases 2000 +maxmemory 512mb +dir "../Temp" +dbfilename "secure-6381.rdb" +save "" \ No newline at end of file diff --git a/tests/RedisConfigs/Basic/tls-ciphers-6384.conf b/tests/RedisConfigs/Basic/tls-ciphers-6384.conf new file mode 100644 index 000000000..52fc7d7b1 --- /dev/null +++ b/tests/RedisConfigs/Basic/tls-ciphers-6384.conf @@ -0,0 +1,11 @@ +port 0 +tls-port 6384 +timeout 0 +protected-mode no +tls-auth-clients no +tls-ciphers ECDHE-RSA-AES256-GCM-SHA384 +tls-ciphersuites TLS_AES_256_GCM_SHA384 +tls-protocols "TLSv1.2 TLSv1.3" +tls-cert-file /Certs/redis.crt +tls-key-file /Certs/redis.key +tls-ca-cert-file /Certs/ca.crt diff --git a/tests/RedisConfigs/Certs/ca.crt b/tests/RedisConfigs/Certs/ca.crt new file mode 100755 index 000000000..4eee6fbfe --- /dev/null +++ b/tests/RedisConfigs/Certs/ca.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE5jCCAs4CCQCCCQ8gWCbLVjANBgkqhkiG9w0BAQsFADA1MRMwEQYDVQQKDApS +ZWRpcyBUZXN0MR4wHAYDVQQDDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjIw +ODI0MTMwOTEwWhcNMzIwODIxMTMwOTEwWjA1MRMwEQYDVQQKDApSZWRpcyBUZXN0 +MR4wHAYDVQQDDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDDuKJ+rUI5/MmOdc7PmhKlJrceqwnDsgyVtvV5X6Xn +P8OG2jSXSqpmQn32WCDOL0EldTYS5UPIE0dS7XOYKFJaZnlSZtKBVZaam1+T2mMv +/rNjk3qmJpNiFpjJbktEchiwrsF6l91gsNfRdc1XXku9nvLhjEyhpNRZ7NKLT+Vx +F7h3wkEqLJFwzaAxIPPyvt6aQsip5dRfExFSwCLY4PTGzsvfNNauWASFvgh+zk80 +FFTeDm6AZRmMIgizUc+0JK46QposPZHZA4N9/wmNZ3gAGzIEXvIZ1A5Nn/xMmU/7 +3IRdFkE6pZmaCLA5CwE2M8Z8WyYtPTwLGU9c5yjTKrcX69Dy1hzjyk3H+DsqObuR +rpEcCx6x9SlrJQb0zLcumeqNsXSLdLlUwOgGX/d78J3jYEMSwatnU9wTP26nWhXH +b37sQZz+kh9ZM9rlfhzij4eq/4QtDRzLN0G+y6uveujW+s2LXlhY73K6DP7ujUUW +tCYy0X+iw8YfXHYgYyoby84gYETg0kpR1bjUKQL2PNNf0BOKUjQF9K9IPIiQX0v7 +0YFg/2Fs3fidTVPFCwiLGCQzmy6P9VZQ3EkblHcLtoNAaPieoXdX/s/wMXTqj/hU +b9jwmrqJ2sbEb6VBMrrIgCJqz52zQzE+64KgHCmrQR/ABTCUWhgnsDsUGmaHs8y6 +cwIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCSg6CZEvBoRqrOCZ+m4z3pQzt/oRxZ +sUz9ZM94pumEOnbEV0AjObqq2LnNTfuyexguH+5HH4Dr2OTvDIL7xNMor7NSUXGA +yhiu+BxiS1tB1o6IyExwyjpbS61iqtUX09abSWP/+2UW1i8oEIuwUmkRlAxbxFWI +HN6+LFe1L32fbrbp90sRS96bljvxNBxGpYqcooLAHCbK2T6jHDAZF0cK5ByWZoJ4 +FcD3tRYWelj8k80ZeoG4PIsCZylsSMPWeglbFqDV4gSpWx7nb4Pgpzs9REp02Cp0 +4MWxAt2fmvPFn9xypeyo6gxZ+R2cmSKiu0sdVnp3u1RscH1aGnVJTpdygpuDYJQ7 +hxn1Wv91zRi+h4MfVywSO/3gMIvdiJIiV7avgNEWiLXYUn2bb4gHzEMOrp2Z7BUp +/SwNHmikaWQj0MY6sOW7pOaistbokyyZw8TjgrTnS4GocN91h16JbuSgAI+Nrwsa +VcFvDCd7qSmJgeMfGhhlOdNenDGXsA9UVyTTkGfw3mgcm62uvewmj1I70ogk8M3z +khwAMI2OeagmHtXtjtg2BULM53IwFHJKV41B4rwekcMkboCsOfbhZwz42aLpT0YG +d0btKJkNcL7n8QiGtFmvreizrdyC5r23GNVnNdn2dhuJBqN65xJQoXh0x1PTnK7/ +4IWfRo8kosNhmw== +-----END CERTIFICATE----- diff --git a/tests/RedisConfigs/Certs/redis.crt b/tests/RedisConfigs/Certs/redis.crt new file mode 100755 index 000000000..cb69138e7 --- /dev/null +++ b/tests/RedisConfigs/Certs/redis.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID3TCCAcUCCQDIqu2SpngxXjANBgkqhkiG9w0BAQsFADA1MRMwEQYDVQQKDApS +ZWRpcyBUZXN0MR4wHAYDVQQDDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjIw +ODI0MTMwOTEwWhcNMjMwODI0MTMwOTEwWjAsMRMwEQYDVQQKDApSZWRpcyBUZXN0 +MRUwEwYDVQQDDAxHZW5lcmljLWNlcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDCtcwbyBNSBgM4Ym72Sz7grrpYtx43JQtgiz4o7rYfCjoxkEcWic2J +3/UC2nqbtmb2vUOTyqxe5VUh6bXHB3OaZfLkyyGJM8dJN3p3rC8Ef4zr2CkCzpAK +jquXz0do9epXrUZCsYSdw1pOZDsRXx9ZgImtvB5Yj0UXfatAQvt9xk7UdxIrNDs4 +r0V34gZVvU4OhFnTEQVwLYqi0VOiknKRtW9BaD0niObjdc+aMmVYBo00G0UmFO4A +UuanO6PJz3FiX+ejY+aViBCD4lJUbuH719/EwWXYNxXbZasC5I0EE6zU0PEOcECm +cbWlSS23eid06HuaqRmcEwTNKPk0/CVjAgMBAAEwDQYJKoZIhvcNAQELBQADggIB +AJOtU8CutoZncckfG+6YL1raeqAcnCYvm0bL3RTs4OYMwHDPOIBCSG6kqyIKiTc2 +uU2G2XUZcWs2a3MxhoOgZhl0TDQBgSQMnMcr/d91IBNwUnRWA553KSpBhOn31SGr +fo8U4IOMz9I/wJ05AFt0bE4WDfm73tiwsIx/2SMn75/d5UgY+II7epx+MpIrWGpT +SwBbm7is9Go/Mwr1bdNy35lrUAL+Si80aHhVPWa+bIFqyqsWal+iZZND+NrqilJe +y27Syhikq0R+U8gPjSdIT2OYj7kwrUZI1exOzpUDa9gUjfy13+lLJWxPbgQEc7Uq +hyu6+CaY9q9YNT6eIIymdLtGTSs/rMYLACHylS/J4WNXr/YCmk2xhGqGDlPq3wjw +Q5WtmdHDaSQXo2+H9fQbw2N2loQ29Gcz4FEgF1CVhbuCZUstelDl6F38cvgRHPrY +gLro6ijlxtfvka6GOZZeVksJWaW9ikAz+aw3yqKQoFMnILjvwxpuCTphvgvlKIb4 +TFg5DU+a+RHW/S3qP3PCatw+f/FaFkRavD2P9oNz0XAcmLld0iWbDXHntDBF1q0N +c9bgdoP9pVS+NKb6Hq/zf2kUC7AseUiLAju5iMQVglunhNcbm/H6RnxfnYekUMkp +DdenAmOqjXa/n5IQkfwOxW97EJyI9SGo3Is+DKgUEmd4 +-----END CERTIFICATE----- diff --git a/tests/RedisConfigs/Certs/redis.key b/tests/RedisConfigs/Certs/redis.key new file mode 100755 index 000000000..56f301528 --- /dev/null +++ b/tests/RedisConfigs/Certs/redis.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAwrXMG8gTUgYDOGJu9ks+4K66WLceNyULYIs+KO62Hwo6MZBH +FonNid/1Atp6m7Zm9r1Dk8qsXuVVIem1xwdzmmXy5MshiTPHSTd6d6wvBH+M69gp +As6QCo6rl89HaPXqV61GQrGEncNaTmQ7EV8fWYCJrbweWI9FF32rQEL7fcZO1HcS +KzQ7OK9Fd+IGVb1ODoRZ0xEFcC2KotFTopJykbVvQWg9J4jm43XPmjJlWAaNNBtF +JhTuAFLmpzujyc9xYl/no2PmlYgQg+JSVG7h+9ffxMFl2DcV22WrAuSNBBOs1NDx +DnBApnG1pUktt3ondOh7mqkZnBMEzSj5NPwlYwIDAQABAoIBAQC8kO2r1hcH77S8 +rW+C7Rpm5DCp7CXCCAk9pXw8jfook3IKQAzogephZVhWPBpTpNGQkXjZr4VBnd3V +qw4VQ10soSEbfLHsuw18FdNwBHvAYnqqiTwmcL/Eyajaq64fs1EROkj6HAsv8loJ +4z3lM/cbacVsUOwenhmuh1ELOhNvGKQuCzSpCoVykP2cWCMnqHEl43Ilqm5tga23 +PtMJS1jM6IazE6EzfelwuGGCEmKK5EKeDHB+3PU4sUSHfXv/l17adSJtDbiK/JiH +2Op3DzSGWZ2xhkYt35Oj7auxJ90f3BoG0/JZABdiaZu3DOgp9JNzqr1sH4rFPWfe +dWBk665JAoGBAPrRhcjkHmRVvQwViGrzE8RUqblW6Wd6xVcf7KbparKjcFbPkoo1 +3NJpKonkvyLj3WQdltXNXuvM+QcdT2TNFv+hywkCC7Wxb/MPZ2eF+nRBMg59209T +eAWjq9GflPn7uO/4jnfCLCR+DNiEvctJ1nHf0qBTVC+s+QyhO9ilZd1PAoGBAMa7 +imK7XH7kX0zpsafPLiORr7XvOzDo/NE/kpKHisdre8VL397KlVsQmQldx33zRa7g +ctCIGjQcsnitpa24vS2G4wru3fqGbKqf3tASoC9yNMRxIBDxlhsASe0TczRw4HKT +i2HMlb7rDZdXa9mY+eDszOUUnGtkmX/D372fcTmtAoGBAOOpFoQX+zYbVLMJQH/D +D2gfaMbgCo9wsnq4cXe3Wq+3Bhrl4h8tcLhT2Na9GHi015kt+mEqPkROEqPQiOX3 ++i4iT0Zn4vUSj4jRrIwc4g5vtt3Mgynnm4OS4jwtW23kfCLlO3ucdbDR8Rr+sb85 +0DogbPA1cq6rlItQNiAZUPKlAoGAKqEL/EXIf4epUaxHaYGtmf+kO1iHz+QKZzBF +1pywjjpmIFo4OWgnRZN34GR3aHMInYyT1Ft9k3QcbHqDMZKRMfTfOvcmMpknMip8 +9xEnv0W2P/UsNbY8xqn3MZ2cdsFHxAwWN/JUpNFy5uXfwptn7nGdOf6D1x2LN7bi +haBv/zkCgYAqSHcp5ETqJMMz/v3H3eLDu/Xc//SdyqldKEjmv+kd0BYQNqHB9rEB +B4rtRVeWUZ6TA5x1T8dK5OaDZ+W+vdnzmOGw27eFuD+203m76+3cJS37mroEcNPt +5npe1IydjS2qU8iA8lhDeIWr2dTnrQnBtgkKiJvYbP2XG5/LahxixA== +-----END RSA PRIVATE KEY----- diff --git a/tests/RedisConfigs/Cluster/cluster-7000.conf b/tests/RedisConfigs/Cluster/cluster-7000.conf new file mode 100644 index 000000000..f250a3db3 --- /dev/null +++ b/tests/RedisConfigs/Cluster/cluster-7000.conf @@ -0,0 +1,9 @@ +port 7000 +cluster-enabled yes +dir "../Temp" +cluster-config-file "../Cluster/nodes-7000.conf" +cluster-node-timeout 5000 +appendonly yes +dbfilename "dump-7000.rdb" +appendfilename "appendonly-7000.aof" +save "" \ No newline at end of file diff --git a/tests/RedisConfigs/Cluster/cluster-7001.conf b/tests/RedisConfigs/Cluster/cluster-7001.conf new file mode 100644 index 000000000..1ae0c6f83 --- /dev/null +++ b/tests/RedisConfigs/Cluster/cluster-7001.conf @@ -0,0 +1,9 @@ +port 7001 +cluster-enabled yes +dir "../Temp" +cluster-config-file "../Cluster/nodes-7001.conf" +cluster-node-timeout 5000 +appendonly yes +dbfilename "dump-7001.rdb" +appendfilename "appendonly-7001.aof" +save "" \ No newline at end of file diff --git a/tests/RedisConfigs/Cluster/cluster-7002.conf b/tests/RedisConfigs/Cluster/cluster-7002.conf new file mode 100644 index 000000000..897301f59 --- /dev/null +++ b/tests/RedisConfigs/Cluster/cluster-7002.conf @@ -0,0 +1,9 @@ +port 7002 +cluster-enabled yes +dir "../Temp" +cluster-config-file "../Cluster/nodes-7002.conf" +cluster-node-timeout 5000 +appendonly yes +dbfilename "dump-7002.rdb" +appendfilename "appendonly-7002.aof" +save "" \ No newline at end of file diff --git a/tests/RedisConfigs/Cluster/cluster-7003.conf b/tests/RedisConfigs/Cluster/cluster-7003.conf new file mode 100644 index 000000000..0b51677fd --- /dev/null +++ b/tests/RedisConfigs/Cluster/cluster-7003.conf @@ -0,0 +1,9 @@ +port 7003 +cluster-enabled yes +dir "../Temp" +cluster-config-file "../Cluster/nodes-7003.conf" +cluster-node-timeout 5000 +appendonly yes +dbfilename "dump-7003.rdb" +appendfilename "appendonly-7003.aof" +save "" \ No newline at end of file diff --git a/tests/RedisConfigs/Cluster/cluster-7004.conf b/tests/RedisConfigs/Cluster/cluster-7004.conf new file mode 100644 index 000000000..9a49d21f5 --- /dev/null +++ b/tests/RedisConfigs/Cluster/cluster-7004.conf @@ -0,0 +1,9 @@ +port 7004 +cluster-enabled yes +dir "../Temp" +cluster-config-file "../Cluster/nodes-7004.conf" +cluster-node-timeout 5000 +appendonly yes +dbfilename "dump-7004.rdb" +appendfilename "appendonly-7004.aof" +save "" \ No newline at end of file diff --git a/tests/RedisConfigs/Cluster/cluster-7005.conf b/tests/RedisConfigs/Cluster/cluster-7005.conf new file mode 100644 index 000000000..b333a4b44 --- /dev/null +++ b/tests/RedisConfigs/Cluster/cluster-7005.conf @@ -0,0 +1,9 @@ +port 7005 +cluster-enabled yes +dir "../Temp" +cluster-config-file "../Cluster/nodes-7005.conf" +cluster-node-timeout 5000 +appendonly yes +dbfilename "dump-7005.rdb" +appendfilename "appendonly-7005.aof" +save "" \ No newline at end of file diff --git a/Redis Configs/Cluster/7000/nodes.conf b/tests/RedisConfigs/Cluster/nodes-7000.conf similarity index 100% rename from Redis Configs/Cluster/7000/nodes.conf rename to tests/RedisConfigs/Cluster/nodes-7000.conf diff --git a/Redis Configs/Cluster/7001/nodes.conf b/tests/RedisConfigs/Cluster/nodes-7001.conf similarity index 100% rename from Redis Configs/Cluster/7001/nodes.conf rename to tests/RedisConfigs/Cluster/nodes-7001.conf diff --git a/Redis Configs/Cluster/7002/nodes.conf b/tests/RedisConfigs/Cluster/nodes-7002.conf similarity index 100% rename from Redis Configs/Cluster/7002/nodes.conf rename to tests/RedisConfigs/Cluster/nodes-7002.conf diff --git a/Redis Configs/Cluster/7003/nodes.conf b/tests/RedisConfigs/Cluster/nodes-7003.conf similarity index 100% rename from Redis Configs/Cluster/7003/nodes.conf rename to tests/RedisConfigs/Cluster/nodes-7003.conf diff --git a/Redis Configs/Cluster/7004/nodes.conf b/tests/RedisConfigs/Cluster/nodes-7004.conf similarity index 100% rename from Redis Configs/Cluster/7004/nodes.conf rename to tests/RedisConfigs/Cluster/nodes-7004.conf diff --git a/Redis Configs/Cluster/7005/nodes.conf b/tests/RedisConfigs/Cluster/nodes-7005.conf similarity index 100% rename from Redis Configs/Cluster/7005/nodes.conf rename to tests/RedisConfigs/Cluster/nodes-7005.conf diff --git a/tests/RedisConfigs/Failover/primary-6382.conf b/tests/RedisConfigs/Failover/primary-6382.conf new file mode 100644 index 000000000..c19e8c701 --- /dev/null +++ b/tests/RedisConfigs/Failover/primary-6382.conf @@ -0,0 +1,9 @@ +port 6382 +repl-diskless-sync yes +repl-diskless-sync-delay 0 +databases 2000 +maxmemory 2gb +dir "../Temp" +appendonly no +dbfilename "primary-6382.rdb" +save "" \ No newline at end of file diff --git a/tests/RedisConfigs/Failover/replica-6383.conf b/tests/RedisConfigs/Failover/replica-6383.conf new file mode 100644 index 000000000..6f1a0fc7d --- /dev/null +++ b/tests/RedisConfigs/Failover/replica-6383.conf @@ -0,0 +1,10 @@ +port 6383 +slaveof 127.0.0.1 6382 +repl-diskless-sync yes +repl-diskless-sync-delay 0 +databases 2000 +maxmemory 2gb +appendonly no +dir "../Temp" +dbfilename "replica-6383.rdb" +save "" \ No newline at end of file diff --git a/tests/RedisConfigs/Sentinel/redis-7010.conf b/tests/RedisConfigs/Sentinel/redis-7010.conf new file mode 100644 index 000000000..0e27680b2 --- /dev/null +++ b/tests/RedisConfigs/Sentinel/redis-7010.conf @@ -0,0 +1,8 @@ +port 7010 +repl-diskless-sync yes +repl-diskless-sync-delay 0 +maxmemory 100mb +appendonly no +dir "../Temp" +dbfilename "sentinel-target-7010.rdb" +save "" \ No newline at end of file diff --git a/tests/RedisConfigs/Sentinel/redis-7011.conf b/tests/RedisConfigs/Sentinel/redis-7011.conf new file mode 100644 index 000000000..6d02eb150 --- /dev/null +++ b/tests/RedisConfigs/Sentinel/redis-7011.conf @@ -0,0 +1,9 @@ +port 7011 +slaveof 127.0.0.1 7010 +repl-diskless-sync yes +repl-diskless-sync-delay 0 +maxmemory 100mb +appendonly no +dir "../Temp" +dbfilename "sentinel-target-7011.rdb" +save "" \ No newline at end of file diff --git a/tests/RedisConfigs/Sentinel/sentinel-26379.conf b/tests/RedisConfigs/Sentinel/sentinel-26379.conf new file mode 100644 index 000000000..27cefe69a --- /dev/null +++ b/tests/RedisConfigs/Sentinel/sentinel-26379.conf @@ -0,0 +1,6 @@ +port 26379 +sentinel monitor myprimary 127.0.0.1 7010 1 +sentinel down-after-milliseconds myprimary 1000 +sentinel failover-timeout myprimary 1000 +sentinel config-epoch myprimary 0 +dir "../Temp" \ No newline at end of file diff --git a/tests/RedisConfigs/Sentinel/sentinel-26380.conf b/tests/RedisConfigs/Sentinel/sentinel-26380.conf new file mode 100644 index 000000000..b01a4d080 --- /dev/null +++ b/tests/RedisConfigs/Sentinel/sentinel-26380.conf @@ -0,0 +1,6 @@ +port 26380 +sentinel monitor myprimary 127.0.0.1 7010 1 +sentinel down-after-milliseconds myprimary 1000 +sentinel failover-timeout myprimary 1000 +sentinel config-epoch myprimary 0 +dir "../Temp" \ No newline at end of file diff --git a/tests/RedisConfigs/Sentinel/sentinel-26381.conf b/tests/RedisConfigs/Sentinel/sentinel-26381.conf new file mode 100644 index 000000000..ee8022a5a --- /dev/null +++ b/tests/RedisConfigs/Sentinel/sentinel-26381.conf @@ -0,0 +1,6 @@ +port 26381 +sentinel monitor myprimary 127.0.0.1 7010 1 +sentinel down-after-milliseconds myprimary 1000 +sentinel failover-timeout myprimary 1000 +sentinel config-epoch myprimary 0 +dir "../Temp" diff --git a/tests/RedisConfigs/Temp/.gitignore b/tests/RedisConfigs/Temp/.gitignore new file mode 100644 index 000000000..86d0cb272 --- /dev/null +++ b/tests/RedisConfigs/Temp/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/tests/RedisConfigs/appveyor.cmd b/tests/RedisConfigs/appveyor.cmd new file mode 100644 index 000000000..3ed04c478 --- /dev/null +++ b/tests/RedisConfigs/appveyor.cmd @@ -0,0 +1,5 @@ +@echo off +echo Starting Redis wrapper... +call %~dp0\start-all.cmd +echo Servers really started, we hope. +exit /b 0 \ No newline at end of file diff --git a/tests/RedisConfigs/cli-master.cmd b/tests/RedisConfigs/cli-master.cmd new file mode 100644 index 000000000..44d5d5d3d --- /dev/null +++ b/tests/RedisConfigs/cli-master.cmd @@ -0,0 +1 @@ +@%~dp0\3.0.503\redis-cli.exe -p 6379 diff --git a/tests/RedisConfigs/cli-replica.cmd b/tests/RedisConfigs/cli-replica.cmd new file mode 100644 index 000000000..f64ce3474 --- /dev/null +++ b/tests/RedisConfigs/cli-replica.cmd @@ -0,0 +1 @@ +@%~dp0\3.0.503\redis-cli.exe -p 6380 diff --git a/tests/RedisConfigs/cli-secure.cmd b/tests/RedisConfigs/cli-secure.cmd new file mode 100644 index 000000000..2dbcb58ec --- /dev/null +++ b/tests/RedisConfigs/cli-secure.cmd @@ -0,0 +1 @@ +@%~dp0\3.0.503\redis-cli.exe -p 6381 diff --git a/tests/RedisConfigs/docker-compose.yml b/tests/RedisConfigs/docker-compose.yml new file mode 100644 index 000000000..84cb3cc75 --- /dev/null +++ b/tests/RedisConfigs/docker-compose.yml @@ -0,0 +1,28 @@ +version: '2.7' + +services: + redis: + build: + context: .docker/Redis + additional_contexts: + configs: . + platform: linux + ports: + - 6379-6384:6379-6384 # Misc + - 7000-7006:7000-7006 # Cluster + - 7010-7011:7010-7011 # Sentinel Controllers + - 26379-26381:26379-26381 # Sentinel Data + sysctls : + net.core.somaxconn: '511' + envoy: + build: + context: .docker/Envoy + platform: linux + environment: + loglevel: warning + depends_on: + redis: + condition: service_started + ports: + - 7015:7015 # Cluster + - 8001:8001 # Admin diff --git a/tests/RedisConfigs/start-all.cmd b/tests/RedisConfigs/start-all.cmd new file mode 100644 index 000000000..55bb2fc0e --- /dev/null +++ b/tests/RedisConfigs/start-all.cmd @@ -0,0 +1,8 @@ +@echo off +echo Starting Redis servers for testing... +call %~dp0\start-basic.cmd +call %~dp0\start-failover.cmd +call %~dp0\start-cluster.cmd +call %~dp0\start-sentinel.cmd +echo Servers started (minimized). +exit /b 0 \ No newline at end of file diff --git a/tests/RedisConfigs/start-all.sh b/tests/RedisConfigs/start-all.sh new file mode 100644 index 000000000..792d75a5f --- /dev/null +++ b/tests/RedisConfigs/start-all.sh @@ -0,0 +1,58 @@ +INDENT=' ' +echo "Starting Redis servers for testing..." + +#Basic Servers +echo "Starting Basic: 6379-6382" +pushd Basic > /dev/null +echo "${INDENT}Primary: 6379" +redis-server primary-6379.conf &>/dev/null & +echo "${INDENT}Replica: 6380" +redis-server replica-6380.conf &>/dev/null & +echo "${INDENT}Secure: 6381" +redis-server secure-6381.conf &>/dev/null & +echo "${INDENT}Tls: 6384" +redis-server tls-ciphers-6384.conf &>/dev/null & +popd > /dev/null + +#Failover Servers +echo Starting Failover: 6382-6383 +pushd Failover > /dev/null +echo "${INDENT}Primary: 6382" +redis-server primary-6382.conf &>/dev/null & +echo "${INDENT}Replica: 6383" +redis-server replica-6383.conf &>/dev/null & +popd > /dev/null + +# Cluster Servers +echo Starting Cluster: 7000-7005 +pushd Cluster > /dev/null +redis-server cluster-7000.conf &>/dev/null & +redis-server cluster-7001.conf &>/dev/null & +redis-server cluster-7002.conf &>/dev/null & +redis-server cluster-7003.conf &>/dev/null & +redis-server cluster-7004.conf &>/dev/null & +redis-server cluster-7005.conf &>/dev/null & +popd > /dev/null + +#Sentinel Servers +echo Starting Sentinel: 7010-7011,26379-26381 +pushd Sentinel > /dev/null +echo "${INDENT}Targets: 7010-7011" +redis-server redis-7010.conf &>/dev/null & +redis-server redis-7011.conf &>/dev/null & +echo "${INDENT}Monitors: 26379-26381" +redis-server sentinel-26379.conf --sentinel &>/dev/null & +redis-server sentinel-26380.conf --sentinel &>/dev/null & +redis-server sentinel-26381.conf --sentinel &>/dev/null & +popd > /dev/null + +#Envoy Servers +# Installation: https://www.envoyproxy.io/docs/envoy/latest/start/install +# Use Envoy on Ubuntu Linux to install on WSL2 +echo Starting Envoy: 7015 +pushd Envoy > /dev/null +echo "${INDENT}Envoy: 7015" +envoy -c envoy.yaml &> /dev/null & +popd > /dev/null + +echo Servers started. diff --git a/tests/RedisConfigs/start-basic.cmd b/tests/RedisConfigs/start-basic.cmd new file mode 100644 index 000000000..16bec8780 --- /dev/null +++ b/tests/RedisConfigs/start-basic.cmd @@ -0,0 +1,13 @@ +@echo off +echo Starting Basic: +pushd %~dp0\Basic +echo Primary: 6379 +@start "Redis (Primary): 6379" /min ..\3.0.503\redis-server.exe primary-6379-3.0.conf +echo Replica: 6380 +@start "Redis (Replica): 6380" /min ..\3.0.503\redis-server.exe replica-6380.conf +echo Secure: 6381 +@start "Redis (Secure): 6381" /min ..\3.0.503\redis-server.exe secure-6381.conf +@REM TLS config doesn't work in 3.x - don't even start it +@REM echo TLS: 6384 +@REM @start "Redis (TLS): 6384" /min ..\3.0.503\redis-server.exe tls-ciphers-6384.conf +popd \ No newline at end of file diff --git a/tests/RedisConfigs/start-basic.sh b/tests/RedisConfigs/start-basic.sh new file mode 100644 index 000000000..4c35ea5c1 --- /dev/null +++ b/tests/RedisConfigs/start-basic.sh @@ -0,0 +1,17 @@ +INDENT=' ' +echo "Starting Redis servers for testing..." + +#Basic Servers +echo "Starting Basic: 6379-6382" +pushd Basic > /dev/null +echo "${INDENT}Primary: 6379" +redis-server primary-6379.conf &>/dev/null & +echo "${INDENT}Replica: 6380" +redis-server replica-6380.conf &>/dev/null & +echo "${INDENT}Secure: 6381" +redis-server secure-6381.conf &>/dev/null & +echo "${INDENT}Tls: 6384" +redis-server tls-ciphers-6384.conf &>/dev/null & +popd > /dev/null + +echo Servers started. \ No newline at end of file diff --git a/tests/RedisConfigs/start-cluster.cmd b/tests/RedisConfigs/start-cluster.cmd new file mode 100644 index 000000000..2db6be94a --- /dev/null +++ b/tests/RedisConfigs/start-cluster.cmd @@ -0,0 +1,12 @@ +@echo off +echo Starting Cluster: 7000-7005 +pushd %~dp0\Cluster +@start "Redis (Cluster): 7000" /min ..\3.0.503\redis-server.exe cluster-7000.conf +@start "Redis (Cluster): 7001" /min ..\3.0.503\redis-server.exe cluster-7001.conf +@start "Redis (Cluster): 7002" /min ..\3.0.503\redis-server.exe cluster-7002.conf +@start "Redis (Cluster): 7003" /min ..\3.0.503\redis-server.exe cluster-7003.conf +@start "Redis (Cluster): 7004" /min ..\3.0.503\redis-server.exe cluster-7004.conf +@start "Redis (Cluster): 7005" /min ..\3.0.503\redis-server.exe cluster-7005.conf +popd +REM envoy doesnt have an windows image, only a docker +REM need to explore if we can setup host networking \ No newline at end of file diff --git a/tests/RedisConfigs/start-failover.cmd b/tests/RedisConfigs/start-failover.cmd new file mode 100644 index 000000000..e696bdfa3 --- /dev/null +++ b/tests/RedisConfigs/start-failover.cmd @@ -0,0 +1,8 @@ +@echo off +echo Starting Failover: +pushd %~dp0\Failover +echo Master: 6382 +@start "Redis (Failover Master): 6382" /min ..\3.0.503\redis-server.exe primary-6382.conf +echo Replica: 6383 +@start "Redis (Failover Replica): 6383" /min ..\3.0.503\redis-server.exe replica-6383.conf +popd \ No newline at end of file diff --git a/tests/RedisConfigs/start-sentinel.cmd b/tests/RedisConfigs/start-sentinel.cmd new file mode 100644 index 000000000..05e0f27b5 --- /dev/null +++ b/tests/RedisConfigs/start-sentinel.cmd @@ -0,0 +1,11 @@ +@echo off +echo Starting Sentinel: +pushd %~dp0\Sentinel +echo Targets: 7010-7011 +@start "Redis (Sentinel-Target): 7010" /min ..\3.0.503\redis-server.exe redis-7010.conf +@start "Redis (Sentinel-Target): 7011" /min ..\3.0.503\redis-server.exe redis-7011.conf +echo Monitors: 26379-26381 +@start "Redis (Sentinel): 26379" /min ..\3.0.503\redis-server.exe sentinel-26379.conf --sentinel +@start "Redis (Sentinel): 26380" /min ..\3.0.503\redis-server.exe sentinel-26380.conf --sentinel +@start "Redis (Sentinel): 26381" /min ..\3.0.503\redis-server.exe sentinel-26381.conf --sentinel +popd \ No newline at end of file diff --git a/tests/RedisConfigs/wsl2.md b/tests/RedisConfigs/wsl2.md new file mode 100644 index 000000000..d25faaa3e --- /dev/null +++ b/tests/RedisConfigs/wsl2.md @@ -0,0 +1,6 @@ +If you're using WSL2, then the WSL2 instance is now a full VM with a separate IP address rather than being part of the current machine; +this means that if you're working on the *main desktop* with the server on the *VM*, the tests that expect it to work on the local machine will fail. +You *can* work around this with the `TestConfig.json` file, but you'd also need to disable `protected-mode` on the servers (we can't do that here, +because we use the same config files with the redis 3 on Windows tests, which does not allow that setting), *and* rebuild the redis cluster from scratch. + +It is much easier to run the tests directly on the VM, honestly, where everything is local. \ No newline at end of file diff --git a/tests/StackExchange.Redis.Benchmarks/CustomConfig.cs b/tests/StackExchange.Redis.Benchmarks/CustomConfig.cs new file mode 100644 index 000000000..09f44cc31 --- /dev/null +++ b/tests/StackExchange.Redis.Benchmarks/CustomConfig.cs @@ -0,0 +1,28 @@ +using System.Runtime.InteropServices; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Validators; + +namespace StackExchange.Redis.Benchmarks +{ + internal class CustomConfig : ManualConfig + { + protected virtual Job Configure(Job j) => j; + + public CustomConfig() + { + AddDiagnoser(MemoryDiagnoser.Default); + AddColumn(StatisticColumn.OperationsPerSecond); + AddValidator(JitOptimizationsValidator.FailOnError); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + AddJob(Configure(Job.Default.WithRuntime(ClrRuntime.Net481))); + } + AddJob(Configure(Job.Default.WithRuntime(CoreRuntime.Core80))); + } + } +} diff --git a/tests/StackExchange.Redis.Benchmarks/FastHashBenchmarks.cs b/tests/StackExchange.Redis.Benchmarks/FastHashBenchmarks.cs new file mode 100644 index 000000000..78877f163 --- /dev/null +++ b/tests/StackExchange.Redis.Benchmarks/FastHashBenchmarks.cs @@ -0,0 +1,139 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Text; +using BenchmarkDotNet.Attributes; + +namespace StackExchange.Redis.Benchmarks; + +[Config(typeof(CustomConfig))] +public class FastHashBenchmarks +{ + private const string SharedString = "some-typical-data-for-comparisons"; + private static readonly byte[] SharedUtf8; + private static readonly ReadOnlySequence SharedMultiSegment; + + static FastHashBenchmarks() + { + SharedUtf8 = Encoding.UTF8.GetBytes(SharedString); + + var first = new Segment(SharedUtf8.AsMemory(0, 1), null); + var second = new Segment(SharedUtf8.AsMemory(1), first); + SharedMultiSegment = new ReadOnlySequence(first, 0, second, second.Memory.Length); + } + + private sealed class Segment : ReadOnlySequenceSegment + { + public Segment(ReadOnlyMemory memory, Segment? previous) + { + Memory = memory; + if (previous is { }) + { + RunningIndex = previous.RunningIndex + previous.Memory.Length; + previous.Next = this; + } + } + } + + private string _sourceString = SharedString; + private ReadOnlyMemory _sourceBytes = SharedUtf8; + private ReadOnlySequence _sourceMultiSegmentBytes = SharedMultiSegment; + private ReadOnlySequence SingleSegmentBytes => new(_sourceBytes); + + [GlobalSetup] + public void Setup() + { + _sourceString = SharedString.Substring(0, Size); + _sourceBytes = SharedUtf8.AsMemory(0, Size); + _sourceMultiSegmentBytes = SharedMultiSegment.Slice(0, Size); + +#pragma warning disable CS0618 // Type or member is obsolete + var bytes = _sourceBytes.Span; + var expected = FastHash.Hash64Fallback(bytes); + + Assert(bytes.Hash64(), nameof(FastHash.Hash64)); + Assert(FastHash.Hash64Unsafe(bytes), nameof(FastHash.Hash64Unsafe)); +#pragma warning restore CS0618 // Type or member is obsolete + Assert(SingleSegmentBytes.Hash64(), nameof(FastHash.Hash64) + " (single segment)"); + Assert(_sourceMultiSegmentBytes.Hash64(), nameof(FastHash.Hash64) + " (multi segment)"); + + void Assert(long actual, string name) + { + if (actual != expected) + { + throw new InvalidOperationException($"Hash mismatch for {name}, {expected} != {actual}"); + } + } + } + + [ParamsSource(nameof(Sizes))] + public int Size { get; set; } = 7; + + public IEnumerable Sizes => [0, 1, 2, 3, 4, 5, 6, 7, 8, 16]; + + private const int OperationsPerInvoke = 1024; + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke, Baseline = true)] + public void String() + { + var val = _sourceString; + for (int i = 0; i < OperationsPerInvoke; i++) + { + _ = val.GetHashCode(); + } + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public void Hash64() + { + var val = _sourceBytes.Span; + for (int i = 0; i < OperationsPerInvoke; i++) + { + _ = val.Hash64(); + } + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public void Hash64Unsafe() + { + var val = _sourceBytes.Span; + for (int i = 0; i < OperationsPerInvoke; i++) + { +#pragma warning disable CS0618 // Type or member is obsolete + _ = FastHash.Hash64Unsafe(val); +#pragma warning restore CS0618 // Type or member is obsolete + } + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public void Hash64Fallback() + { + var val = _sourceBytes.Span; + for (int i = 0; i < OperationsPerInvoke; i++) + { +#pragma warning disable CS0618 // Type or member is obsolete + _ = FastHash.Hash64Fallback(val); +#pragma warning restore CS0618 // Type or member is obsolete + } + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public void Hash64_SingleSegment() + { + var val = SingleSegmentBytes; + for (int i = 0; i < OperationsPerInvoke; i++) + { + _ = val.Hash64(); + } + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public void Hash64_MultiSegment() + { + var val = _sourceMultiSegmentBytes; + for (int i = 0; i < OperationsPerInvoke; i++) + { + _ = val.Hash64(); + } + } +} diff --git a/tests/StackExchange.Redis.Benchmarks/FormatBenchmarks.cs b/tests/StackExchange.Redis.Benchmarks/FormatBenchmarks.cs new file mode 100644 index 000000000..77548b254 --- /dev/null +++ b/tests/StackExchange.Redis.Benchmarks/FormatBenchmarks.cs @@ -0,0 +1,53 @@ +using System; +using System.Net; +using BenchmarkDotNet.Attributes; + +namespace StackExchange.Redis.Benchmarks +{ + [Config(typeof(CustomConfig))] + public class FormatBenchmarks + { + [GlobalSetup] + public void Setup() { } + + [Benchmark] + [Arguments("64")] + [Arguments("-1")] + [Arguments("0")] + [Arguments("123442")] + public long ParseInt64(string s) => Format.ParseInt64(s); + + [Benchmark] + [Arguments("64")] + [Arguments("-1")] + [Arguments("0")] + [Arguments("123442")] + public long ParseInt32(string s) => Format.ParseInt32(s); + + [Benchmark] + [Arguments("64")] + [Arguments("-1")] + [Arguments("0")] + [Arguments("123442")] + [Arguments("-inf")] + [Arguments("nan")] + public double ParseDouble(string s) => Format.TryParseDouble(s, out var val) ? val : double.NaN; + + private byte[] buffer = new byte[128]; + + [Benchmark] + [Arguments(64D)] + [Arguments(-1D)] + [Arguments(0D)] + [Arguments(123442D)] + [Arguments(double.NegativeInfinity)] + [Arguments(double.NaN)] + public int FormatDouble(double value) => Format.FormatDouble(value, buffer.AsSpan()); + + [Benchmark] + [Arguments("host.com", -1)] + [Arguments("host.com", 0)] + [Arguments("host.com", 65345)] + public EndPoint ParseEndPoint(string host, int port) => Format.ParseEndPoint(host, port); + } +} diff --git a/tests/StackExchange.Redis.Benchmarks/Program.cs b/tests/StackExchange.Redis.Benchmarks/Program.cs new file mode 100644 index 000000000..311202877 --- /dev/null +++ b/tests/StackExchange.Redis.Benchmarks/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Reflection; +using BenchmarkDotNet.Running; + +namespace StackExchange.Redis.Benchmarks +{ + internal static class Program + { + private static void Main(string[] args) + { +#if DEBUG + var obj = new FastHashBenchmarks(); + foreach (var size in obj.Sizes) + { + Console.WriteLine($"Size: {size}"); + obj.Size = size; + obj.Setup(); + obj.Hash64(); + } +#else + BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly).Run(args); +#endif + } + } +} diff --git a/tests/StackExchange.Redis.Benchmarks/SlowConfig.cs b/tests/StackExchange.Redis.Benchmarks/SlowConfig.cs new file mode 100644 index 000000000..fc1ab6f71 --- /dev/null +++ b/tests/StackExchange.Redis.Benchmarks/SlowConfig.cs @@ -0,0 +1,12 @@ +using BenchmarkDotNet.Jobs; + +namespace StackExchange.Redis.Benchmarks +{ + internal sealed class SlowConfig : CustomConfig + { + protected override Job Configure(Job j) + => j.WithLaunchCount(1) + .WithWarmupCount(1) + .WithIterationCount(5); + } +} diff --git a/tests/StackExchange.Redis.Benchmarks/StackExchange.Redis.Benchmarks.csproj b/tests/StackExchange.Redis.Benchmarks/StackExchange.Redis.Benchmarks.csproj new file mode 100644 index 000000000..8b335ab02 --- /dev/null +++ b/tests/StackExchange.Redis.Benchmarks/StackExchange.Redis.Benchmarks.csproj @@ -0,0 +1,16 @@ + + + StackExchange.Redis MicroBenchmark Suite + net481;net8.0 + Release + Exe + true + enable + + + + + + + + diff --git a/tests/StackExchange.Redis.Benchmarks/run.cmd b/tests/StackExchange.Redis.Benchmarks/run.cmd new file mode 100644 index 000000000..2b8844c56 --- /dev/null +++ b/tests/StackExchange.Redis.Benchmarks/run.cmd @@ -0,0 +1 @@ +dotnet run --framework net8.0 -c Release %* \ No newline at end of file diff --git a/tests/StackExchange.Redis.Benchmarks/run.sh b/tests/StackExchange.Redis.Benchmarks/run.sh new file mode 100755 index 000000000..1824c7161 --- /dev/null +++ b/tests/StackExchange.Redis.Benchmarks/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +dotnet run --framework net8.0 -c Release "$@" \ No newline at end of file diff --git a/tests/StackExchange.Redis.Tests/AbortOnConnectFailTests.cs b/tests/StackExchange.Redis.Tests/AbortOnConnectFailTests.cs new file mode 100644 index 000000000..25033fa1a --- /dev/null +++ b/tests/StackExchange.Redis.Tests/AbortOnConnectFailTests.cs @@ -0,0 +1,98 @@ +using System; +using System.Threading.Tasks; +using StackExchange.Redis.Tests.Helpers; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class AbortOnConnectFailTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task NeverEverConnectedNoBacklogThrowsConnectionNotAvailableSync() + { + await using var conn = GetFailFastConn(); + var db = conn.GetDatabase(); + var key = Me(); + + // No connection is active/available to service this operation: GET 6.0.18AbortOnConnectFailTests-NeverEverConnectedNoBacklogThrowsConnectionNotAvailableSync; UnableToConnect on doesnot.exist.d4d1424806204b68b047954b1db3411d:6379/Interactive, Initializing/NotStarted, last: NONE, origin: BeginConnectAsync, outstanding: 0, last-read: 0s ago, last-write: 0s ago, keep-alive: 100s, state: Connecting, mgr: 4 of 10 available, last-heartbeat: never, global: 0s ago, v: 2.6.120.51136, mc: 1/1/0, mgr: 5 of 10 available, clientName: CRAVERTOP7(SE.Redis-v2.6.120.51136), IOCP: (Busy=0,Free=1000,Min=16,Max=1000), WORKER: (Busy=3,Free=32764,Min=16,Max=32767), POOL: (Threads=25,QueuedItems=0,CompletedItems=1066,Timers=46), v: 2.6.120.51136 + var ex = Assert.Throws(() => db.StringGet(key)); + Log("Exception: " + ex.Message); + Assert.Contains("No connection is active/available to service this operation", ex.Message); + } + + [Fact] + public async Task NeverEverConnectedNoBacklogThrowsConnectionNotAvailableAsync() + { + await using var conn = GetFailFastConn(); + var db = conn.GetDatabase(); + var key = Me(); + + // No connection is active/available to service this operation: GET 6.0.18AbortOnConnectFailTests-NeverEverConnectedNoBacklogThrowsConnectionNotAvailableSync; UnableToConnect on doesnot.exist.d4d1424806204b68b047954b1db3411d:6379/Interactive, Initializing/NotStarted, last: NONE, origin: BeginConnectAsync, outstanding: 0, last-read: 0s ago, last-write: 0s ago, keep-alive: 100s, state: Connecting, mgr: 4 of 10 available, last-heartbeat: never, global: 0s ago, v: 2.6.120.51136, mc: 1/1/0, mgr: 5 of 10 available, clientName: CRAVERTOP7(SE.Redis-v2.6.120.51136), IOCP: (Busy=0,Free=1000,Min=16,Max=1000), WORKER: (Busy=3,Free=32764,Min=16,Max=32767), POOL: (Threads=25,QueuedItems=0,CompletedItems=1066,Timers=46), v: 2.6.120.51136 + var ex = await Assert.ThrowsAsync(() => db.StringGetAsync(key)); + Log("Exception: " + ex.Message); + Assert.Contains("No connection is active/available to service this operation", ex.Message); + } + + [Fact] + public async Task DisconnectAndReconnectThrowsConnectionExceptionSync() + { + await using var conn = GetWorkingBacklogConn(); + + var db = conn.GetDatabase(); + var key = Me(); + await db.PingAsync(); // Doesn't throw - we're connected + + // Disconnect and don't allow re-connection + conn.AllowConnect = false; + var server = conn.GetServerSnapshot()[0]; + server.SimulateConnectionFailure(SimulatedFailureType.All); + + // Exception: The message timed out in the backlog attempting to send because no connection became available (400ms) - Last Connection Exception: SocketFailure (InputReaderCompleted, last-recv: 7) on 127.0.0.1:6379/Interactive, Idle/ReadAsync, last: PING, origin: SimulateConnectionFailure, outstanding: 0, last-read: 0s ago, last-write: 0s ago, keep-alive: 100s, state: ConnectedEstablished, mgr: 10 of 10 available, in: 0, in-pipe: 0, out-pipe: 0, last-heartbeat: never, last-mbeat: 0s ago, global: 0s ago, v: 2.6.120.51136, command=PING, timeout: 100, inst: 13, qu: 1, qs: 0, aw: False, bw: Inactive, last-in: 0, cur-in: 0, sync-ops: 2, async-ops: 0, serverEndpoint: 127.0.0.1:6379, conn-sec: n/a, aoc: 0, mc: 1/1/0, mgr: 10 of 10 available, clientName: CRAVERTOP7(SE.Redis-v2.6.120.51136), IOCP: (Busy=0,Free=1000,Min=16,Max=1000), WORKER: (Busy=2,Free=32765,Min=16,Max=32767), POOL: (Threads=33,QueuedItems=0,CompletedItems=6237,Timers=39), v: 2.6.120.51136 (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts) + var ex = Assert.ThrowsAny(() => db.Ping()); + Log("Exception: " + ex.Message); + Assert.True(ex is RedisConnectionException or RedisTimeoutException); + Assert.StartsWith("The message timed out in the backlog attempting to send because no connection became available (1000ms) - Last Connection Exception: ", ex.Message); + Assert.NotNull(ex.InnerException); + var iex = Assert.IsType(ex.InnerException); + Assert.Contains(iex.Message, ex.Message); + } + + [Fact] + public async Task DisconnectAndNoReconnectThrowsConnectionExceptionAsync() + { + await using var conn = GetWorkingBacklogConn(); + + var db = conn.GetDatabase(); + var key = Me(); + await db.PingAsync(); // Doesn't throw - we're connected + + // Disconnect and don't allow re-connection + conn.AllowConnect = false; + var server = conn.GetServerSnapshot()[0]; + server.SimulateConnectionFailure(SimulatedFailureType.All); + + // Exception: The message timed out in the backlog attempting to send because no connection became available (400ms) - Last Connection Exception: SocketFailure (InputReaderCompleted, last-recv: 7) on 127.0.0.1:6379/Interactive, Idle/ReadAsync, last: PING, origin: SimulateConnectionFailure, outstanding: 0, last-read: 0s ago, last-write: 0s ago, keep-alive: 100s, state: ConnectedEstablished, mgr: 8 of 10 available, in: 0, in-pipe: 0, out-pipe: 0, last-heartbeat: never, last-mbeat: 0s ago, global: 0s ago, v: 2.6.120.51136, command=PING, timeout: 100, inst: 0, qu: 0, qs: 0, aw: False, bw: CheckingForTimeout, last-in: 0, cur-in: 0, sync-ops: 1, async-ops: 1, serverEndpoint: 127.0.0.1:6379, conn-sec: n/a, aoc: 0, mc: 1/1/0, mgr: 8 of 10 available, clientName: CRAVERTOP7(SE.Redis-v2.6.120.51136), IOCP: (Busy=0,Free=1000,Min=16,Max=1000), WORKER: (Busy=6,Free=32761,Min=16,Max=32767), POOL: (Threads=33,QueuedItems=0,CompletedItems=5547,Timers=60), v: 2.6.120.51136 (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts) + var ex = await Assert.ThrowsAsync(() => db.PingAsync()); + Log("Exception: " + ex.Message); + Assert.StartsWith("The message timed out in the backlog attempting to send because no connection became available (1000ms) - Last Connection Exception: ", ex.Message); + Assert.NotNull(ex.InnerException); + var iex = Assert.IsType(ex.InnerException); + Assert.Contains(iex.Message, ex.Message); + } + + private ConnectionMultiplexer GetFailFastConn() => + ConnectionMultiplexer.Connect(GetOptions(BacklogPolicy.FailFast, duration: 400, connectTimeout: 500).Apply(o => o.EndPoints.Add($"doesnot.exist.{Guid.NewGuid():N}:6379")), Writer); + + private ConnectionMultiplexer GetWorkingBacklogConn() => + ConnectionMultiplexer.Connect(GetOptions(BacklogPolicy.Default).Apply(o => o.EndPoints.Add(GetConfiguration())), Writer); + + private static ConfigurationOptions GetOptions(BacklogPolicy policy, int duration = 1000, int connectTimeout = 2000) => new ConfigurationOptions() + { + AbortOnConnectFail = false, + BacklogPolicy = policy, + ConnectTimeout = connectTimeout, + SyncTimeout = duration, + KeepAlive = duration, + AllowAdmin = true, + }.WithoutSubscriptions(); +} diff --git a/tests/StackExchange.Redis.Tests/AdhocTests.cs b/tests/StackExchange.Redis.Tests/AdhocTests.cs new file mode 100644 index 000000000..42a5ebb23 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/AdhocTests.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class AdhocTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task TestAdhocCommandsAPI() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + + // needs explicit RedisKey type for key-based + // sharding to work; will still work with strings, + // but no key-based sharding support + RedisKey key = Me(); + + // note: if command renames are configured in + // the API, they will still work automatically + db.Execute("del", key); + db.Execute("set", key, "12"); + db.Execute("incrby", key, 4); + int i = (int)db.Execute("get", key); + + Assert.Equal(16, i); + } +} diff --git a/tests/StackExchange.Redis.Tests/AggressiveTests.cs b/tests/StackExchange.Redis.Tests/AggressiveTests.cs new file mode 100644 index 000000000..f0ba91f16 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/AggressiveTests.cs @@ -0,0 +1,317 @@ +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class AggressiveTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task ParallelTransactionsWithConditions() + { + Skip.UnlessLongRunning(); + const int Muxers = 4, Workers = 20, PerThread = 250; + + var muxers = new IConnectionMultiplexer[Muxers]; + try + { + for (int i = 0; i < Muxers; i++) + muxers[i] = Create(); + + RedisKey hits = Me(), trigger = Me() + "3"; + int expectedSuccess = 0; + + await muxers[0].GetDatabase().KeyDeleteAsync([hits, trigger]).ForAwait(); + + Task[] tasks = new Task[Workers]; + for (int i = 0; i < tasks.Length; i++) + { + var scopedDb = muxers[i % Muxers].GetDatabase(); + tasks[i] = Task.Run(async () => + { + for (int j = 0; j < PerThread; j++) + { + var oldVal = await scopedDb.StringGetAsync(trigger).ForAwait(); + var tran = scopedDb.CreateTransaction(); + tran.AddCondition(Condition.StringEqual(trigger, oldVal)); + var x = tran.StringIncrementAsync(trigger); + var y = tran.StringIncrementAsync(hits); + if (await tran.ExecuteAsync().ForAwait()) + { + Interlocked.Increment(ref expectedSuccess); + await x; + await y; + } + else + { + await Assert.ThrowsAsync(() => x).ForAwait(); + await Assert.ThrowsAsync(() => y).ForAwait(); + } + } + }); + } + for (int i = tasks.Length - 1; i >= 0; i--) + { + await tasks[i]; + } + var actual = (int)await muxers[0].GetDatabase().StringGetAsync(hits).ForAwait(); + Assert.Equal(expectedSuccess, actual); + Log($"success: {actual} out of {Workers * PerThread} attempts"); + } + finally + { + for (int i = 0; i < muxers.Length; i++) + { + try { muxers[i]?.Dispose(); } + catch { /* Don't care */ } + } + } + } + + private const int IterationCount = 5000, InnerCount = 20; + + [Fact] + public async Task RunCompetingBatchesOnSameMuxer() + { + Skip.UnlessLongRunning(); + await using var conn = Create(); + var db = conn.GetDatabase(); + + Thread x = new Thread(state => BatchRunPings((IDatabase)state!)) + { + Name = nameof(BatchRunPings), + }; + Thread y = new Thread(state => BatchRunIntegers((IDatabase)state!)) + { + Name = nameof(BatchRunIntegers), + }; + + x.Start(db); + y.Start(db); + x.Join(); + y.Join(); + + Log(conn.GetCounters().Interactive.ToString()); + } + + private void BatchRunIntegers(IDatabase db) + { + var key = Me(); + db.KeyDelete(key); + db.StringSet(key, 1); + Task[] tasks = new Task[InnerCount]; + for (int i = 0; i < IterationCount; i++) + { + var batch = db.CreateBatch(); + for (int j = 0; j < tasks.Length; j++) + { + tasks[j] = batch.StringIncrementAsync(key); + } + batch.Execute(); + db.Multiplexer.WaitAll(tasks); + } + + var count = (long)db.StringGet(key); + Log($"tally: {count}"); + } + + private static void BatchRunPings(IDatabase db) + { + Task[] tasks = new Task[InnerCount]; + for (int i = 0; i < IterationCount; i++) + { + var batch = db.CreateBatch(); + for (int j = 0; j < tasks.Length; j++) + { + tasks[j] = batch.PingAsync(); + } + batch.Execute(); + db.Multiplexer.WaitAll(tasks); + } + } + + [Fact] + public async Task RunCompetingBatchesOnSameMuxerAsync() + { + Skip.UnlessLongRunning(); + await using var conn = Create(); + var db = conn.GetDatabase(); + + var x = Task.Run(() => BatchRunPingsAsync(db)); + var y = Task.Run(() => BatchRunIntegersAsync(db)); + + await x; + await y; + + Log(conn.GetCounters().Interactive.ToString()); + } + + private async Task BatchRunIntegersAsync(IDatabase db) + { + var key = Me(); + await db.KeyDeleteAsync(key).ForAwait(); + await db.StringSetAsync(key, 1).ForAwait(); + Task[] tasks = new Task[InnerCount]; + for (int i = 0; i < IterationCount; i++) + { + var batch = db.CreateBatch(); + for (int j = 0; j < tasks.Length; j++) + { + tasks[j] = batch.StringIncrementAsync(key); + } + batch.Execute(); + for (int j = tasks.Length - 1; j >= 0; j--) + { + await tasks[j]; + } + } + + var count = (long)await db.StringGetAsync(key).ForAwait(); + Log($"tally: {count}"); + } + + private static async Task BatchRunPingsAsync(IDatabase db) + { + Task[] tasks = new Task[InnerCount]; + for (int i = 0; i < IterationCount; i++) + { + var batch = db.CreateBatch(); + for (int j = 0; j < tasks.Length; j++) + { + tasks[j] = batch.PingAsync(); + } + batch.Execute(); + for (int j = tasks.Length - 1; j >= 0; j--) + { + await tasks[j]; + } + } + } + + [Fact] + public async Task RunCompetingTransactionsOnSameMuxer() + { + Skip.UnlessLongRunning(); + await using var conn = Create(logTransactionData: false); + var db = conn.GetDatabase(); + + Thread x = new Thread(state => TranRunPings((IDatabase)state!)) + { + Name = nameof(BatchRunPings), + }; + Thread y = new Thread(state => TranRunIntegers((IDatabase)state!)) + { + Name = nameof(BatchRunIntegers), + }; + + x.Start(db); + y.Start(db); + x.Join(); + y.Join(); + + Log(conn.GetCounters().Interactive.ToString()); + } + + private void TranRunIntegers(IDatabase db) + { + var key = Me(); + db.KeyDelete(key); + db.StringSet(key, 1); + Task[] tasks = new Task[InnerCount]; + for (int i = 0; i < IterationCount; i++) + { + var batch = db.CreateTransaction(); + batch.AddCondition(Condition.KeyExists(key)); + for (int j = 0; j < tasks.Length; j++) + { + tasks[j] = batch.StringIncrementAsync(key); + } + batch.Execute(); + db.Multiplexer.WaitAll(tasks); + } + + var count = (long)db.StringGet(key); + Log($"tally: {count}"); + } + + private void TranRunPings(IDatabase db) + { + var key = Me(); + db.KeyDelete(key); + Task[] tasks = new Task[InnerCount]; + for (int i = 0; i < IterationCount; i++) + { + var batch = db.CreateTransaction(); + batch.AddCondition(Condition.KeyNotExists(key)); + for (int j = 0; j < tasks.Length; j++) + { + tasks[j] = batch.PingAsync(); + } + batch.Execute(); + db.Multiplexer.WaitAll(tasks); + } + } + + [Fact] + public async Task RunCompetingTransactionsOnSameMuxerAsync() + { + Skip.UnlessLongRunning(); + await using var conn = Create(logTransactionData: false); + var db = conn.GetDatabase(); + + var x = Task.Run(() => TranRunPingsAsync(db)); + var y = Task.Run(() => TranRunIntegersAsync(db)); + + await x; + await y; + + Log(conn.GetCounters().Interactive.ToString()); + } + + private async Task TranRunIntegersAsync(IDatabase db) + { + var key = Me(); + await db.KeyDeleteAsync(key).ForAwait(); + await db.StringSetAsync(key, 1).ForAwait(); + Task[] tasks = new Task[InnerCount]; + for (int i = 0; i < IterationCount; i++) + { + var batch = db.CreateTransaction(); + batch.AddCondition(Condition.KeyExists(key)); + for (int j = 0; j < tasks.Length; j++) + { + tasks[j] = batch.StringIncrementAsync(key); + } + await batch.ExecuteAsync().ForAwait(); + for (int j = tasks.Length - 1; j >= 0; j--) + { + await tasks[j]; + } + } + + var count = (long)await db.StringGetAsync(key).ForAwait(); + Log($"tally: {count}"); + } + + private async Task TranRunPingsAsync(IDatabase db) + { + var key = Me(); + db.KeyDelete(key); + Task[] tasks = new Task[InnerCount]; + for (int i = 0; i < IterationCount; i++) + { + var batch = db.CreateTransaction(); + batch.AddCondition(Condition.KeyNotExists(key)); + for (int j = 0; j < tasks.Length; j++) + { + tasks[j] = batch.PingAsync(); + } + await batch.ExecuteAsync().ForAwait(); + for (int j = tasks.Length - 1; j >= 0; j--) + { + await tasks[j]; + } + } + } +} diff --git a/tests/StackExchange.Redis.Tests/App.config b/tests/StackExchange.Redis.Tests/App.config new file mode 100644 index 000000000..c7c0b6d7a --- /dev/null +++ b/tests/StackExchange.Redis.Tests/App.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/StackExchange.Redis.Tests/AsyncTests.cs b/tests/StackExchange.Redis.Tests/AsyncTests.cs new file mode 100644 index 000000000..cba1b1145 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/AsyncTests.cs @@ -0,0 +1,86 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class AsyncTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task AsyncTasksReportFailureIfServerUnavailable() + { + SetExpectedAmbientFailureCount(-1); // this will get messy + + await using var conn = Create(allowAdmin: true, shared: false, backlogPolicy: BacklogPolicy.FailFast); + var server = conn.GetServer(TestConfig.Current.PrimaryServer, TestConfig.Current.PrimaryPort); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key); + var a = db.SetAddAsync(key, "a"); + var b = db.SetAddAsync(key, "b"); + + Assert.True(conn.Wait(a)); + Assert.True(conn.Wait(b)); + + conn.AllowConnect = false; + server.SimulateConnectionFailure(SimulatedFailureType.All); + var c = db.SetAddAsync(key, "c"); + + Assert.True(c.IsFaulted, "faulted"); + Assert.NotNull(c.Exception); + var ex = c.Exception.InnerExceptions.Single(); + Assert.IsType(ex); + Assert.StartsWith("No connection is active/available to service this operation: SADD " + key.ToString(), ex.Message); + } + + [Fact] + public async Task AsyncTimeoutIsNoticed() + { + await using var conn = Create(syncTimeout: 1000, asyncTimeout: 1000); + await using var pauseConn = Create(); + var opt = ConfigurationOptions.Parse(conn.Configuration); + if (!Debugger.IsAttached) + { // we max the timeouts if a debugger is detected + Assert.Equal(1000, opt.AsyncTimeout); + } + + RedisKey key = Me(); + var val = Guid.NewGuid().ToString(); + var db = conn.GetDatabase(); + db.StringSet(key, val); + + Assert.Contains("; async timeouts: 0;", conn.GetStatus()); + + // This is done on another connection, because it queues a SELECT due to being an unknown command that will not timeout + // at the head of the queue + await pauseConn.GetDatabase().ExecuteAsync("client", "pause", 4000).ForAwait(); // client pause returns immediately + + var ms = Stopwatch.StartNew(); + var ex = await Assert.ThrowsAsync(async () => + { + Log("Issuing StringGetAsync"); + await db.StringGetAsync(key).ForAwait(); // but *subsequent* operations are paused + ms.Stop(); + Log($"Unexpectedly succeeded after {ms.ElapsedMilliseconds}ms"); + }).ForAwait(); + ms.Stop(); + Log($"Timed out after {ms.ElapsedMilliseconds}ms"); + + Log("Exception message: " + ex.Message); + Assert.Contains("Timeout awaiting response", ex.Message); + // Ensure we are including the last payload size + Assert.Contains("last-in:", ex.Message); + Assert.DoesNotContain("last-in: 0", ex.Message); + Assert.NotNull(ex.Data["Redis-Last-Result-Bytes"]); + + Assert.Contains("cur-in:", ex.Message); + + string status = conn.GetStatus(); + Log(status); + Assert.Contains("; async timeouts: 1;", status); + } +} diff --git a/tests/StackExchange.Redis.Tests/AzureMaintenanceEventTests.cs b/tests/StackExchange.Redis.Tests/AzureMaintenanceEventTests.cs new file mode 100644 index 000000000..b43731efc --- /dev/null +++ b/tests/StackExchange.Redis.Tests/AzureMaintenanceEventTests.cs @@ -0,0 +1,47 @@ +using System; +using System.Globalization; +using System.Net; +using StackExchange.Redis.Maintenance; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class AzureMaintenanceEventTests(ITestOutputHelper output) : TestBase(output) +{ + [Theory] + [InlineData("NotificationType|NodeMaintenanceStarting|StartTimeInUTC|2021-03-02T23:26:57|IsReplica|False|IPAddress||SSLPort|15001|NonSSLPort|13001", AzureNotificationType.NodeMaintenanceStarting, "2021-03-02T23:26:57", false, null, 15001, 13001)] + [InlineData("NotificationType|NodeMaintenanceFailover|StartTimeInUTC||IsReplica|False|IPAddress||SSLPort|15001|NonSSLPort|13001", AzureNotificationType.NodeMaintenanceFailoverComplete, null, false, null, 15001, 13001)] + [InlineData("NotificationType|NodeMaintenanceFailover|StartTimeInUTC||IsReplica|True|IPAddress||SSLPort|15001|NonSSLPort|13001", AzureNotificationType.NodeMaintenanceFailoverComplete, null, true, null, 15001, 13001)] + [InlineData("NotificationType|NodeMaintenanceStarting|StartTimeInUTC|2021-03-02T23:26:57|IsReplica|j|IPAddress||SSLPort|char|NonSSLPort|char", AzureNotificationType.NodeMaintenanceStarting, "2021-03-02T23:26:57", false, null, 0, 0)] + [InlineData("NotificationType|NodeMaintenanceStarting|somejunkkey|somejunkvalue|StartTimeInUTC|2021-03-02T23:26:57|IsReplica|False|IPAddress||SSLPort|15999|NonSSLPort|139991", AzureNotificationType.NodeMaintenanceStarting, "2021-03-02T23:26:57", false, null, 15999, 139991)] + [InlineData("NotificationType|NodeMaintenanceStarting|somejunkkey|somejunkvalue|StartTimeInUTC|2021-03-02T23:26:57|IsReplica|False|IPAddress|127.0.0.1|SSLPort|15999|NonSSLPort|139991", AzureNotificationType.NodeMaintenanceStarting, "2021-03-02T23:26:57", false, "127.0.0.1", 15999, 139991)] + [InlineData("NotificationType|NodeMaintenanceScaleComplete|somejunkkey|somejunkvalue|StartTimeInUTC|2021-03-02T23:26:57|IsReplica|False|IPAddress|127.0.0.1|SSLPort|15999|NonSSLPort|139991", AzureNotificationType.NodeMaintenanceScaleComplete, "2021-03-02T23:26:57", false, "127.0.0.1", 15999, 139991)] + [InlineData("NotificationTypeNodeMaintenanceStartingsomejunkkeysomejunkvalueStartTimeInUTC2021-03-02T23:26:57IsReplicaFalseIPAddress127.0.0.1SSLPort15999NonSSLPort139991", AzureNotificationType.Unknown, null, false, null, 0, 0)] + [InlineData("NotificationType|", AzureNotificationType.Unknown, null, false, null, 0, 0)] + [InlineData("NotificationType|NodeMaintenanceStarting1", AzureNotificationType.Unknown, null, false, null, 0, 0)] + [InlineData("1|2|3", AzureNotificationType.Unknown, null, false, null, 0, 0)] + [InlineData("StartTimeInUTC|", AzureNotificationType.Unknown, null, false, null, 0, 0)] + [InlineData("IsReplica|", AzureNotificationType.Unknown, null, false, null, 0, 0)] + [InlineData("SSLPort|", AzureNotificationType.Unknown, null, false, null, 0, 0)] + [InlineData("NonSSLPort |", AzureNotificationType.Unknown, null, false, null, 0, 0)] + [InlineData("StartTimeInUTC|thisisthestart", AzureNotificationType.Unknown, null, false, null, 0, 0)] + [InlineData(null, AzureNotificationType.Unknown, null, false, null, 0, 0)] + public void TestAzureMaintenanceEventStrings(string? message, AzureNotificationType expectedEventType, string? expectedStart, bool expectedIsReplica, string? expectedIP, int expectedSSLPort, int expectedNonSSLPort) + { + DateTime? expectedStartTimeUtc = null; + if (expectedStart != null && DateTime.TryParseExact(expectedStart, "s", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTime startTimeUtc)) + { + expectedStartTimeUtc = DateTime.SpecifyKind(startTimeUtc, DateTimeKind.Utc); + } + _ = IPAddress.TryParse(expectedIP, out IPAddress? expectedIPAddress); + + var azureMaintenance = new AzureMaintenanceEvent(message); + + Assert.Equal(expectedEventType, azureMaintenance.NotificationType); + Assert.Equal(expectedStartTimeUtc, azureMaintenance.StartTimeUtc); + Assert.Equal(expectedIsReplica, azureMaintenance.IsReplica); + Assert.Equal(expectedIPAddress, azureMaintenance.IPAddress); + Assert.Equal(expectedSSLPort, azureMaintenance.SslPort); + Assert.Equal(expectedNonSSLPort, azureMaintenance.NonSslPort); + } +} diff --git a/tests/StackExchange.Redis.Tests/BacklogTests.cs b/tests/StackExchange.Redis.Tests/BacklogTests.cs new file mode 100644 index 000000000..f0c0d3d0c --- /dev/null +++ b/tests/StackExchange.Redis.Tests/BacklogTests.cs @@ -0,0 +1,401 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class BacklogTests(ITestOutputHelper output) : TestBase(output) +{ + protected override string GetConfiguration() => TestConfig.Current.PrimaryServerAndPort + "," + TestConfig.Current.ReplicaServerAndPort; + + [Fact] + public async Task FailFast() + { + void PrintSnapshot(ConnectionMultiplexer muxer) + { + Log("Snapshot summary:"); + foreach (var server in muxer.GetServerSnapshot()) + { + Log($" {server.EndPoint}: "); + Log($" Type: {server.ServerType}"); + Log($" IsConnected: {server.IsConnected}"); + Log($" IsConnecting: {server.IsConnecting}"); + Log($" IsSelectable(allowDisconnected: true): {server.IsSelectable(RedisCommand.PING, true)}"); + Log($" IsSelectable(allowDisconnected: false): {server.IsSelectable(RedisCommand.PING, false)}"); + Log($" UnselectableFlags: {server.GetUnselectableFlags()}"); + var bridge = server.GetBridge(RedisCommand.PING, create: false); + Log($" GetBridge: {bridge}"); + Log($" IsConnected: {bridge?.IsConnected}"); + Log($" ConnectionState: {bridge?.ConnectionState}"); + } + } + + try + { + // Ensuring the FailFast policy errors immediate with no connection available exceptions + var options = new ConfigurationOptions() + { + BacklogPolicy = BacklogPolicy.FailFast, + AbortOnConnectFail = false, + ConnectTimeout = 1000, + ConnectRetry = 2, + SyncTimeout = 10000, + KeepAlive = 10000, + AsyncTimeout = 5000, + AllowAdmin = true, + }; + options.EndPoints.Add(TestConfig.Current.PrimaryServerAndPort); + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer); + + var db = conn.GetDatabase(); + Log("Test: Initial (connected) ping"); + await db.PingAsync(); + + var server = conn.GetServerSnapshot()[0]; + var stats = server.GetBridgeStatus(ConnectionType.Interactive); + Assert.Equal(0, stats.BacklogMessagesPending); // Everything's normal + + // Fail the connection + Log("Test: Simulating failure"); + conn.AllowConnect = false; + server.SimulateConnectionFailure(SimulatedFailureType.All); + Assert.False(conn.IsConnected); + + // Queue up some commands + Log("Test: Disconnected pings"); + await Assert.ThrowsAsync(() => db.PingAsync()); + + var disconnectedStats = server.GetBridgeStatus(ConnectionType.Interactive); + Assert.False(conn.IsConnected); + Assert.Equal(0, disconnectedStats.BacklogMessagesPending); + + Log("Test: Allowing reconnect"); + conn.AllowConnect = true; + Log("Test: Awaiting reconnect"); + await UntilConditionAsync(TimeSpan.FromSeconds(3), () => conn.IsConnected).ForAwait(); + + Log("Test: Reconnecting"); + Assert.True(conn.IsConnected); + Assert.True(server.IsConnected); + var reconnectedStats = server.GetBridgeStatus(ConnectionType.Interactive); + Assert.Equal(0, reconnectedStats.BacklogMessagesPending); + + _ = db.PingAsync(); + _ = db.PingAsync(); + var lastPing = db.PingAsync(); + + // For debug, print out the snapshot and server states + PrintSnapshot(conn); + + Assert.NotNull(conn.SelectServer(Message.Create(-1, CommandFlags.None, RedisCommand.PING))); + + // We should see none queued + Assert.Equal(0, stats.BacklogMessagesPending); + await lastPing; + } + finally + { + ClearAmbientFailures(); + } + } + + [Fact] + public async Task QueuesAndFlushesAfterReconnectingAsync() + { + try + { + var options = new ConfigurationOptions() + { + BacklogPolicy = BacklogPolicy.Default, + AbortOnConnectFail = false, + ConnectTimeout = 1000, + ConnectRetry = 2, + SyncTimeout = 10000, + KeepAlive = 10000, + AsyncTimeout = 5000, + AllowAdmin = true, + SocketManager = SocketManager.ThreadPool, + }; + options.EndPoints.Add(TestConfig.Current.PrimaryServerAndPort); + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer); + conn.ErrorMessage += (s, e) => Log($"Error Message {e.EndPoint}: {e.Message}"); + conn.InternalError += (s, e) => Log($"Internal Error {e.EndPoint}: {e.Exception.Message}"); + conn.ConnectionFailed += (s, a) => Log("Disconnected: " + EndPointCollection.ToString(a.EndPoint)); + conn.ConnectionRestored += (s, a) => Log("Reconnected: " + EndPointCollection.ToString(a.EndPoint)); + + var db = conn.GetDatabase(); + Log("Test: Initial (connected) ping"); + await db.PingAsync(); + + var server = conn.GetServerSnapshot()[0]; + var stats = server.GetBridgeStatus(ConnectionType.Interactive); + Assert.Equal(0, stats.BacklogMessagesPending); // Everything's normal + + // Fail the connection + Log("Test: Simulating failure"); + conn.AllowConnect = false; + server.SimulateConnectionFailure(SimulatedFailureType.All); + Assert.False(conn.IsConnected); + + // Queue up some commands + Log("Test: Disconnected pings"); + var ignoredA = db.PingAsync(); + var ignoredB = db.PingAsync(); + var lastPing = db.PingAsync(); + + // TODO: Add specific server call + var disconnectedStats = server.GetBridgeStatus(ConnectionType.Interactive); + Assert.False(conn.IsConnected); + Assert.True(disconnectedStats.BacklogMessagesPending >= 3, $"Expected {nameof(disconnectedStats.BacklogMessagesPending)} > 3, got {disconnectedStats.BacklogMessagesPending}"); + + Log("Test: Allowing reconnect"); + conn.AllowConnect = true; + Log("Test: Awaiting reconnect"); + await UntilConditionAsync(TimeSpan.FromSeconds(3), () => conn.IsConnected).ForAwait(); + + Log("Test: Checking reconnected 1"); + Assert.True(conn.IsConnected); + + Log("Test: ignoredA Status: " + ignoredA.Status); + Log("Test: ignoredB Status: " + ignoredB.Status); + Log("Test: lastPing Status: " + lastPing.Status); + var afterConnectedStats = server.GetBridgeStatus(ConnectionType.Interactive); + Log($"Test: BacklogStatus: {afterConnectedStats.BacklogStatus}, BacklogMessagesPending: {afterConnectedStats.BacklogMessagesPending}, IsWriterActive: {afterConnectedStats.IsWriterActive}, MessagesSinceLastHeartbeat: {afterConnectedStats.MessagesSinceLastHeartbeat}, TotalBacklogMessagesQueued: {afterConnectedStats.TotalBacklogMessagesQueued}"); + + Log("Test: Awaiting lastPing 1"); + await lastPing; + + Log("Test: Checking reconnected 2"); + Assert.True(conn.IsConnected); + var reconnectedStats = server.GetBridgeStatus(ConnectionType.Interactive); + Assert.Equal(0, reconnectedStats.BacklogMessagesPending); + + Log("Test: Pinging again..."); + _ = db.PingAsync(); + _ = db.PingAsync(); + Log("Test: Last Ping issued"); + lastPing = db.PingAsync(); + + // We should see none queued + Log("Test: BacklogMessagesPending check"); + Assert.Equal(0, stats.BacklogMessagesPending); + Log("Test: Awaiting lastPing 2"); + await lastPing; + Log("Test: Done"); + } + finally + { + ClearAmbientFailures(); + } + } + + [Fact] + public async Task QueuesAndFlushesAfterReconnecting() + { + try + { + var options = new ConfigurationOptions() + { + BacklogPolicy = BacklogPolicy.Default, + AbortOnConnectFail = false, + ConnectTimeout = 1000, + ConnectRetry = 2, + SyncTimeout = 10000, + KeepAlive = 10000, + AsyncTimeout = 5000, + AllowAdmin = true, + SocketManager = SocketManager.ThreadPool, + }; + options.EndPoints.Add(TestConfig.Current.PrimaryServerAndPort); + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer); + conn.ErrorMessage += (s, e) => Log($"Error Message {e.EndPoint}: {e.Message}"); + conn.InternalError += (s, e) => Log($"Internal Error {e.EndPoint}: {e.Exception.Message}"); + conn.ConnectionFailed += (s, a) => Log("Disconnected: " + EndPointCollection.ToString(a.EndPoint)); + conn.ConnectionRestored += (s, a) => Log("Reconnected: " + EndPointCollection.ToString(a.EndPoint)); + + var db = conn.GetDatabase(); + Log("Test: Initial (connected) ping"); + await db.PingAsync(); + + var server = conn.GetServerSnapshot()[0]; + var stats = server.GetBridgeStatus(ConnectionType.Interactive); + Assert.Equal(0, stats.BacklogMessagesPending); // Everything's normal + + // Fail the connection + Log("Test: Simulating failure"); + conn.AllowConnect = false; + server.SimulateConnectionFailure(SimulatedFailureType.All); + Assert.False(conn.IsConnected); + + // Queue up some commands + Log("Test: Disconnected pings"); + + Task[] pings = + [ + RunBlockingSynchronousWithExtraThreadAsync(() => DisconnectedPings(1)), + RunBlockingSynchronousWithExtraThreadAsync(() => DisconnectedPings(2)), + RunBlockingSynchronousWithExtraThreadAsync(() => DisconnectedPings(3)), + ]; + void DisconnectedPings(int id) + { + // No need to delay, we're going to try a disconnected connection immediately so it'll fail... + Log($"Pinging (disconnected - {id})"); + var result = db.Ping(); + Log($"Pinging (disconnected - {id}) - result: " + result); + } + Log("Test: Disconnected pings issued"); + + Assert.False(conn.IsConnected); + // Give the tasks time to queue + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => server.GetBridgeStatus(ConnectionType.Interactive).BacklogMessagesPending >= 3); + + var disconnectedStats = server.GetBridgeStatus(ConnectionType.Interactive); + Log($"Test Stats: (BacklogMessagesPending: {disconnectedStats.BacklogMessagesPending}, TotalBacklogMessagesQueued: {disconnectedStats.TotalBacklogMessagesQueued})"); + Assert.True(disconnectedStats.BacklogMessagesPending >= 3, $"Expected {nameof(disconnectedStats.BacklogMessagesPending)} > 3, got {disconnectedStats.BacklogMessagesPending}"); + + Log("Test: Allowing reconnect"); + conn.AllowConnect = true; + Log("Test: Awaiting reconnect"); + await UntilConditionAsync(TimeSpan.FromSeconds(3), () => conn.IsConnected).ForAwait(); + + Log("Test: Checking reconnected 1"); + Assert.True(conn.IsConnected); + + var afterConnectedStats = server.GetBridgeStatus(ConnectionType.Interactive); + Log($"Test: BacklogStatus: {afterConnectedStats.BacklogStatus}, BacklogMessagesPending: {afterConnectedStats.BacklogMessagesPending}, IsWriterActive: {afterConnectedStats.IsWriterActive}, MessagesSinceLastHeartbeat: {afterConnectedStats.MessagesSinceLastHeartbeat}, TotalBacklogMessagesQueued: {afterConnectedStats.TotalBacklogMessagesQueued}"); + + Log("Test: Awaiting 3 pings"); + await Task.WhenAll(pings); + + Log("Test: Checking reconnected 2"); + Assert.True(conn.IsConnected); + var reconnectedStats = server.GetBridgeStatus(ConnectionType.Interactive); + Assert.Equal(0, reconnectedStats.BacklogMessagesPending); + + Log("Test: Pinging again..."); + pings[0] = RunBlockingSynchronousWithExtraThreadAsync(() => DisconnectedPings(4)); + pings[1] = RunBlockingSynchronousWithExtraThreadAsync(() => DisconnectedPings(5)); + pings[2] = RunBlockingSynchronousWithExtraThreadAsync(() => DisconnectedPings(6)); + Log("Test: Last Ping queued"); + + // We should see none queued + Log("Test: BacklogMessagesPending check"); + Assert.Equal(0, stats.BacklogMessagesPending); + Log("Test: Awaiting 3 more pings"); + await Task.WhenAll(pings); + Log("Test: Done"); + } + finally + { + ClearAmbientFailures(); + } + } + + [Fact] + public async Task QueuesAndFlushesAfterReconnectingClusterAsync() + { + try + { + var options = ConfigurationOptions.Parse(TestConfig.Current.ClusterServersAndPorts); + options.BacklogPolicy = BacklogPolicy.Default; + options.AbortOnConnectFail = false; + options.ConnectTimeout = 1000; + options.ConnectRetry = 2; + options.SyncTimeout = 10000; + options.KeepAlive = 10000; + options.AsyncTimeout = 5000; + options.AllowAdmin = true; + options.SocketManager = SocketManager.ThreadPool; + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer); + conn.ErrorMessage += (s, e) => Log($"Error Message {e.EndPoint}: {e.Message}"); + conn.InternalError += (s, e) => Log($"Internal Error {e.EndPoint}: {e.Exception.Message}"); + conn.ConnectionFailed += (s, a) => Log("Disconnected: " + EndPointCollection.ToString(a.EndPoint)); + conn.ConnectionRestored += (s, a) => Log("Reconnected: " + EndPointCollection.ToString(a.EndPoint)); + + var db = conn.GetDatabase(); + Log("Test: Initial (connected) ping"); + await db.PingAsync(); + + RedisKey meKey = Me(); + var getMsg = Message.Create(0, CommandFlags.None, RedisCommand.GET, meKey); + + ServerEndPoint? server = null; // Get the server specifically for this message's hash slot + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => (server = conn.SelectServer(getMsg)) != null); + + Assert.NotNull(server); + var stats = server.GetBridgeStatus(ConnectionType.Interactive); + Assert.Equal(0, stats.BacklogMessagesPending); // Everything's normal + + static Task PingAsync(ServerEndPoint server, CommandFlags flags = CommandFlags.None) + { + var message = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.PING); + + server.Multiplexer.CheckMessage(message); + return server.Multiplexer.ExecuteAsyncImpl(message, ResultProcessor.ResponseTimer, null, server); + } + + // Fail the connection + Log("Test: Simulating failure"); + conn.AllowConnect = false; + server.SimulateConnectionFailure(SimulatedFailureType.All); + Assert.False(server.IsConnected); // Server isn't connected + Assert.True(conn.IsConnected); // ...but the multiplexer is + + // Queue up some commands + Log("Test: Disconnected pings"); + var ignoredA = PingAsync(server); + var ignoredB = PingAsync(server); + var lastPing = PingAsync(server); + + var disconnectedStats = server.GetBridgeStatus(ConnectionType.Interactive); + Assert.False(server.IsConnected); + Assert.True(conn.IsConnected); + Assert.True(disconnectedStats.BacklogMessagesPending >= 3, $"Expected {nameof(disconnectedStats.BacklogMessagesPending)} > 3, got {disconnectedStats.BacklogMessagesPending}"); + + Log("Test: Allowing reconnect"); + conn.AllowConnect = true; + Log("Test: Awaiting reconnect"); + await UntilConditionAsync(TimeSpan.FromSeconds(3), () => server.IsConnected).ForAwait(); + + Log("Test: Checking reconnected 1"); + Assert.True(server.IsConnected); + Assert.True(conn.IsConnected); + + Log("Test: ignoredA Status: " + ignoredA.Status); + Log("Test: ignoredB Status: " + ignoredB.Status); + Log("Test: lastPing Status: " + lastPing.Status); + var afterConnectedStats = server.GetBridgeStatus(ConnectionType.Interactive); + Log($"Test: BacklogStatus: {afterConnectedStats.BacklogStatus}, BacklogMessagesPending: {afterConnectedStats.BacklogMessagesPending}, IsWriterActive: {afterConnectedStats.IsWriterActive}, MessagesSinceLastHeartbeat: {afterConnectedStats.MessagesSinceLastHeartbeat}, TotalBacklogMessagesQueued: {afterConnectedStats.TotalBacklogMessagesQueued}"); + + Log("Test: Awaiting lastPing 1"); + await lastPing; + + Log("Test: Checking reconnected 2"); + Assert.True(server.IsConnected); + Assert.True(conn.IsConnected); + var reconnectedStats = server.GetBridgeStatus(ConnectionType.Interactive); + Assert.Equal(0, reconnectedStats.BacklogMessagesPending); + + Log("Test: Pinging again..."); + _ = PingAsync(server); + _ = PingAsync(server); + Log("Test: Last Ping issued"); + lastPing = PingAsync(server); + + // We should see none queued + Log("Test: BacklogMessagesPending check"); + Assert.Equal(0, stats.BacklogMessagesPending); + Log("Test: Awaiting lastPing 2"); + await lastPing; + Log("Test: Done"); + } + finally + { + ClearAmbientFailures(); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/BasicOpTests.cs b/tests/StackExchange.Redis.Tests/BasicOpTests.cs new file mode 100644 index 000000000..ad85f5a88 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/BasicOpTests.cs @@ -0,0 +1,507 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using StackExchange.Redis.KeyspaceIsolation; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class BasicOpsTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task PingOnce() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + + var duration = await db.PingAsync().ForAwait(); + Log("Ping took: " + duration); + Assert.True(duration.TotalMilliseconds > 0); + } + + [Fact(Skip = "This needs some CI love, it's not a scenario we care about too much but noisy atm.")] + public async Task RapidDispose() + { + await using var primary = Create(); + var db = primary.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + for (int i = 0; i < 10; i++) + { + await using var secondary = Create(fail: true, shared: false); + secondary.GetDatabase().StringIncrement(key, flags: CommandFlags.FireAndForget); + } + // Give it a moment to get through the pipe...they were fire and forget + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => 10 == (int)db.StringGet(key)); + Assert.Equal(10, (int)db.StringGet(key)); + } + + [Fact] + public async Task PingMany() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + var tasks = new Task[100]; + for (int i = 0; i < tasks.Length; i++) + { + tasks[i] = db.PingAsync(); + } + await Task.WhenAll(tasks).ForAwait(); + Assert.True(tasks[0].Result.TotalMilliseconds > 0); + Assert.True(tasks[tasks.Length - 1].Result.TotalMilliseconds > 0); + } + + [Fact] + public async Task GetWithNullKey() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + const string? key = null; + var ex = Assert.Throws(() => db.StringGet(key)); + Assert.Equal("A null key is not valid in this context", ex.Message); + } + + [Fact] + public async Task SetWithNullKey() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + const string? key = null, value = "abc"; + var ex = Assert.Throws(() => db.StringSet(key!, value)); + Assert.Equal("A null key is not valid in this context", ex.Message); + } + + [Fact] + public async Task SetWithNullValue() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + string key = Me(); + const string? value = null; + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); + Assert.True(db.KeyExists(key)); + db.StringSet(key, value, flags: CommandFlags.FireAndForget); + + var actual = (string?)db.StringGet(key); + Assert.Null(actual); + Assert.False(db.KeyExists(key)); + } + + [Fact] + public async Task SetWithDefaultValue() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + string key = Me(); + var value = default(RedisValue); // this is kinda 0... ish + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); + Assert.True(db.KeyExists(key)); + db.StringSet(key, value, flags: CommandFlags.FireAndForget); + + var actual = (string?)db.StringGet(key); + Assert.Null(actual); + Assert.False(db.KeyExists(key)); + } + + [Fact] + public async Task SetWithZeroValue() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + string key = Me(); + const long value = 0; + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); + Assert.True(db.KeyExists(key)); + db.StringSet(key, value, flags: CommandFlags.FireAndForget); + + var actual = (string?)db.StringGet(key); + Assert.Equal("0", actual); + Assert.True(db.KeyExists(key)); + } + + [Fact] + public async Task GetSetAsync() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + + RedisKey key = Me(); + var d0 = db.KeyDeleteAsync(key); + var d1 = db.KeyDeleteAsync(key); + var g1 = db.StringGetAsync(key); + var s1 = db.StringSetAsync(key, "123"); + var g2 = db.StringGetAsync(key); + var d2 = db.KeyDeleteAsync(key); + + await d0; + Assert.False(await d1); + Assert.Null((string?)(await g1)); + Assert.True((await g1).IsNull); + await s1; + Assert.Equal("123", await g2); + Assert.Equal(123, (int)(await g2)); + Assert.False((await g2).IsNull); + Assert.True(await d2); + } + + [Fact] + public async Task GetSetSync() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + var d1 = db.KeyDelete(key); + var g1 = db.StringGet(key); + db.StringSet(key, "123", flags: CommandFlags.FireAndForget); + var g2 = db.StringGet(key); + var d2 = db.KeyDelete(key); + + Assert.False(d1); + Assert.Null((string?)g1); + Assert.True(g1.IsNull); + + Assert.Equal("123", g2); + Assert.Equal(123, (int)g2); + Assert.False(g2.IsNull); + Assert.True(d2); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, true)] + [InlineData(true, false)] + public async Task GetWithExpiry(bool exists, bool hasExpiry) + { + await using var conn = Create(); + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + if (exists) + { + if (hasExpiry) + db.StringSet(key, "val", TimeSpan.FromMinutes(5), flags: CommandFlags.FireAndForget); + else + db.StringSet(key, "val", flags: CommandFlags.FireAndForget); + } + var async = db.StringGetWithExpiryAsync(key); + var syncResult = db.StringGetWithExpiry(key); + var asyncResult = await async; + + if (exists) + { + Assert.Equal("val", asyncResult.Value); + Assert.Equal(hasExpiry, asyncResult.Expiry.HasValue); + if (hasExpiry) Assert.True(asyncResult.Expiry!.Value.TotalMinutes >= 4.9 && asyncResult.Expiry.Value.TotalMinutes <= 5); + Assert.Equal("val", syncResult.Value); + Assert.Equal(hasExpiry, syncResult.Expiry.HasValue); + if (hasExpiry) Assert.True(syncResult.Expiry!.Value.TotalMinutes >= 4.9 && syncResult.Expiry.Value.TotalMinutes <= 5); + } + else + { + Assert.True(asyncResult.Value.IsNull); + Assert.False(asyncResult.Expiry.HasValue); + Assert.True(syncResult.Value.IsNull); + Assert.False(syncResult.Expiry.HasValue); + } + } + + [Fact] + public async Task GetWithExpiryWrongTypeAsync() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + RedisKey key = Me(); + _ = db.KeyDeleteAsync(key); + _ = db.SetAddAsync(key, "abc"); + var ex = await Assert.ThrowsAsync(async () => + { + try + { + Log("Key: " + (string?)key); + await db.StringGetWithExpiryAsync(key).ForAwait(); + } + catch (AggregateException e) + { + throw e.InnerExceptions[0]; + } + }).ForAwait(); + Assert.Equal("WRONGTYPE Operation against a key holding the wrong kind of value", ex.Message); + } + + [Fact] + public async Task GetWithExpiryWrongTypeSync() + { + RedisKey key = Me(); + var ex = await Assert.ThrowsAsync(async () => + { + await using var conn = Create(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SetAdd(key, "abc", CommandFlags.FireAndForget); + db.StringGetWithExpiry(key); + }); + Assert.Equal("WRONGTYPE Operation against a key holding the wrong kind of value", ex.Message); + } + +#if DEBUG + [Fact] + public async Task TestSevered() + { + SetExpectedAmbientFailureCount(2); + await using var conn = Create(allowAdmin: true, shared: false); + var db = conn.GetDatabase(); + string key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, key, flags: CommandFlags.FireAndForget); + var server = GetServer(conn); + server.SimulateConnectionFailure(SimulatedFailureType.All); + var watch = Stopwatch.StartNew(); + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => server.IsConnected); + watch.Stop(); + Log("Time to re-establish: {0}ms (any order)", watch.ElapsedMilliseconds); + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => key == db.StringGet(key)); + Debug.WriteLine("Pinging..."); + Assert.Equal(key, db.StringGet(key)); + } +#endif + + [Fact] + public async Task IncrAsync() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + var nix = db.KeyExistsAsync(key).ForAwait(); + var a = db.StringGetAsync(key).ForAwait(); + var b = db.StringIncrementAsync(key).ForAwait(); + var c = db.StringGetAsync(key).ForAwait(); + var d = db.StringIncrementAsync(key, 10).ForAwait(); + var e = db.StringGetAsync(key).ForAwait(); + var f = db.StringDecrementAsync(key, 11).ForAwait(); + var g = db.StringGetAsync(key).ForAwait(); + var h = db.KeyExistsAsync(key).ForAwait(); + Assert.False(await nix); + Assert.True((await a).IsNull); + Assert.Equal(0, (long)(await a)); + Assert.Equal(1, await b); + Assert.Equal(1, (long)(await c)); + Assert.Equal(11, await d); + Assert.Equal(11, (long)(await e)); + Assert.Equal(0, await f); + Assert.Equal(0, (long)(await g)); + Assert.True(await h); + } + + [Fact] + public async Task IncrSync() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + RedisKey key = Me(); + Log(key); + db.KeyDelete(key, CommandFlags.FireAndForget); + var nix = db.KeyExists(key); + var a = db.StringGet(key); + var b = db.StringIncrement(key); + var c = db.StringGet(key); + var d = db.StringIncrement(key, 10); + var e = db.StringGet(key); + var f = db.StringDecrement(key, 11); + var g = db.StringGet(key); + var h = db.KeyExists(key); + Assert.False(nix); + Assert.True(a.IsNull); + Assert.Equal(0, (long)a); + Assert.Equal(1, b); + Assert.Equal(1, (long)c); + Assert.Equal(11, d); + Assert.Equal(11, (long)e); + Assert.Equal(0, f); + Assert.Equal(0, (long)g); + Assert.True(h); + } + + [Fact] + public async Task IncrDifferentSizes() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + int expected = 0; + Incr(db, key, -129019, ref expected); + Incr(db, key, -10023, ref expected); + Incr(db, key, -9933, ref expected); + Incr(db, key, -23, ref expected); + Incr(db, key, -7, ref expected); + Incr(db, key, -1, ref expected); + Incr(db, key, 0, ref expected); + Incr(db, key, 1, ref expected); + Incr(db, key, 9, ref expected); + Incr(db, key, 11, ref expected); + Incr(db, key, 345, ref expected); + Incr(db, key, 4982, ref expected); + Incr(db, key, 13091, ref expected); + Incr(db, key, 324092, ref expected); + Assert.NotEqual(0, expected); + var sum = (long)db.StringGet(key); + Assert.Equal(expected, sum); + } + + private static void Incr(IDatabase database, RedisKey key, int delta, ref int total) + { + database.StringIncrement(key, delta, CommandFlags.FireAndForget); + total += delta; + } + + [Fact] + public async Task ShouldUseSharedMuxer() + { + Log($"Shared: {SharedFixtureAvailable}"); + if (SharedFixtureAvailable) + { + await using var a = Create(); + Assert.IsNotType(a); + await using var b = Create(); + Assert.Same(a, b); + } + else + { + await using var a = Create(); + Assert.IsType(a); + await using var b = Create(); + Assert.NotSame(a, b); + } + } + + [Fact] + public async Task Delete() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + var key = Me(); + _ = db.StringSetAsync(key, "Heyyyyy"); + var ke1 = db.KeyExistsAsync(key).ForAwait(); + var ku1 = db.KeyDelete(key); + var ke2 = db.KeyExistsAsync(key).ForAwait(); + Assert.True(await ke1); + Assert.True(ku1); + Assert.False(await ke2); + } + + [Fact] + public async Task DeleteAsync() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + var key = Me(); + _ = db.StringSetAsync(key, "Heyyyyy"); + var ke1 = db.KeyExistsAsync(key).ForAwait(); + var ku1 = db.KeyDeleteAsync(key).ForAwait(); + var ke2 = db.KeyExistsAsync(key).ForAwait(); + Assert.True(await ke1); + Assert.True(await ku1); + Assert.False(await ke2); + } + + [Fact] + public async Task DeleteMany() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + var key1 = Me(); + var key2 = Me() + "2"; + var key3 = Me() + "3"; + _ = db.StringSetAsync(key1, "Heyyyyy"); + _ = db.StringSetAsync(key2, "Heyyyyy"); + // key 3 not set + var ku1 = db.KeyDelete([key1, key2, key3]); + var ke1 = db.KeyExistsAsync(key1).ForAwait(); + var ke2 = db.KeyExistsAsync(key2).ForAwait(); + Assert.Equal(2, ku1); + Assert.False(await ke1); + Assert.False(await ke2); + } + + [Fact] + public async Task DeleteManyAsync() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + var key1 = Me(); + var key2 = Me() + "2"; + var key3 = Me() + "3"; + _ = db.StringSetAsync(key1, "Heyyyyy"); + _ = db.StringSetAsync(key2, "Heyyyyy"); + // key 3 not set + var ku1 = db.KeyDeleteAsync([key1, key2, key3]).ForAwait(); + var ke1 = db.KeyExistsAsync(key1).ForAwait(); + var ke2 = db.KeyExistsAsync(key2).ForAwait(); + Assert.Equal(2, await ku1); + Assert.False(await ke1); + Assert.False(await ke2); + } + + [Fact] + public async Task WrappedDatabasePrefixIntegration() + { + var key = Me(); + await using var conn = Create(); + var db = conn.GetDatabase().WithKeyPrefix("abc"); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringIncrement(key, flags: CommandFlags.FireAndForget); + db.StringIncrement(key, flags: CommandFlags.FireAndForget); + db.StringIncrement(key, flags: CommandFlags.FireAndForget); + + int count = (int)conn.GetDatabase().StringGet("abc" + key); + Assert.Equal(3, count); + } + + [Fact] + public async Task TransactionSync() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + + RedisKey key = Me(); + + var tran = db.CreateTransaction(); + _ = db.KeyDeleteAsync(key); + var x = tran.StringIncrementAsync(Me()); + var y = tran.StringIncrementAsync(Me()); + var z = tran.StringIncrementAsync(Me()); + Assert.True(tran.Execute()); + Assert.Equal(1, x.Result); + Assert.Equal(2, y.Result); + Assert.Equal(3, z.Result); + } + + [Fact] + public async Task TransactionAsync() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + + RedisKey key = Me(); + + var tran = db.CreateTransaction(); + _ = db.KeyDeleteAsync(key); + var x = tran.StringIncrementAsync(Me()); + var y = tran.StringIncrementAsync(Me()); + var z = tran.StringIncrementAsync(Me()); + Assert.True(await tran.ExecuteAsync()); + Assert.Equal(1, await x); + Assert.Equal(2, await y); + Assert.Equal(3, await z); + } +} diff --git a/tests/StackExchange.Redis.Tests/BatchTests.cs b/tests/StackExchange.Redis.Tests/BatchTests.cs new file mode 100644 index 000000000..2605b172d --- /dev/null +++ b/tests/StackExchange.Redis.Tests/BatchTests.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class BatchTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task TestBatchNotSent() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + var key = Me(); + _ = db.KeyDeleteAsync(key); + _ = db.StringSetAsync(key, "batch-not-sent"); + var batch = db.CreateBatch(); + + _ = batch.KeyDeleteAsync(key); + _ = batch.SetAddAsync(key, "a"); + _ = batch.SetAddAsync(key, "b"); + _ = batch.SetAddAsync(key, "c"); + + Assert.Equal("batch-not-sent", db.StringGet(key)); + } + + [Fact] + public async Task TestBatchSent() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + var key = Me(); + _ = db.KeyDeleteAsync(key); + _ = db.StringSetAsync(key, "batch-sent"); + var tasks = new List(); + var batch = db.CreateBatch(); + tasks.Add(batch.KeyDeleteAsync(key)); + tasks.Add(batch.SetAddAsync(key, "a")); + tasks.Add(batch.SetAddAsync(key, "b")); + tasks.Add(batch.SetAddAsync(key, "c")); + batch.Execute(); + + var result = db.SetMembersAsync(key); + tasks.Add(result); + await Task.WhenAll(tasks.ToArray()); + + var arr = result.Result; + Array.Sort(arr, (x, y) => string.Compare(x, y)); + Assert.Equal(3, arr.Length); + Assert.Equal("a", arr[0]); + Assert.Equal("b", arr[1]); + Assert.Equal("c", arr[2]); + } +} diff --git a/tests/StackExchange.Redis.Tests/BitTests.cs b/tests/StackExchange.Redis.Tests/BitTests.cs new file mode 100644 index 000000000..b4c032366 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/BitTests.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class BitTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task BasicOps() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + RedisKey key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSetBit(key, 10, true); + Assert.True(db.StringGetBit(key, 10)); + Assert.False(db.StringGetBit(key, 11)); + } +} diff --git a/tests/StackExchange.Redis.Tests/BoxUnboxTests.cs b/tests/StackExchange.Redis.Tests/BoxUnboxTests.cs new file mode 100644 index 000000000..033a24839 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/BoxUnboxTests.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class BoxUnboxTests +{ + [Theory] + [MemberData(nameof(RoundTripValues))] + public void RoundTripRedisValue(RedisValue value) + { + var boxed = value.Box(); + var unboxed = RedisValue.Unbox(boxed); + AssertEqualGiveOrTakeNaN(value, unboxed); + } + + [Theory] + [MemberData(nameof(UnboxValues))] + public void UnboxCommonValues(object value, RedisValue expected) + { + var unboxed = RedisValue.Unbox(value); + AssertEqualGiveOrTakeNaN(expected, unboxed); + } + + [Theory] + [MemberData(nameof(InternedValues))] + public void ReturnInternedBoxesForCommonValues(RedisValue value, bool expectSameReference) + { + object? x = value.Box(), y = value.Box(); + Assert.Equal(expectSameReference, ReferenceEquals(x, y)); + // check we got the right values! + AssertEqualGiveOrTakeNaN(value, RedisValue.Unbox(x)); + AssertEqualGiveOrTakeNaN(value, RedisValue.Unbox(y)); + } + + private static void AssertEqualGiveOrTakeNaN(RedisValue expected, RedisValue actual) + { + if (expected.Type == RedisValue.StorageType.Double && actual.Type == expected.Type) + { + // because NaN != NaN, we need to special-case this scenario + bool enan = double.IsNaN((double)expected), anan = double.IsNaN((double)actual); + if (enan | anan) + { + Assert.Equal(enan, anan); + return; // and that's all + } + } + Assert.Equal(expected, actual); + } + + private static readonly byte[] s_abc = Encoding.UTF8.GetBytes("abc"); + public static IEnumerable RoundTripValues + => new[] + { + new object[] { RedisValue.Null }, + [RedisValue.EmptyString], + [(RedisValue)0L], + [(RedisValue)1L], + [(RedisValue)18L], + [(RedisValue)19L], + [(RedisValue)20L], + [(RedisValue)21L], + [(RedisValue)22L], + [(RedisValue)(-1L)], + [(RedisValue)0], + [(RedisValue)1], + [(RedisValue)18], + [(RedisValue)19], + [(RedisValue)20], + [(RedisValue)21], + [(RedisValue)22], + [(RedisValue)(-1)], + [(RedisValue)0F], + [(RedisValue)1F], + [(RedisValue)(-1F)], + [(RedisValue)0D], + [(RedisValue)1D], + [(RedisValue)(-1D)], + [(RedisValue)float.PositiveInfinity], + [(RedisValue)float.NegativeInfinity], + [(RedisValue)float.NaN], + [(RedisValue)double.PositiveInfinity], + [(RedisValue)double.NegativeInfinity], + [(RedisValue)double.NaN], + [(RedisValue)true], + [(RedisValue)false], + [(RedisValue)(string?)null], + [(RedisValue)"abc"], + [(RedisValue)s_abc], + [(RedisValue)new Memory(s_abc)], + [(RedisValue)new ReadOnlyMemory(s_abc)], + }; + + public static IEnumerable UnboxValues + => new[] + { + new object?[] { null, RedisValue.Null }, + ["", RedisValue.EmptyString], + [0, (RedisValue)0], + [1, (RedisValue)1], + [18, (RedisValue)18], + [19, (RedisValue)19], + [20, (RedisValue)20], + [21, (RedisValue)21], + [22, (RedisValue)22], + [-1, (RedisValue)(-1)], + [18L, (RedisValue)18], + [19L, (RedisValue)19], + [20L, (RedisValue)20], + [21L, (RedisValue)21], + [22L, (RedisValue)22], + [-1L, (RedisValue)(-1)], + [0F, (RedisValue)0], + [1F, (RedisValue)1], + [-1F, (RedisValue)(-1)], + [0D, (RedisValue)0], + [1D, (RedisValue)1], + [-1D, (RedisValue)(-1)], + [float.PositiveInfinity, (RedisValue)double.PositiveInfinity], + [float.NegativeInfinity, (RedisValue)double.NegativeInfinity], + [float.NaN, (RedisValue)double.NaN], + [double.PositiveInfinity, (RedisValue)double.PositiveInfinity], + [double.NegativeInfinity, (RedisValue)double.NegativeInfinity], + [double.NaN, (RedisValue)double.NaN], + [true, (RedisValue)true], + [false, (RedisValue)false], + ["abc", (RedisValue)"abc"], + [s_abc, (RedisValue)s_abc], + [new Memory(s_abc), (RedisValue)s_abc], + [new ReadOnlyMemory(s_abc), (RedisValue)s_abc], + [(RedisValue)1234, (RedisValue)1234], + }; + + public static IEnumerable InternedValues() + { + for (int i = -20; i <= 40; i++) + { + bool expectInterned = i >= -1 & i <= 20; + yield return new object[] { (RedisValue)i, expectInterned }; + yield return new object[] { (RedisValue)(long)i, expectInterned }; + yield return new object[] { (RedisValue)(float)i, expectInterned }; + yield return new object[] { (RedisValue)(double)i, expectInterned }; + } + + yield return new object[] { (RedisValue)float.NegativeInfinity, true }; + yield return new object[] { (RedisValue)(-0.5F), false }; + yield return new object[] { (RedisValue)0.5F, false }; + yield return new object[] { (RedisValue)float.PositiveInfinity, true }; + yield return new object[] { (RedisValue)float.NaN, true }; + + yield return new object[] { (RedisValue)double.NegativeInfinity, true }; + yield return new object[] { (RedisValue)(-0.5D), false }; + yield return new object[] { (RedisValue)0.5D, false }; + yield return new object[] { (RedisValue)double.PositiveInfinity, true }; + yield return new object[] { (RedisValue)double.NaN, true }; + + yield return new object[] { (RedisValue)true, true }; + yield return new object[] { (RedisValue)false, true }; + yield return new object[] { RedisValue.Null, true }; + yield return new object[] { RedisValue.EmptyString, true }; + yield return new object[] { (RedisValue)"abc", true }; + yield return new object[] { (RedisValue)s_abc, true }; + yield return new object[] { (RedisValue)new Memory(s_abc), false }; + yield return new object[] { (RedisValue)new ReadOnlyMemory(s_abc), false }; + } +} diff --git a/tests/StackExchange.Redis.Tests/CancellationTests.cs b/tests/StackExchange.Redis.Tests/CancellationTests.cs new file mode 100644 index 000000000..a512743f9 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/CancellationTests.cs @@ -0,0 +1,195 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class CancellationTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task WithCancellation_CancelledToken_ThrowsOperationCanceledException() + { +#if NETFRAMEWORK + Skip.UnlessLongRunning(); // unpredictable on netfx due to weak WaitAsync impl +#endif + + await using var conn = Create(); + var db = conn.GetDatabase(); + + using var cts = new CancellationTokenSource(); + cts.Cancel(); // Cancel immediately + + await Assert.ThrowsAnyAsync(async () => await db.StringSetAsync(Me(), "value").WaitAsync(cts.Token)); + } + + private IInternalConnectionMultiplexer Create() => Create(syncTimeout: 10_000); + + [Fact] + public async Task WithCancellation_ValidToken_OperationSucceeds() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + + using var cts = new CancellationTokenSource(); + + RedisKey key = Me(); + // This should succeed + await db.StringSetAsync(key, "value"); + var result = await db.StringGetAsync(key).WaitAsync(cts.Token); + Assert.Equal("value", result); + } + + private static void Pause(IDatabase db) => db.Execute("client", ["pause", ConnectionPauseMilliseconds], CommandFlags.FireAndForget); + + private void Pause(IServer server) + { + server.Execute("client", new object[] { "pause", ConnectionPauseMilliseconds }, CommandFlags.FireAndForget); + } + + [Fact] + public async Task WithTimeout_ShortTimeout_Async_ThrowsOperationCanceledException() + { + Skip.UnlessLongRunning(); // because of CLIENT PAUSE impact to unrelated tests + + await using var conn = Create(); + var db = conn.GetDatabase(); + + var watch = Stopwatch.StartNew(); + Pause(db); + + var timeout = TimeSpan.FromMilliseconds(ShortDelayMilliseconds); + // This might throw due to timeout, but let's test the mechanism + var pending = db.StringSetAsync(Me(), "value").WaitAsync(timeout); // check we get past this + try + { + await pending; + // If it succeeds, that's fine too - Redis is fast + Assert.Fail(ExpectedCancel + ": " + watch.ElapsedMilliseconds + "ms"); + } + catch (TimeoutException) + { + // Expected for very short timeouts + Log($"Timeout after {watch.ElapsedMilliseconds}ms"); + } + } + + private const string ExpectedCancel = "This operation should have been cancelled"; + + [Fact] + public async Task WithoutCancellation_OperationsWorkNormally() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + + // No cancellation - should work normally + RedisKey key = Me(); + await db.StringSetAsync(key, "value"); + var result = await db.StringGetAsync(key); + Assert.Equal("value", result); + } + + public enum CancelStrategy + { + Constructor, + Method, + Manual, + } + + private const int ConnectionPauseMilliseconds = 50, ShortDelayMilliseconds = 5; + + private static CancellationTokenSource CreateCts(CancelStrategy strategy) + { + switch (strategy) + { + case CancelStrategy.Constructor: + return new CancellationTokenSource(TimeSpan.FromMilliseconds(ShortDelayMilliseconds)); + case CancelStrategy.Method: + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromMilliseconds(ShortDelayMilliseconds)); + return cts; + case CancelStrategy.Manual: + cts = new(); + _ = Task.Run(async () => + { + await Task.Delay(ShortDelayMilliseconds); + // ReSharper disable once MethodHasAsyncOverload - TFM-dependent + cts.Cancel(); + }); + return cts; + default: + throw new ArgumentOutOfRangeException(nameof(strategy)); + } + } + + [Theory] + [InlineData(CancelStrategy.Constructor)] + [InlineData(CancelStrategy.Method)] + [InlineData(CancelStrategy.Manual)] + public async Task CancellationDuringOperation_Async_CancelsGracefully(CancelStrategy strategy) + { + Skip.UnlessLongRunning(); // because of CLIENT PAUSE impact to unrelated tests + + await using var conn = Create(); + var db = conn.GetDatabase(); + + var watch = Stopwatch.StartNew(); + Pause(db); + + // Cancel after a short delay + using var cts = CreateCts(strategy); + + // Start an operation and cancel it mid-flight + var pending = db.StringSetAsync($"{Me()}:{strategy}", "value").WaitAsync(cts.Token); + + try + { + await pending; + Assert.Fail(ExpectedCancel + ": " + watch.ElapsedMilliseconds + "ms"); + } + catch (OperationCanceledException oce) + { + // Expected if cancellation happens during operation + Log($"Cancelled after {watch.ElapsedMilliseconds}ms"); + Assert.Equal(cts.Token, oce.CancellationToken); + } + } + + [Fact] + public async Task ScanCancellable() + { + Skip.UnlessLongRunning(); // because of CLIENT PAUSE impact to unrelated tests + + using var conn = Create(); + var db = conn.GetDatabase(); + var server = conn.GetServer(conn.GetEndPoints()[0]); + + using var cts = new CancellationTokenSource(); + + var watch = Stopwatch.StartNew(); + Pause(server); + try + { + db.StringSet(Me(), "value", TimeSpan.FromMinutes(5), flags: CommandFlags.FireAndForget); + await using var iter = server.KeysAsync(pageSize: 1000).WithCancellation(cts.Token).GetAsyncEnumerator(); + var pending = iter.MoveNextAsync(); + Assert.False(cts.Token.IsCancellationRequested); + cts.CancelAfter(ShortDelayMilliseconds); // start this *after* we've got past the initial check + while (await pending) + { + pending = iter.MoveNextAsync(); + } + Assert.Fail($"{ExpectedCancel}: {watch.ElapsedMilliseconds}ms"); + } + catch (OperationCanceledException oce) + { + var taken = watch.ElapsedMilliseconds; + // Expected if cancellation happens during operation + Log($"Cancelled after {taken}ms"); + Assert.True(taken < (ConnectionPauseMilliseconds * 3) / 4, $"Should have cancelled sooner; took {taken}ms"); + Assert.Equal(cts.Token, oce.CancellationToken); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/Certificates/CertValidationTests.cs b/tests/StackExchange.Redis.Tests/Certificates/CertValidationTests.cs new file mode 100644 index 000000000..a0d9b5c88 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Certificates/CertValidationTests.cs @@ -0,0 +1,64 @@ +using System; +using System.IO; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class CertValidationTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public void CheckIssuerValidity() + { + // The endpoint cert is the same here + var endpointCert = LoadCert(Path.Combine("Certificates", "device01.foo.com.pem")); + + // Trusting CA explicitly + var callback = ConfigurationOptions.TrustIssuerCallback(Path.Combine("Certificates", "ca.foo.com.pem")); + Assert.True(callback(this, endpointCert, null, SslPolicyErrors.None), "subtest 1a"); + Assert.True(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors), "subtest 1b"); + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNameMismatch), "subtest 1c"); + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNotAvailable), "subtest 1d"); + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch), "subtest 1e"); + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNotAvailable), "subtest 1f"); + + // Trusting the remote endpoint cert directly + callback = ConfigurationOptions.TrustIssuerCallback(Path.Combine("Certificates", "device01.foo.com.pem")); + Assert.True(callback(this, endpointCert, null, SslPolicyErrors.None), "subtest 2a"); + if (Runtime.IsMono) + { + // Mono doesn't support this cert usage, reports as rejection (happy for someone to work around this, but isn't high priority) + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors), "subtest 2b"); + } + else + { + Assert.True(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors), "subtest 2b"); + } + + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNameMismatch), "subtest 2c"); + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNotAvailable), "subtest 2d"); + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch), "subtest 2e"); + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNotAvailable), "subtest 2f"); + + // Attempting to trust another CA (mismatch) + callback = ConfigurationOptions.TrustIssuerCallback(Path.Combine("Certificates", "ca2.foo.com.pem")); + Assert.True(callback(this, endpointCert, null, SslPolicyErrors.None), "subtest 3a"); + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors), "subtest 3b"); + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNameMismatch), "subtest 3c"); + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateNotAvailable), "subtest 3d"); + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch), "subtest 3e"); + Assert.False(callback(this, endpointCert, null, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNotAvailable), "subtest 3f"); + } + + private static X509Certificate2 LoadCert(string certificatePath) => new X509Certificate2(File.ReadAllBytes(certificatePath)); + + [Fact] + public void CheckIssuerArgs() + { + Assert.ThrowsAny(() => ConfigurationOptions.TrustIssuerCallback("")); + + var opt = new ConfigurationOptions(); + Assert.Throws(() => opt.TrustIssuer((X509Certificate2)null!)); + } +} diff --git a/tests/StackExchange.Redis.Tests/Certificates/README.md b/tests/StackExchange.Redis.Tests/Certificates/README.md new file mode 100644 index 000000000..83fb5a14c --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Certificates/README.md @@ -0,0 +1,4 @@ +The certificates here are randomly generated for testing only. +They are not valid and only used for test validation. + +Please do not file security issue noise - these have no impact being public at all. \ No newline at end of file diff --git a/tests/StackExchange.Redis.Tests/Certificates/ca.foo.com.pem b/tests/StackExchange.Redis.Tests/Certificates/ca.foo.com.pem new file mode 100644 index 000000000..33496adea --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Certificates/ca.foo.com.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDTTCCAjWgAwIBAgIUU9SR3QMGVO8yN/mr8SQJ1p/OgIAwDQYJKoZIhvcNAQEL +BQAwNjETMBEGA1UEAwwKY2EuZm9vLmNvbTESMBAGA1UECgwJTXlEZXZpY2VzMQsw +CQYDVQQGEwJVUzAeFw0yNDAzMDcxNDAxMzJaFw00ODEwMjcxNDAxMzJaMDYxEzAR +BgNVBAMMCmNhLmZvby5jb20xEjAQBgNVBAoMCU15RGV2aWNlczELMAkGA1UEBhMC +VVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqk8FT5dHU335oSEuY +RGeHOHmtxtr5Eoxe4pRHWcBKARzRYi+fPjP/aSWh75yYcmyQ5o5e2JQTZQRwSaLh +q8lrsT7AIeZboATVxECyT3kZdIJkLgWbfyzwJQtrW+ccDx3gDRt0FKRt8Hd3foIf +ULICgkiz3C5sihT589QWmcP4XhcRf3A1bt3rrFWJBO1jmKz0P7pijT14lkdW4sVL +AdFhoNg/a042a7wq2i8PxrkhWpwmHkW9ErnbWG9pRjMme+GDeNfGdHslL5grzbzC +4B4w3QP4opLUp29O9oO1DjaAuZ86JVdy3+glugMvj4f8NVCVlHxRM5Kn/3WgWIIM +XBK7AgMBAAGjUzBRMB0GA1UdDgQWBBRmgj4urVgvTcPgJtyqyUHaFX0svjAfBgNV +HSMEGDAWgBRmgj4urVgvTcPgJtyqyUHaFX0svjAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4IBAQB2DIGlKpCdluVHURfgA5zfwoOnhtOZm7lwC/zbNd5a +wNmb6Vy29feN/+6/dv7MFTXXB5f0TkDGrGbAsKVLD5mNSfQhHC8sxwotheMYq6LS +T1Pjv3Vxku1O7q6FQrslDWfSBxzwep2q8fDXwD4C7VgVRM2EGg/vVee2juuTCmMU +Z1LwJrOkBczW6b3ZvUThFGOvZkuI138EuR2gqPHMQIiQcPyX1syT7yhJAyDQRYOG +cHSRojNciYVotSTgyYguUJdU7vfRJ+MLfoZClzJgvNav8yUC+sSrb5RD5vQlvxzG +KrJ8Hh+hpIFsmQKj5dcochKvLLd1Z748b2+FB0jtxraU +-----END CERTIFICATE----- diff --git a/tests/StackExchange.Redis.Tests/Certificates/ca2.foo.com.pem b/tests/StackExchange.Redis.Tests/Certificates/ca2.foo.com.pem new file mode 100644 index 000000000..b2b18d02b --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Certificates/ca2.foo.com.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDTzCCAjegAwIBAgIUYXv168vvB1NPg3PfoRzcNFEMaC8wDQYJKoZIhvcNAQEL +BQAwNzEUMBIGA1UEAwwLY2EyLmZvby5jb20xEjAQBgNVBAoMCU15RGV2aWNlczEL +MAkGA1UEBhMCVVMwHhcNMjQwMzA3MTQwMTMyWhcNNDgxMDI3MTQwMTMyWjA3MRQw +EgYDVQQDDAtjYTIuZm9vLmNvbTESMBAGA1UECgwJTXlEZXZpY2VzMQswCQYDVQQG +EwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOFDZ2sf8Ik/I3jD +Mp4NGoo+kMY1BiRWjSKnaphfcosR2MAT/ROIVhnkzSeiQQByf34cqN/7xNkHaufr +oVcMuuiyWERPoZjBqnfzLZ+93uxnyIU6DVDdNIcKcBQxyhHMfOigFhKTia6eWhrf +zSaBhbkndaUsXdINBAJgSq3HDuk8bIw8MTZH0giorhIyyyqT/gjWEbzKx6Ww99qV +MMcjFIvXEmD9AEaNilHD4TtwqZrZKZpnVBaQvWrCK3tCGBDyiFlUhAibchbt/JzV +sK002TFfUbn41ygHmcrBVL7lSEsuT2W/PNuDOdWa6NQ2RVzYivs5jYbWV1cAvAJP +HMWJkZ8CAwEAAaNTMFEwHQYDVR0OBBYEFA6ZeCMPgDEu+eIUoCxU/Q06ViyoMB8G +A1UdIwQYMBaAFA6ZeCMPgDEu+eIUoCxU/Q06ViyoMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggEBAGOa/AD0JNPwvyDi9wbVU+Yktx3vfuVyOMbnUQSn +nOyWd6d95rZwbeYyN908PjERQT3EMo8/O0eOpoG9I79vjbcD6LAIbxS9XdI8kK4+ +D4e/DX/R85KoWSprB+VRXGqsChY0Y+4x5x2q/IoCi6+tywhzjqIlaGDYrlc688HO +/+4iR9L945gpg4NT1hLnCwDYcdZ5vjv4NfgXDbGPUcEheYnfz3cHE2mYxEG9KXta +f8hSj/MNNv31BzNcj4XKcDqp4Ke3ow4lAZsPPlixOxxRaLnpsKZmEYYQcLI8KVNk +gdAUOSPZgzRqAag0rvVfrpyvfvlu0D9xeiBLdhaJeZCq1/s= +-----END CERTIFICATE----- diff --git a/tests/StackExchange.Redis.Tests/Certificates/create_certificates.sh b/tests/StackExchange.Redis.Tests/Certificates/create_certificates.sh new file mode 100644 index 000000000..71ef0caa3 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Certificates/create_certificates.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -eu +# Adapted from https://github.com/stewartadam/dotnet-x509-certificate-verification/blob/main/create_certificates.sh + +base_dir="certificates" + +create_ca() { + local CA_CN="$1" + local certificate_output="${base_dir}/${CA_CN}.pem" + + openssl genrsa -out "${base_dir}/${CA_CN}.key.pem" 2048 # Generate private key + openssl req -x509 -new -nodes -key "${base_dir}/${CA_CN}.key.pem" -sha256 -days 9000 -out "${certificate_output}" -subj "/CN=${CA_CN}/O=MyDevices/C=US" # Generate root certificate + + echo -e "\nCertificate for CA ${CA_CN} saved to ${certificate_output}\n\n" +} + +create_leaf_cert_req() { + local DEVICE_CN="$1" + + openssl genrsa -out "${base_dir}/${DEVICE_CN}.key.pem" 2048 # new private key + openssl req -new -key "${base_dir}/${DEVICE_CN}.key.pem" -out "${base_dir}/${DEVICE_CN}.csr.pem" -subj "/CN=${DEVICE_CN}/O=MyDevices/C=US" # generate signing request for the CA +} + +sign_leaf_cert() { + local DEVICE_CN="$1" + local CA_CN="$2" + local certificate_output="${base_dir}/${DEVICE_CN}.pem" + + openssl x509 -req -in "${base_dir}/${DEVICE_CN}.csr.pem" -CA ""${base_dir}/${CA_CN}.pem"" -CAkey "${base_dir}/${CA_CN}.key.pem" -set_serial 01 -out "${certificate_output}" -days 8999 -sha256 # sign the CSR + + echo -e "\nCertificate for ${DEVICE_CN} saved to ${certificate_output}\n\n" +} + +mkdir -p "${base_dir}" + +# Create one self-issued CA + signed cert +create_ca "ca.foo.com" +create_leaf_cert_req "device01.foo.com" +sign_leaf_cert "device01.foo.com" "ca.foo.com" \ No newline at end of file diff --git a/tests/StackExchange.Redis.Tests/Certificates/device01.foo.com.pem b/tests/StackExchange.Redis.Tests/Certificates/device01.foo.com.pem new file mode 100644 index 000000000..58f47641b --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Certificates/device01.foo.com.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC5jCCAc4CAQEwDQYJKoZIhvcNAQELBQAwNjETMBEGA1UEAwwKY2EuZm9vLmNv +bTESMBAGA1UECgwJTXlEZXZpY2VzMQswCQYDVQQGEwJVUzAeFw0yNDAzMDcxNDAx +MzJaFw00ODEwMjYxNDAxMzJaMDwxGTAXBgNVBAMMEGRldmljZTAxLmZvby5jb20x +EjAQBgNVBAoMCU15RGV2aWNlczELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDBb4Mv87+MFVGLIWArc0wV1GH4h7Ha+49K+JAi8rtk +fpQACcu3OGq5TjUuxecOz5eXDwJj/vR1rvjP0DaCuIlx4SNXXqVKooWpCLb2g4Mr +IIiFcBsiaJNmhFvd92bqHOyuXsUTjkJKaLmH6nUqVIXEA/Py+jpuSFRp9N475IGZ +yUUdaQUx9Ur953FagLbPVeE5Vh+NEA8vnw+ZBCQRBHlRgvSJtCAR/oznXXPdHGGZ +rMWeNjl+v1iP8fZMq4vvooW0/zTVgH8lE/HJXtpaWEVeGpnOqBsgvl12WTGL5dMU +n81JiI3AdUyW0ieh/5yr+OFNa/HNqGLK1NvnCDPbBFpnAgMBAAEwDQYJKoZIhvcN +AQELBQADggEBAEpIIJJ7q4/EbCJog29Os9l5unn7QJon4R5TGJQIxdqDGrhXG8QA +HiBGl/lFhAp9tfKvQIj4aODzMgHmDpzZmH/yhjDlquJgB4JYDDjf9UhtwUUbRDp0 +rEc5VikLuTJ21hcusKALH5fgBjzplRNPH8P660FxWnv9gSWCMNaiFCCxTU91g4L3 +/qZPTl5nr1j6M/+zXbndS5qlF7GkU5Kv9xEmasZ0Z65Wao6Ufhw+nczLbiLErrxD +zLtTfr6WYFqzXeiPGnjTueG+1cYDjngmj2fbtjPn4W67q8Z0M/ZMj9ikr881d3zP +3dzUEaGexGqvA2MCapIQ2vCCMDF33ismQts= +-----END CERTIFICATE----- diff --git a/tests/StackExchange.Redis.Tests/ChannelTests.cs b/tests/StackExchange.Redis.Tests/ChannelTests.cs new file mode 100644 index 000000000..4843090f4 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ChannelTests.cs @@ -0,0 +1,153 @@ +using System.Text; +using Xunit; + +namespace StackExchange.Redis.Tests +{ + public class ChannelTests + { + [Fact] + public void UseImplicitAutoPattern_OnByDefault() + { + Assert.True(RedisChannel.UseImplicitAutoPattern); + } + + [Theory] + [InlineData("abc", true, false)] + [InlineData("abc*def", true, true)] + [InlineData("abc", false, false)] + [InlineData("abc*def", false, false)] + public void ValidateAutoPatternModeString(string name, bool useImplicitAutoPattern, bool isPatternBased) + { + bool oldValue = RedisChannel.UseImplicitAutoPattern; + try + { + RedisChannel.UseImplicitAutoPattern = useImplicitAutoPattern; +#pragma warning disable CS0618 // we need to test the operator + RedisChannel channel = name; +#pragma warning restore CS0618 + Assert.Equal(isPatternBased, channel.IsPattern); + } + finally + { + RedisChannel.UseImplicitAutoPattern = oldValue; + } + } + + [Theory] + [InlineData("abc", RedisChannel.PatternMode.Auto, true, false)] + [InlineData("abc*def", RedisChannel.PatternMode.Auto, true, true)] + [InlineData("abc", RedisChannel.PatternMode.Literal, true, false)] + [InlineData("abc*def", RedisChannel.PatternMode.Literal, true, false)] + [InlineData("abc", RedisChannel.PatternMode.Pattern, true, true)] + [InlineData("abc*def", RedisChannel.PatternMode.Pattern, true, true)] + [InlineData("abc", RedisChannel.PatternMode.Auto, false, false)] + [InlineData("abc*def", RedisChannel.PatternMode.Auto, false, true)] + [InlineData("abc", RedisChannel.PatternMode.Literal, false, false)] + [InlineData("abc*def", RedisChannel.PatternMode.Literal, false, false)] + [InlineData("abc", RedisChannel.PatternMode.Pattern, false, true)] + [InlineData("abc*def", RedisChannel.PatternMode.Pattern, false, true)] + public void ValidateModeSpecifiedIgnoresGlobalSetting(string name, RedisChannel.PatternMode mode, bool useImplicitAutoPattern, bool isPatternBased) + { + bool oldValue = RedisChannel.UseImplicitAutoPattern; + try + { + RedisChannel.UseImplicitAutoPattern = useImplicitAutoPattern; + RedisChannel channel = new(name, mode); + Assert.Equal(isPatternBased, channel.IsPattern); + } + finally + { + RedisChannel.UseImplicitAutoPattern = oldValue; + } + } + + [Theory] + [InlineData("abc", true, false)] + [InlineData("abc*def", true, true)] + [InlineData("abc", false, false)] + [InlineData("abc*def", false, false)] + public void ValidateAutoPatternModeBytes(string name, bool useImplicitAutoPattern, bool isPatternBased) + { + var bytes = Encoding.UTF8.GetBytes(name); + bool oldValue = RedisChannel.UseImplicitAutoPattern; + try + { + RedisChannel.UseImplicitAutoPattern = useImplicitAutoPattern; +#pragma warning disable CS0618 // we need to test the operator + RedisChannel channel = bytes; +#pragma warning restore CS0618 + Assert.Equal(isPatternBased, channel.IsPattern); + } + finally + { + RedisChannel.UseImplicitAutoPattern = oldValue; + } + } + + [Theory] + [InlineData("abc", RedisChannel.PatternMode.Auto, true, false)] + [InlineData("abc*def", RedisChannel.PatternMode.Auto, true, true)] + [InlineData("abc", RedisChannel.PatternMode.Literal, true, false)] + [InlineData("abc*def", RedisChannel.PatternMode.Literal, true, false)] + [InlineData("abc", RedisChannel.PatternMode.Pattern, true, true)] + [InlineData("abc*def", RedisChannel.PatternMode.Pattern, true, true)] + [InlineData("abc", RedisChannel.PatternMode.Auto, false, false)] + [InlineData("abc*def", RedisChannel.PatternMode.Auto, false, true)] + [InlineData("abc", RedisChannel.PatternMode.Literal, false, false)] + [InlineData("abc*def", RedisChannel.PatternMode.Literal, false, false)] + [InlineData("abc", RedisChannel.PatternMode.Pattern, false, true)] + [InlineData("abc*def", RedisChannel.PatternMode.Pattern, false, true)] + public void ValidateModeSpecifiedIgnoresGlobalSettingBytes(string name, RedisChannel.PatternMode mode, bool useImplicitAutoPattern, bool isPatternBased) + { + var bytes = Encoding.UTF8.GetBytes(name); + bool oldValue = RedisChannel.UseImplicitAutoPattern; + try + { + RedisChannel.UseImplicitAutoPattern = useImplicitAutoPattern; + RedisChannel channel = new(bytes, mode); + Assert.Equal(isPatternBased, channel.IsPattern); + } + finally + { + RedisChannel.UseImplicitAutoPattern = oldValue; + } + } + + [Theory] + [InlineData("abc*def", false)] + [InlineData("abcdef", false)] + [InlineData("abc*def", true)] + [InlineData("abcdef", true)] + public void ValidateLiteralPatternMode(string name, bool useImplicitAutoPattern) + { + bool oldValue = RedisChannel.UseImplicitAutoPattern; + try + { + RedisChannel.UseImplicitAutoPattern = useImplicitAutoPattern; + RedisChannel channel; + + // literal, string + channel = RedisChannel.Literal(name); + Assert.False(channel.IsPattern); + + // pattern, string + channel = RedisChannel.Pattern(name); + Assert.True(channel.IsPattern); + + var bytes = Encoding.UTF8.GetBytes(name); + + // literal, byte[] + channel = RedisChannel.Literal(bytes); + Assert.False(channel.IsPattern); + + // pattern, byte[] + channel = RedisChannel.Pattern(bytes); + Assert.True(channel.IsPattern); + } + finally + { + RedisChannel.UseImplicitAutoPattern = oldValue; + } + } + } +} diff --git a/tests/StackExchange.Redis.Tests/ClientKillTests.cs b/tests/StackExchange.Redis.Tests/ClientKillTests.cs new file mode 100644 index 000000000..f10f69ef6 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ClientKillTests.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] + +public class ClientKillTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task ClientKill() + { + SetExpectedAmbientFailureCount(-1); + await using var otherConnection = Create(allowAdmin: true, shared: false, backlogPolicy: BacklogPolicy.FailFast, require: RedisFeatures.v7_4_0_rc1); + var id = otherConnection.GetDatabase().Execute(RedisCommand.CLIENT.ToString(), RedisLiterals.ID); + + await using var conn = Create(allowAdmin: true, shared: false, backlogPolicy: BacklogPolicy.FailFast); + var server = conn.GetServer(conn.GetEndPoints()[0]); + long result = server.ClientKill(id.AsInt64(), ClientType.Normal, null, true); + Assert.Equal(1, result); + } + + [Fact] + public async Task ClientKillWithMaxAge() + { + SetExpectedAmbientFailureCount(-1); + await using var otherConnection = Create(allowAdmin: true, shared: false, backlogPolicy: BacklogPolicy.FailFast, require: RedisFeatures.v7_4_0_rc1); + var id = otherConnection.GetDatabase().Execute(RedisCommand.CLIENT.ToString(), RedisLiterals.ID); + await Task.Delay(1000); + + await using var conn = Create(allowAdmin: true, shared: false, backlogPolicy: BacklogPolicy.FailFast); + var server = conn.GetServer(conn.GetEndPoints()[0]); + var filter = new ClientKillFilter().WithId(id.AsInt64()).WithMaxAgeInSeconds(1).WithSkipMe(true); + long result = server.ClientKill(filter, CommandFlags.DemandMaster); + Assert.Equal(1, result); + } + + [Fact] + public void TestClientKillMessageWithAllArguments() + { + long id = 101; + ClientType type = ClientType.Normal; + string userName = "user1"; + EndPoint endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234); + EndPoint serverEndpoint = new IPEndPoint(IPAddress.Parse("198.0.0.1"), 6379); + bool skipMe = true; + long maxAge = 102; + + var filter = new ClientKillFilter().WithId(id).WithClientType(type).WithUsername(userName).WithEndpoint(endpoint).WithServerEndpoint(serverEndpoint).WithSkipMe(skipMe).WithMaxAgeInSeconds(maxAge); + List expected = + [ + "KILL", "ID", "101", "TYPE", "normal", "USERNAME", "user1", "ADDR", "127.0.0.1:1234", "LADDR", "198.0.0.1:6379", "SKIPME", "yes", "MAXAGE", "102", + ]; + Assert.Equal(expected, filter.ToList(true)); + } +} diff --git a/tests/StackExchange.Redis.Tests/ClusterShardedTests.cs b/tests/StackExchange.Redis.Tests/ClusterShardedTests.cs new file mode 100644 index 000000000..8af0a1c7b --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ClusterShardedTests.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +[Collection(NonParallelCollection.Name)] +public class ClusterShardedTests(ITestOutputHelper output) : TestBase(output) +{ + protected override string GetConfiguration() => TestConfig.Current.ClusterServersAndPorts + ",connectTimeout=10000"; + + [Fact] + public async Task TestShardedPubsubSubscriberAgainstReconnects() + { + Skip.UnlessLongRunning(); + var channel = RedisChannel.Sharded(Me()); + await using var conn = Create(allowAdmin: true, keepAlive: 1, connectTimeout: 3000, shared: false, require: RedisFeatures.v7_0_0_rc1); + Assert.True(conn.IsConnected); + var db = conn.GetDatabase(); + Assert.Equal(0, await db.PublishAsync(channel, "noClientReceivesThis")); + await Task.Delay(50); // let the sub settle (this isn't needed on RESP3, note) + + var pubsub = conn.GetSubscriber(); + List<(RedisChannel, RedisValue)> received = []; + var queue = await pubsub.SubscribeAsync(channel); + _ = Task.Run(async () => + { + // use queue API to have control over order + await foreach (var item in queue) + { + lock (received) + { + if (item.Channel.IsSharded && item.Channel == channel) received.Add((item.Channel, item.Message)); + } + } + }); + Assert.Equal(1, conn.GetSubscriptionsCount()); + + await Task.Delay(50); // let the sub settle (this isn't needed on RESP3, note) + await db.PingAsync(); + + for (int i = 0; i < 5; i++) + { + // check we get a hit + Assert.Equal(1, await db.PublishAsync(channel, i.ToString())); + } + await Task.Delay(50); // let the sub settle (this isn't needed on RESP3, note) + + // this is endpoint at index 1 which has the hashslot for "testShardChannel" + var server = conn.GetServer(conn.GetEndPoints()[1]); + server.SimulateConnectionFailure(SimulatedFailureType.All); + SetExpectedAmbientFailureCount(2); + + await Task.Delay(4000); + for (int i = 0; i < 5; i++) + { + // check we get a hit + Assert.Equal(1, await db.PublishAsync(channel, i.ToString())); + } + await Task.Delay(50); // let the sub settle (this isn't needed on RESP3, note) + + Assert.Equal(1, conn.GetSubscriptionsCount()); + Assert.Equal(10, received.Count); + ClearAmbientFailures(); + } + + [Fact] + public async Task TestShardedPubsubSubscriberAgainsHashSlotMigration() + { + Skip.UnlessLongRunning(); + var channel = RedisChannel.Sharded(Me()); + await using var conn = Create(allowAdmin: true, keepAlive: 1, connectTimeout: 3000, shared: false, require: RedisFeatures.v7_0_0_rc1); + Assert.True(conn.IsConnected); + var db = conn.GetDatabase(); + Assert.Equal(0, await db.PublishAsync(channel, "noClientReceivesThis")); + await Task.Delay(50); // let the sub settle (this isn't needed on RESP3, note) + + var pubsub = conn.GetSubscriber(); + List<(RedisChannel, RedisValue)> received = []; + var queue = await pubsub.SubscribeAsync(channel); + _ = Task.Run(async () => + { + // use queue API to have control over order + await foreach (var item in queue) + { + lock (received) + { + if (item.Channel.IsSharded && item.Channel == channel) received.Add((item.Channel, item.Message)); + } + } + }); + Assert.Equal(1, conn.GetSubscriptionsCount()); + + await Task.Delay(50); // let the sub settle (this isn't needed on RESP3, note) + await db.PingAsync(); + + for (int i = 0; i < 5; i++) + { + // check we get a hit + Assert.Equal(1, await db.PublishAsync(channel, i.ToString())); + } + await Task.Delay(50); // let the sub settle (this isn't needed on RESP3, note) + + // lets migrate the slot for "testShardChannel" to another node + await DoHashSlotMigrationAsync(); + + await Task.Delay(4000); + for (int i = 0; i < 5; i++) + { + // check we get a hit + Assert.Equal(1, await db.PublishAsync(channel, i.ToString())); + } + await Task.Delay(50); // let the sub settle (this isn't needed on RESP3, note) + + Assert.Equal(1, conn.GetSubscriptionsCount()); + Assert.Equal(10, received.Count); + await RollbackHashSlotMigrationAsync(); + ClearAmbientFailures(); + } + + private Task DoHashSlotMigrationAsync() => MigrateSlotForTestShardChannelAsync(false); + private Task RollbackHashSlotMigrationAsync() => MigrateSlotForTestShardChannelAsync(true); + + private async Task MigrateSlotForTestShardChannelAsync(bool rollback) + { + int hashSlotForTestShardChannel = 7177; + await using var conn = Create(allowAdmin: true, keepAlive: 1, connectTimeout: 5000, shared: false); + var servers = conn.GetServers(); + IServer? serverWithPort7000 = null; + IServer? serverWithPort7001 = null; + + string nodeIdForPort7000 = "780813af558af81518e58e495d63b6e248e80adf"; + string nodeIdForPort7001 = "ea828c6074663c8bd4e705d3e3024d9d1721ef3b"; + foreach (var server in servers) + { + string id = server.Execute("CLUSTER", "MYID").ToString(); + if (id == nodeIdForPort7000) + { + serverWithPort7000 = server; + } + if (id == nodeIdForPort7001) + { + serverWithPort7001 = server; + } + } + + IServer fromServer, toServer; + string fromNode, toNode; + if (rollback) + { + fromServer = serverWithPort7000!; + fromNode = nodeIdForPort7000; + toServer = serverWithPort7001!; + toNode = nodeIdForPort7001; + } + else + { + fromServer = serverWithPort7001!; + fromNode = nodeIdForPort7001; + toServer = serverWithPort7000!; + toNode = nodeIdForPort7000; + } + + try + { + Assert.Equal("OK", toServer.Execute("CLUSTER", "SETSLOT", hashSlotForTestShardChannel, "IMPORTING", fromNode).ToString()); + Assert.Equal("OK", fromServer.Execute("CLUSTER", "SETSLOT", hashSlotForTestShardChannel, "MIGRATING", toNode).ToString()); + Assert.Equal("OK", toServer.Execute("CLUSTER", "SETSLOT", hashSlotForTestShardChannel, "NODE", toNode).ToString()); + Assert.Equal("OK", fromServer!.Execute("CLUSTER", "SETSLOT", hashSlotForTestShardChannel, "NODE", toNode).ToString()); + } + catch (RedisServerException ex) when (ex.Message == "ERR I'm already the owner of hash slot 7177") + { + Log("Slot already migrated."); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SubscribeToWrongServerAsync(bool sharded) + { + // the purpose of this test is to simulate subscribing while a node move is happening, i.e. we send + // the SSUBSCRIBE to the wrong server, get a -MOVED, and redirect; in particular: do we end up *knowing* + // where we actually subscribed to? + // + // note: to check our thinking, we also do this for regular non-sharded channels too; the point here + // being that this should behave *differently*, since there will be no -MOVED + var name = $"{Me()}:{Guid.NewGuid()}"; + var channel = sharded ? RedisChannel.Sharded(name) : RedisChannel.Literal(name).WithKeyRouting(); + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var asKey = (RedisKey)(byte[])channel!; + Assert.False(asKey.IsEmpty); + var shouldBeServer = conn.GetServer(asKey); // this is where it *should* go + + // now intentionally choose *a different* server + var server = conn.GetServers().First(s => !Equals(s.EndPoint, shouldBeServer.EndPoint)); + Log($"Should be {Format.ToString(shouldBeServer.EndPoint)}; routing via {Format.ToString(server.EndPoint)}"); + + var subscriber = Assert.IsType(conn.GetSubscriber()); + var serverEndpoint = conn.GetServerEndPoint(server.EndPoint); + Assert.Equal(server.EndPoint, serverEndpoint.EndPoint); + var queue = await subscriber.SubscribeAsync(channel, server: serverEndpoint); + await Task.Delay(50); + var actual = subscriber.SubscribedEndpoint(channel); + + if (sharded) + { + // we should end up at the correct node, following the -MOVED + Assert.Equal(shouldBeServer.EndPoint, actual); + } + else + { + // we should end up where we *actually sent the message* - there is no -MOVED + Assert.Equal(serverEndpoint.EndPoint, actual); + } + + Log("Unsubscribing..."); + await queue.UnsubscribeAsync(); + Log("Unsubscribed."); + } + + [Fact] + public async Task KeepSubscribedThroughSlotMigrationAsync() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1, allowAdmin: true); + var name = $"{Me()}:{Guid.NewGuid()}"; + var channel = RedisChannel.Sharded(name); + var subscriber = conn.GetSubscriber(); + var queue = await subscriber.SubscribeAsync(channel); + await Task.Delay(50); + var actual = subscriber.SubscribedEndpoint(channel); + Assert.NotNull(actual); + + var asKey = (RedisKey)(byte[])channel!; + Assert.False(asKey.IsEmpty); + var slot = conn.GetHashSlot(asKey); + var viaMap = conn.ServerSelectionStrategy.Select(slot, RedisCommand.SSUBSCRIBE, CommandFlags.None, allowDisconnected: false); + + Log($"Slot {slot}, subscribed to {Format.ToString(actual)} (mapped to {Format.ToString(viaMap?.EndPoint)})"); + Assert.NotNull(viaMap); + Assert.Equal(actual, viaMap.EndPoint); + + var oldServer = conn.GetServer(asKey); // this is where it *should* go + + using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5))) + { + // now publish... we *expect* things to have sorted themselves out + var msg = Guid.NewGuid().ToString(); + var count = await subscriber.PublishAsync(channel, msg); + Assert.Equal(1, count); + + Log("Waiting for message on original subscription..."); + var received = await queue.ReadAsync(timeout.Token); + Log($"Message received: {received.Message}"); + Assert.Equal(msg, (string)received.Message!); + } + + // now intentionally choose *a different* server + var newServer = conn.GetServers().First(s => !Equals(s.EndPoint, oldServer.EndPoint)); + + var nodes = await newServer.ClusterNodesAsync(); + Assert.NotNull(nodes); + var fromNode = nodes[oldServer.EndPoint]?.NodeId; + var toNode = nodes[newServer.EndPoint]?.NodeId; + Assert.NotNull(fromNode); + Assert.NotNull(toNode); + Assert.Equal(oldServer.EndPoint, nodes.GetBySlot(slot)?.EndPoint); + + var ep = subscriber.SubscribedEndpoint(channel); + Log($"Endpoint before migration: {Format.ToString(ep)}"); + Log($"Migrating slot {slot} to {Format.ToString(newServer.EndPoint)}; node {fromNode} -> {toNode}..."); + + // see https://redis.io/docs/latest/commands/cluster-setslot/#redis-cluster-live-resharding-explained + WriteLog("IMPORTING", await newServer.ExecuteAsync("CLUSTER", "SETSLOT", slot, "IMPORTING", fromNode)); + WriteLog("MIGRATING", await oldServer.ExecuteAsync("CLUSTER", "SETSLOT", slot, "MIGRATING", toNode)); + + while (true) + { + var keys = (await oldServer.ExecuteAsync("CLUSTER", "GETKEYSINSLOT", slot, 100)).AsRedisKeyArray()!; + Log($"Migrating {keys.Length} keys..."); + if (keys.Length == 0) break; + foreach (var key in keys) + { + await conn.GetDatabase().KeyMigrateAsync(key, newServer.EndPoint, migrateOptions: MigrateOptions.None); + } + } + + WriteLog("NODE (old)", await newServer.ExecuteAsync("CLUSTER", "SETSLOT", slot, "NODE", toNode)); + WriteLog("NODE (new)", await oldServer.ExecuteAsync("CLUSTER", "SETSLOT", slot, "NODE", toNode)); + + void WriteLog(string caption, RedisResult result) + { + if (result.IsNull) + { + Log($"{caption}: null"); + } + else if (result.Length >= 0) + { + var arr = result.AsRedisValueArray()!; + Log($"{caption}: {arr.Length} items"); + foreach (var item in arr) + { + Log($" {item}"); + } + } + else + { + Log($"{caption}: {result}"); + } + } + + Log("Migration initiated; checking node state..."); + await Task.Delay(100); + ep = subscriber.SubscribedEndpoint(channel); + Log($"Endpoint after migration: {Format.ToString(ep)}"); + Assert.True( + ep is null || ep == newServer.EndPoint, + "Target server after migration should be null or the new server"); + + nodes = await newServer.ClusterNodesAsync(); + Assert.NotNull(nodes); + Assert.Equal(newServer.EndPoint, nodes.GetBySlot(slot)?.EndPoint); + await conn.ConfigureAsync(); + Assert.Equal(newServer, conn.GetServer(asKey)); + + using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5))) + { + // now publish... we *expect* things to have sorted themselves out + var msg = Guid.NewGuid().ToString(); + var count = await subscriber.PublishAsync(channel, msg); + Assert.Equal(1, count); + + Log("Waiting for message on moved subscription..."); + var received = await queue.ReadAsync(timeout.Token); + Log($"Message received: {received.Message}"); + Assert.Equal(msg, (string)received.Message!); + ep = subscriber.SubscribedEndpoint(channel); + Log($"Endpoint after receiving message: {Format.ToString(ep)}"); + } + + Log("Unsubscribing..."); + await queue.UnsubscribeAsync(); + Log("Unsubscribed."); + } +} diff --git a/tests/StackExchange.Redis.Tests/ClusterTests.cs b/tests/StackExchange.Redis.Tests/ClusterTests.cs new file mode 100644 index 000000000..8146dc9be --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ClusterTests.cs @@ -0,0 +1,840 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using StackExchange.Redis.Profiling; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class ClusterTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + protected override string GetConfiguration() => TestConfig.Current.ClusterServersAndPorts + ",connectTimeout=10000"; + + [Fact] + public async Task ExportConfiguration() + { + if (File.Exists("cluster.zip")) File.Delete("cluster.zip"); + Assert.False(File.Exists("cluster.zip")); + await using (var conn = Create(allowAdmin: true)) + using (var file = File.Create("cluster.zip")) + { + conn.ExportConfiguration(file); + } + Assert.True(File.Exists("cluster.zip")); + } + + [Fact] + public async Task ConnectUsesSingleSocket() + { + for (int i = 0; i < 5; i++) + { + await using var conn = Create(failMessage: i + ": ", log: Writer); + + foreach (var ep in conn.GetEndPoints()) + { + var srv = conn.GetServer(ep); + var counters = srv.GetCounters(); + Log($"{i}; interactive, {ep}, count: {counters.Interactive.SocketCount}"); + Log($"{i}; subscription, {ep}, count: {counters.Subscription.SocketCount}"); + } + foreach (var ep in conn.GetEndPoints()) + { + var srv = conn.GetServer(ep); + var counters = srv.GetCounters(); + Assert.Equal(1, counters.Interactive.SocketCount); + Assert.Equal(TestContext.Current.IsResp3() ? 0 : 1, counters.Subscription.SocketCount); + } + } + } + + [Fact] + public async Task CanGetTotalStats() + { + await using var conn = Create(); + + var counters = conn.GetCounters(); + Log(counters.ToString()); + } + + private void PrintEndpoints(EndPoint[] endpoints) + { + Log($"Endpoints Expected: {TestConfig.Current.ClusterStartPort}+{TestConfig.Current.ClusterServerCount}"); + Log("Endpoints Found:"); + foreach (var endpoint in endpoints) + { + Log(" Endpoint: " + endpoint); + } + } + + [Fact] + public async Task Connect() + { + await using var conn = Create(log: Writer); + + var expectedPorts = new HashSet(Enumerable.Range(TestConfig.Current.ClusterStartPort, TestConfig.Current.ClusterServerCount)); + var endpoints = conn.GetEndPoints(); + if (TestConfig.Current.ClusterServerCount != endpoints.Length) + { + PrintEndpoints(endpoints); + } + + Assert.Equal(TestConfig.Current.ClusterServerCount, endpoints.Length); + int primaries = 0, replicas = 0; + var failed = new List(); + foreach (var endpoint in endpoints) + { + var server = conn.GetServer(endpoint); + if (!server.IsConnected) + { + failed.Add(endpoint); + } + Log("endpoint:" + endpoint); + Assert.Equal(endpoint, server.EndPoint); + + Log("endpoint-type:" + endpoint); + Assert.IsType(endpoint); + + Log("port:" + endpoint); + Assert.True(expectedPorts.Remove(((IPEndPoint)endpoint).Port)); + + Log("server-type:" + endpoint); + Assert.Equal(ServerType.Cluster, server.ServerType); + + if (server.IsReplica) replicas++; + else primaries++; + } + if (failed.Count != 0) + { + Log("{0} failues", failed.Count); + foreach (var fail in failed) + { + Log(fail.ToString()); + } + Assert.Fail("not all servers connected"); + } + + Assert.Equal(TestConfig.Current.ClusterServerCount / 2, replicas); + Assert.Equal(TestConfig.Current.ClusterServerCount / 2, primaries); + } + + [Fact] + public async Task TestIdentity() + { + await using var conn = Create(); + + RedisKey key = Guid.NewGuid().ToByteArray(); + var ep = conn.GetDatabase().IdentifyEndpoint(key); + Assert.NotNull(ep); + Assert.Equal(ep, conn.GetServer(ep).ClusterConfiguration?.GetBySlot(key)?.EndPoint); + } + + [Fact] + public async Task IntentionalWrongServer() + { + static string? StringGet(IServer server, RedisKey key, CommandFlags flags = CommandFlags.None) + => (string?)server.Execute("GET", [key], flags); + + await using var conn = Create(); + + var endpoints = conn.GetEndPoints(); + var servers = endpoints.Select(e => conn.GetServer(e)).ToList(); + + var key = Me(); + const string value = "abc"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, value, flags: CommandFlags.FireAndForget); + await servers[0].PingAsync(); + var config = servers[0].ClusterConfiguration; + Assert.NotNull(config); + int slot = conn.HashSlot(key); + var rightPrimaryNode = config.GetBySlot(key); + Assert.NotNull(rightPrimaryNode); + Log($"Right Primary: {rightPrimaryNode.EndPoint} {rightPrimaryNode.NodeId}"); + + Assert.NotNull(rightPrimaryNode.EndPoint); + string? a = StringGet(conn.GetServer(rightPrimaryNode.EndPoint), key); + Assert.Equal(value, a); // right primary + + var node = config.Nodes.FirstOrDefault(x => !x.IsReplica && x.NodeId != rightPrimaryNode.NodeId); + Assert.NotNull(node); + Log($"Using Primary: {node.EndPoint} {node.NodeId}"); + { + Assert.NotNull(node.EndPoint); + string? b = StringGet(conn.GetServer(node.EndPoint), key); + Assert.Equal(value, b); // wrong primary, allow redirect + + var ex = Assert.Throws(() => StringGet(conn.GetServer(node.EndPoint), key, CommandFlags.NoRedirect)); + Assert.StartsWith($"Key has MOVED to Endpoint {rightPrimaryNode.EndPoint} and hashslot {slot}", ex.Message); + } + + node = config.Nodes.FirstOrDefault(x => x.IsReplica && x.ParentNodeId == rightPrimaryNode.NodeId); + Assert.NotNull(node); + { + Assert.NotNull(node.EndPoint); + string? d = StringGet(conn.GetServer(node.EndPoint), key); + Assert.Equal(value, d); // right replica + } + + node = config.Nodes.FirstOrDefault(x => x.IsReplica && x.ParentNodeId != rightPrimaryNode.NodeId); + Assert.NotNull(node); + { + Assert.NotNull(node.EndPoint); + string? e = StringGet(conn.GetServer(node.EndPoint), key); + Assert.Equal(value, e); // wrong replica, allow redirect + + var ex = Assert.Throws(() => StringGet(conn.GetServer(node.EndPoint), key, CommandFlags.NoRedirect)); + Assert.StartsWith($"Key has MOVED to Endpoint {rightPrimaryNode.EndPoint} and hashslot {slot}", ex.Message); + } + } + + [Fact] + public async Task TransactionWithMultiServerKeys() + { + await using var conn = Create(); + var ex = await Assert.ThrowsAsync(async () => + { + // connect + var cluster = conn.GetDatabase(); + var anyServer = conn.GetServer(conn.GetEndPoints()[0]); + await anyServer.PingAsync(); + Assert.Equal(ServerType.Cluster, anyServer.ServerType); + var config = anyServer.ClusterConfiguration; + Assert.NotNull(config); + + // invent 2 keys that we believe are served by different nodes + string x = Guid.NewGuid().ToString(), y; + var xNode = config.GetBySlot(x); + Assert.NotNull(xNode); + int abort = 1000; + do + { + y = Guid.NewGuid().ToString(); + } + while (--abort > 0 && config.GetBySlot(y) == xNode); + if (abort == 0) Assert.Skip("failed to find a different node to use"); + var yNode = config.GetBySlot(y); + Assert.NotNull(yNode); + Log("x={0}, served by {1}", x, xNode.NodeId); + Log("y={0}, served by {1}", y, yNode.NodeId); + Assert.NotEqual(xNode.NodeId, yNode.NodeId); + + // wipe those keys + cluster.KeyDelete(x, CommandFlags.FireAndForget); + cluster.KeyDelete(y, CommandFlags.FireAndForget); + + // create a transaction that attempts to assign both keys + var tran = cluster.CreateTransaction(); + tran.AddCondition(Condition.KeyNotExists(x)); + tran.AddCondition(Condition.KeyNotExists(y)); + _ = tran.StringSetAsync(x, "x-val"); + _ = tran.StringSetAsync(y, "y-val"); + tran.Execute(); + + Assert.Fail("Expected single-slot rules to apply"); + // the rest no longer applies while we are following single-slot rules + + //// check that everything was aborted + // Assert.False(success, "tran aborted"); + // Assert.True(setX.IsCanceled, "set x cancelled"); + // Assert.True(setY.IsCanceled, "set y cancelled"); + // var existsX = cluster.KeyExistsAsync(x); + // var existsY = cluster.KeyExistsAsync(y); + // Assert.False(cluster.Wait(existsX), "x exists"); + // Assert.False(cluster.Wait(existsY), "y exists"); + }); + Assert.Equal("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot", ex.Message); + } + + [Fact] + public async Task TransactionWithSameServerKeys() + { + await using var conn = Create(); + var ex = await Assert.ThrowsAsync(async () => + { + // connect + var cluster = conn.GetDatabase(); + var anyServer = conn.GetServer(conn.GetEndPoints()[0]); + await anyServer.PingAsync(); + var config = anyServer.ClusterConfiguration; + Assert.NotNull(config); + + // invent 2 keys that we believe are served by different nodes + string x = Guid.NewGuid().ToString(), y; + var xNode = config.GetBySlot(x); + int abort = 1000; + do + { + y = Guid.NewGuid().ToString(); + } + while (--abort > 0 && config.GetBySlot(y) != xNode); + Assert.SkipWhen(abort == 0, "failed to find a key with the same node to use"); + var yNode = config.GetBySlot(y); + Assert.NotNull(xNode); + Log("x={0}, served by {1}", x, xNode.NodeId); + Assert.NotNull(yNode); + Log("y={0}, served by {1}", y, yNode.NodeId); + Assert.Equal(xNode.NodeId, yNode.NodeId); + + // wipe those keys + cluster.KeyDelete(x, CommandFlags.FireAndForget); + cluster.KeyDelete(y, CommandFlags.FireAndForget); + + // create a transaction that attempts to assign both keys + var tran = cluster.CreateTransaction(); + tran.AddCondition(Condition.KeyNotExists(x)); + tran.AddCondition(Condition.KeyNotExists(y)); + _ = tran.StringSetAsync(x, "x-val"); + _ = tran.StringSetAsync(y, "y-val"); + tran.Execute(); + + Assert.Fail("Expected single-slot rules to apply"); + // the rest no longer applies while we are following single-slot rules + + //// check that everything was aborted + // Assert.True(success, "tran aborted"); + // Assert.False(setX.IsCanceled, "set x cancelled"); + // Assert.False(setY.IsCanceled, "set y cancelled"); + // var existsX = cluster.KeyExistsAsync(x); + // var existsY = cluster.KeyExistsAsync(y); + // Assert.True(cluster.Wait(existsX), "x exists"); + // Assert.True(cluster.Wait(existsY), "y exists"); + }); + Assert.Equal("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot", ex.Message); + } + + [Fact] + public async Task TransactionWithSameSlotKeys() + { + await using var conn = Create(); + + // connect + var cluster = conn.GetDatabase(); + var anyServer = conn.GetServer(conn.GetEndPoints()[0]); + await anyServer.PingAsync(); + var config = anyServer.ClusterConfiguration; + Assert.NotNull(config); + + // invent 2 keys that we believe are in the same slot + var guid = Guid.NewGuid().ToString(); + string x = "/{" + guid + "}/foo", y = "/{" + guid + "}/bar"; + + Assert.Equal(conn.HashSlot(x), conn.HashSlot(y)); + var xNode = config.GetBySlot(x); + var yNode = config.GetBySlot(y); + Assert.NotNull(xNode); + Log("x={0}, served by {1}", x, xNode.NodeId); + Assert.NotNull(yNode); + Log("y={0}, served by {1}", y, yNode.NodeId); + Assert.Equal(xNode.NodeId, yNode.NodeId); + + // wipe those keys + cluster.KeyDelete(x, CommandFlags.FireAndForget); + cluster.KeyDelete(y, CommandFlags.FireAndForget); + + // create a transaction that attempts to assign both keys + var tran = cluster.CreateTransaction(); + tran.AddCondition(Condition.KeyNotExists(x)); + tran.AddCondition(Condition.KeyNotExists(y)); + var setX = tran.StringSetAsync(x, "x-val"); + var setY = tran.StringSetAsync(y, "y-val"); + bool success = tran.Execute(); + + // check that everything was aborted + Assert.True(success, "tran aborted"); + Assert.False(setX.IsCanceled, "set x cancelled"); + Assert.False(setY.IsCanceled, "set y cancelled"); + var existsX = cluster.KeyExistsAsync(x); + var existsY = cluster.KeyExistsAsync(y); + Assert.True(cluster.Wait(existsX), "x exists"); + Assert.True(cluster.Wait(existsY), "y exists"); + } + + [Theory] + [InlineData(null, 10)] + [InlineData(null, 100)] + [InlineData("abc", 10)] + [InlineData("abc", 100)] + public async Task Keys(string? pattern, int pageSize) + { + await using var conn = Create(allowAdmin: true); + + var dbId = TestConfig.GetDedicatedDB(conn); + var server = conn.GetEndPoints().Select(x => conn.GetServer(x)).First(x => !x.IsReplica); + await server.FlushDatabaseAsync(dbId); + try + { + Assert.False(server.Keys(dbId, pattern: pattern, pageSize: pageSize).Any()); + Log($"Complete: '{pattern}' / {pageSize}"); + } + catch + { + Log($"Failed: '{pattern}' / {pageSize}"); + throw; + } + } + + [Theory] + [InlineData("", 0)] + [InlineData("abc", 7638)] + [InlineData("{abc}", 7638)] + [InlineData("abcdef", 15101)] + [InlineData("abc{abc}def", 7638)] + [InlineData("c", 7365)] + [InlineData("g", 7233)] + [InlineData("d", 11298)] + + [InlineData("user1000", 3443)] + [InlineData("{user1000}", 3443)] + [InlineData("abc{user1000}", 3443)] + [InlineData("abc{user1000}def", 3443)] + [InlineData("{user1000}.following", 3443)] + [InlineData("{user1000}.followers", 3443)] + + [InlineData("foo{}{bar}", 8363)] + + [InlineData("foo{{bar}}zap", 4015)] + [InlineData("{bar", 4015)] + + [InlineData("foo{bar}{zap}", 5061)] + [InlineData("bar", 5061)] + + public async Task HashSlots(string key, int slot) + { + await using var conn = Create(connectTimeout: 5000); + + Assert.Equal(slot, conn.HashSlot(key)); + } + + [Fact] + public async Task SScan() + { + await using var conn = Create(); + + RedisKey key = "a"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + int totalUnfiltered = 0, totalFiltered = 0; + for (int i = 0; i < 1000; i++) + { + db.SetAdd(key, i, CommandFlags.FireAndForget); + totalUnfiltered += i; + if (i.ToString().Contains('3')) totalFiltered += i; + } + var unfilteredActual = db.SetScan(key).Select(x => (int)x).Sum(); + var filteredActual = db.SetScan(key, "*3*").Select(x => (int)x).Sum(); + Assert.Equal(totalUnfiltered, unfilteredActual); + Assert.Equal(totalFiltered, filteredActual); + } + + [Fact] + public async Task GetConfig() + { + await using var conn = Create(allowAdmin: true, log: Writer); + + var endpoints = conn.GetEndPoints(); + var server = conn.GetServer(endpoints[0]); + var nodes = server.ClusterNodes(); + Assert.NotNull(nodes); + + Log("Endpoints:"); + foreach (var endpoint in endpoints) + { + Log(endpoint.ToString()); + } + Log("Nodes:"); + foreach (var node in nodes.Nodes.OrderBy(x => x)) + { + Log(node.ToString()); + } + + Assert.Equal(TestConfig.Current.ClusterServerCount, endpoints.Length); + Assert.Equal(TestConfig.Current.ClusterServerCount, nodes.Nodes.Count); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1004:Test methods should not be skipped", Justification = "Because.")] + [Fact(Skip = "FlushAllDatabases")] + public async Task AccessRandomKeys() + { + await using var conn = Create(allowAdmin: true); + + var cluster = conn.GetDatabase(); + int slotMovedCount = 0; + conn.HashSlotMoved += (s, a) => + { + Assert.NotNull(a.OldEndPoint); + Log("{0} moved from {1} to {2}", a.HashSlot, Describe(a.OldEndPoint), Describe(a.NewEndPoint)); + Interlocked.Increment(ref slotMovedCount); + }; + var pairs = new Dictionary(); + const int COUNT = 500; + int index = 0; + + var servers = conn.GetEndPoints().Select(x => conn.GetServer(x)).ToList(); + foreach (var server in servers) + { + if (!server.IsReplica) + { + await server.PingAsync(); + await server.FlushAllDatabasesAsync(); + } + } + + for (int i = 0; i < COUNT; i++) + { + var key = Guid.NewGuid().ToString(); + var value = Guid.NewGuid().ToString(); + pairs.Add(key, value); + cluster.StringSet(key, value, flags: CommandFlags.FireAndForget); + } + + var expected = new string[COUNT]; + var actual = new Task[COUNT]; + index = 0; + foreach (var pair in pairs) + { + expected[index] = pair.Value; + actual[index] = cluster.StringGetAsync(pair.Key); + index++; + } + cluster.WaitAll(actual); + for (int i = 0; i < COUNT; i++) + { + Assert.Equal(expected[i], actual[i].Result); + } + + int total = 0; + Parallel.ForEach(servers, server => + { + if (!server.IsReplica) + { + int count = server.Keys(pageSize: 100).Count(); + Log("{0} has {1} keys", server.EndPoint, count); + Interlocked.Add(ref total, count); + } + }); + + foreach (var server in servers) + { + var counters = server.GetCounters(); + Log(counters.ToString()); + } + int final = Interlocked.CompareExchange(ref total, 0, 0); + Assert.Equal(COUNT, final); + Assert.Equal(0, Interlocked.CompareExchange(ref slotMovedCount, 0, 0)); + } + + [Theory] + [InlineData(CommandFlags.DemandMaster, false)] + [InlineData(CommandFlags.DemandReplica, true)] + [InlineData(CommandFlags.PreferMaster, false)] + [InlineData(CommandFlags.PreferReplica, true)] + public async Task GetFromRightNodeBasedOnFlags(CommandFlags flags, bool isReplica) + { + await using var conn = Create(allowAdmin: true); + + var db = conn.GetDatabase(); + for (int i = 0; i < 500; i++) + { + var key = Guid.NewGuid().ToString(); + var endpoint = db.IdentifyEndpoint(key, flags); + Assert.NotNull(endpoint); + var server = conn.GetServer(endpoint); + Assert.Equal(isReplica, server.IsReplica); + } + } + + private static string Describe(EndPoint endpoint) => endpoint?.ToString() ?? "(unknown)"; + + [Fact] + public async Task SimpleProfiling() + { + await using var conn = Create(log: Writer); + + var profiler = new ProfilingSession(); + var key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + conn.RegisterProfiler(() => profiler); + db.StringSet(key, "world"); + var val = db.StringGet(key); + Assert.Equal("world", val); + + var msgs = profiler.FinishProfiling().Where(m => m.Command == "GET" || m.Command == "SET").ToList(); + foreach (var msg in msgs) + { + Log("Profiler Message: " + Environment.NewLine + msg); + } + Log("Checking GET..."); + Assert.Contains(msgs, m => m.Command == "GET"); + Log("Checking SET..."); + Assert.Contains(msgs, m => m.Command == "SET"); + Assert.Equal(2, msgs.Count(m => m.RetransmissionOf is null)); + + var arr = msgs.Where(m => m.RetransmissionOf is null).ToArray(); + Assert.Equal("SET", arr[0].Command); + Assert.Equal("GET", arr[1].Command); + } + + [Fact] + public async Task MultiKeyQueryFails() + { + var keys = InventKeys(); // note the rules expected of this data are enforced in GroupedQueriesWork + + await using var conn = Create(); + + var ex = Assert.Throws(() => conn.GetDatabase(0).StringGet(keys)); + Assert.Contains("Multi-key operations must involve a single slot", ex.Message); + } + + private static RedisKey[] InventKeys() + { + RedisKey[] keys = new RedisKey[256]; + Random rand = new Random(12324); + string InventString() + { + const string alphabet = "abcdefghijklmnopqrstuvwxyz012345689"; + var len = rand.Next(10, 50); + char[] chars = new char[len]; + for (int i = 0; i < len; i++) + chars[i] = alphabet[rand.Next(alphabet.Length)]; + return new string(chars); + } + + for (int i = 0; i < keys.Length; i++) + { + keys[i] = InventString(); + } + return keys; + } + + [Fact] + public async Task GroupedQueriesWork() + { + // note it doesn't matter that the data doesn't exist for this; + // the point here is that the entire thing *won't work* otherwise, + // as per above test + var keys = InventKeys(); + await using var conn = Create(); + + var grouped = keys.GroupBy(key => conn.GetHashSlot(key)).ToList(); + Assert.True(grouped.Count > 1); // check not all a super-group + Assert.True(grouped.Count < keys.Length); // check not all singleton groups + Assert.Equal(keys.Length, grouped.Sum(x => x.Count())); // check they're all there + Assert.Contains(grouped, x => x.Count() > 1); // check at least one group with multiple items (redundant from above, but... meh) + + Log($"{grouped.Count} groups, min: {grouped.Min(x => x.Count())}, max: {grouped.Max(x => x.Count())}, avg: {grouped.Average(x => x.Count())}"); + + var db = conn.GetDatabase(0); + var all = grouped.SelectMany(grp => + { + var grpKeys = grp.ToArray(); + var values = db.StringGet(grpKeys); + return grpKeys.Zip(values, (key, val) => new { key, val }); + }).ToDictionary(x => x.key, x => x.val); + + Assert.Equal(keys.Length, all.Count); + } + + [Fact] + public async Task MovedProfiling() + { + var key = Me(); + const string Value = "redirected-value"; + + var profiler = new ProfilingTests.PerThreadProfiler(); + + await using var conn = Create(); + + conn.RegisterProfiler(profiler.GetSession); + + var endpoints = conn.GetEndPoints(); + var servers = endpoints.Select(e => conn.GetServer(e)); + + var db = conn.GetDatabase(); + db.KeyDelete(key); + db.StringSet(key, Value); + var config = servers.First().ClusterConfiguration; + Assert.NotNull(config); + + // int slot = conn.HashSlot(Key); + var rightPrimaryNode = config.GetBySlot(key); + Assert.NotNull(rightPrimaryNode); + + Assert.NotNull(rightPrimaryNode.EndPoint); + string? a = (string?)conn.GetServer(rightPrimaryNode.EndPoint).Execute("GET", key); + Assert.Equal(Value, a); // right primary + + var wrongPrimaryNode = config.Nodes.FirstOrDefault(x => !x.IsReplica && x.NodeId != rightPrimaryNode.NodeId); + Assert.NotNull(wrongPrimaryNode); + + Assert.NotNull(wrongPrimaryNode.EndPoint); + string? b = (string?)conn.GetServer(wrongPrimaryNode.EndPoint).Execute("GET", key); + Assert.Equal(Value, b); // wrong primary, allow redirect + + var msgs = profiler.GetSession().FinishProfiling().ToList(); + + // verify that things actually got recorded properly, and the retransmission profilings are connected as expected + { + // expect 1 DEL, 1 SET, 1 GET (to right primary), 1 GET (to wrong primary) that was responded to by an ASK, and 1 GET (to right primary or a replica of it) + Assert.Equal(5, msgs.Count); + Assert.Equal(1, msgs.Count(c => c.Command == "DEL" || c.Command == "UNLINK")); + Assert.Equal(1, msgs.Count(c => c.Command == "SET")); + Assert.Equal(3, msgs.Count(c => c.Command == "GET")); + + var toRightPrimaryNotRetransmission = msgs.Where(m => m.Command == "GET" && m.EndPoint.Equals(rightPrimaryNode.EndPoint) && m.RetransmissionOf == null); + Assert.Single(toRightPrimaryNotRetransmission); + + var toWrongPrimaryWithoutRetransmission = msgs.Where(m => m.Command == "GET" && m.EndPoint.Equals(wrongPrimaryNode.EndPoint) && m.RetransmissionOf == null).ToList(); + Assert.Single(toWrongPrimaryWithoutRetransmission); + + var toRightPrimaryOrReplicaAsRetransmission = msgs.Where(m => m.Command == "GET" && (m.EndPoint.Equals(rightPrimaryNode.EndPoint) || rightPrimaryNode.Children.Any(c => m.EndPoint.Equals(c.EndPoint))) && m.RetransmissionOf != null).ToList(); + Assert.Single(toRightPrimaryOrReplicaAsRetransmission); + + var originalWrongPrimary = toWrongPrimaryWithoutRetransmission.Single(); + var retransmissionToRight = toRightPrimaryOrReplicaAsRetransmission.Single(); + + Assert.True(ReferenceEquals(originalWrongPrimary, retransmissionToRight.RetransmissionOf)); + } + + foreach (var msg in msgs) + { + Assert.True(msg.CommandCreated != default(DateTime)); + Assert.True(msg.CreationToEnqueued > TimeSpan.Zero); + Assert.True(msg.EnqueuedToSending > TimeSpan.Zero); + Assert.True(msg.SentToResponse > TimeSpan.Zero); + Assert.True(msg.ResponseToCompletion >= TimeSpan.Zero); // this can be immeasurably fast + Assert.True(msg.ElapsedTime > TimeSpan.Zero); + + if (msg.RetransmissionOf != null) + { + // imprecision of DateTime.UtcNow makes this pretty approximate + Assert.True(msg.RetransmissionOf.CommandCreated <= msg.CommandCreated); + Assert.Equal(RetransmissionReasonType.Moved, msg.RetransmissionReason); + } + else + { + Assert.False(msg.RetransmissionReason.HasValue); + } + } + } + + [Fact] + public async Task ConnectIncludesSubscriber() + { + await using var conn = Create(keepAlive: 1, connectTimeout: 3000, shared: false); + + var db = conn.GetDatabase(); + await db.PingAsync(); + Assert.True(conn.IsConnected); + + foreach (var server in conn.GetServerSnapshot()) + { + Assert.Equal(PhysicalBridge.State.ConnectedEstablished, server.InteractiveConnectionState); + Assert.Equal(PhysicalBridge.State.ConnectedEstablished, server.SubscriptionConnectionState); + } + } + + [Theory] + [InlineData(true, false)] + [InlineData(true, true)] + [InlineData(false, false)] + [InlineData(false, true)] + public async Task ClusterPubSub(bool sharded, bool withKeyRouting) + { + var guid = Guid.NewGuid().ToString(); + var channel = sharded ? RedisChannel.Sharded(guid) : RedisChannel.Literal(guid); + if (withKeyRouting) + { + channel = channel.WithKeyRouting(); + } + await using var conn = Create(keepAlive: 1, connectTimeout: 3000, shared: false, require: sharded ? RedisFeatures.v7_0_0_rc1 : RedisFeatures.v2_0_0); + Assert.True(conn.IsConnected); + + var pubsub = conn.GetSubscriber(); + HashSet eps = []; + for (int i = 0; i < 10; i++) + { + var ep = Format.ToString(await pubsub.IdentifyEndpointAsync(channel)); + Log($"Channel {channel} => {ep}"); + eps.Add(ep); + } + + if (sharded | withKeyRouting) + { + Assert.Single(eps); + } + else + { + // if not routed: we should have at least two different endpoints + Assert.True(eps.Count > 1); + } + + List<(RedisChannel, RedisValue)> received = []; + var queue = await pubsub.SubscribeAsync(channel); + _ = Task.Run(async () => + { + // use queue API to have control over order + await foreach (var item in queue) + { + lock (received) + { + received.Add((item.Channel, item.Message)); + } + } + }); + var subscribedEp = Format.ToString(pubsub.SubscribedEndpoint(channel)); + Log($"Subscribed to {subscribedEp}"); + Assert.NotNull(subscribedEp); + if (sharded | withKeyRouting) + { + Assert.Equal(eps.Single(), subscribedEp); + } + var db = conn.GetDatabase(); + await Task.Delay(50); // let the sub settle (this isn't needed on RESP3, note) + await db.PingAsync(); + for (int i = 0; i < 10; i++) + { + // publish + var receivers = await db.PublishAsync(channel, i.ToString()); + + // check we get a hit (we are the only subscriber, and because we prefer to + // use our own subscribed connection: we can reliably expect to see this hit) + Log($"Published {i} to {receivers} receiver(s) against the receiving server."); + Assert.Equal(1, receivers); + } + + await Task.Delay(250); // let the sub settle (this isn't needed on RESP3, note) + await db.PingAsync(); + await pubsub.UnsubscribeAsync(channel); + + (RedisChannel Channel, RedisValue Value)[] snap; + lock (received) + { + snap = received.ToArray(); // in case of concurrency + } + Log("items received: {0}", snap.Length); + Assert.Equal(10, snap.Length); + // separate log and validate loop here simplifies debugging (ask me how I know!) + for (int i = 0; i < 10; i++) + { + var pair = snap[i]; + Log("element {0}: {1}/{2}", i, pair.Channel, pair.Value); + } + // even if not routed: we can expect the *order* to be correct, since there's + // only one publisher (us), and we prefer to publish via our own subscription + for (int i = 0; i < 10; i++) + { + var pair = snap[i]; + Assert.Equal(channel, pair.Channel); + Assert.Equal(i, pair.Value); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/CommandTests.cs b/tests/StackExchange.Redis.Tests/CommandTests.cs new file mode 100644 index 000000000..42df92dd1 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/CommandTests.cs @@ -0,0 +1,56 @@ +using System.Net; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class CommandTests +{ + [Fact] + public void CommandByteLength() + { + Assert.Equal(31, CommandBytes.MaxLength); + } + + [Fact] + public void CheckCommandContents() + { + for (int len = 0; len <= CommandBytes.MaxLength; len++) + { + var s = new string('A', len); + CommandBytes b = s; + Assert.Equal(len, b.Length); + + var t = b.ToString(); + Assert.Equal(s, t); + + CommandBytes b2 = t; + Assert.Equal(b, b2); + + Assert.Equal(len == 0, ReferenceEquals(s, t)); + } + } + + [Fact] + public void Basic() + { + var config = ConfigurationOptions.Parse(".,$PING=p"); + Assert.Single(config.EndPoints); + config.SetDefaultPorts(); + Assert.Contains(new DnsEndPoint(".", 6379), config.EndPoints); + var map = config.CommandMap; + Assert.Equal("$PING=P", map.ToString()); + Assert.Equal(".:6379,$PING=P", config.ToString()); + } + + [Theory] + [InlineData("redisql.CREATE_STATEMENT")] + [InlineData("INSERTINTOTABLE1STMT")] + public void CanHandleNonTrivialCommands(string command) + { + var cmd = new CommandBytes(command); + Assert.Equal(command.Length, cmd.Length); + Assert.Equal(command.ToUpperInvariant(), cmd.ToString()); + + Assert.Equal(31, CommandBytes.MaxLength); + } +} diff --git a/tests/StackExchange.Redis.Tests/CommandTimeoutTests.cs b/tests/StackExchange.Redis.Tests/CommandTimeoutTests.cs new file mode 100644 index 000000000..04e1ca624 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/CommandTimeoutTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class CommandTimeoutTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task DefaultHeartbeatTimeout() + { + Skip.UnlessLongRunning(); + var options = ConfigurationOptions.Parse(TestConfig.Current.PrimaryServerAndPort); + options.AllowAdmin = true; + options.AsyncTimeout = 1000; + + await using var pauseConn = ConnectionMultiplexer.Connect(options); + await using var conn = ConnectionMultiplexer.Connect(options); + + var pauseServer = GetServer(pauseConn); + var pauseTask = pauseServer.ExecuteAsync("CLIENT", "PAUSE", 5000); + + var key = Me(); + var db = conn.GetDatabase(); + var sw = ValueStopwatch.StartNew(); + var ex = await Assert.ThrowsAsync(async () => await db.StringGetAsync(key)); + Log(ex.Message); + var duration = sw.GetElapsedTime(); + Assert.True(duration < TimeSpan.FromSeconds(4000), $"Duration ({duration.Milliseconds} ms) should be less than 4000ms"); + + // Await as to not bias the next test + await pauseTask; + } + +#if DEBUG + [Fact] + public async Task DefaultHeartbeatLowTimeout() + { + var options = ConfigurationOptions.Parse(TestConfig.Current.PrimaryServerAndPort); + options.AllowAdmin = true; + options.AsyncTimeout = 50; + options.HeartbeatInterval = TimeSpan.FromMilliseconds(100); + + await using var pauseConn = await ConnectionMultiplexer.ConnectAsync(options); + await using var conn = await ConnectionMultiplexer.ConnectAsync(options); + + var pauseServer = GetServer(pauseConn); + var pauseTask = pauseServer.ExecuteAsync("CLIENT", "PAUSE", 2000); + + var key = Me(); + var db = conn.GetDatabase(); + var sw = ValueStopwatch.StartNew(); + var ex = await Assert.ThrowsAsync(async () => await db.StringGetAsync(key)); + Log(ex.Message); + var duration = sw.GetElapsedTime(); + Assert.True(duration < TimeSpan.FromSeconds(250), $"Duration ({duration.Milliseconds} ms) should be less than 250ms"); + + // Await as to not bias the next test + await pauseTask; + } +#endif +} diff --git a/tests/StackExchange.Redis.Tests/ConfigTests.cs b/tests/StackExchange.Redis.Tests/ConfigTests.cs new file mode 100644 index 000000000..0c9286e17 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ConfigTests.cs @@ -0,0 +1,774 @@ +using System; +using System.Globalization; +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Security.Authentication; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging.Abstractions; +using StackExchange.Redis.Configuration; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class ConfigTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + public Version DefaultVersion = new(3, 0, 0); + + [Fact] + public void ExpectedFields() + { + // if this test fails, check that you've updated ConfigurationOptions.Clone(), then: fix the test! + // this is a simple but pragmatic "have you considered?" check + var fields = Array.ConvertAll( + typeof(ConfigurationOptions).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), + x => Regex.Replace(x.Name, """^<(\w+)>k__BackingField$""", "$1")); + Array.Sort(fields); + Assert.Equal( + new[] + { + "abortOnConnectFail", + "allowAdmin", + "asyncTimeout", + "backlogPolicy", + "BeforeSocketConnect", + "CertificateSelection", + "CertificateValidation", + "ChannelPrefix", + "checkCertificateRevocation", + "ClientName", + "commandMap", + "configChannel", + "configCheckSeconds", + "connectRetry", + "connectTimeout", + "DefaultDatabase", + "defaultOptions", + "defaultVersion", + "EndPoints", + "heartbeatConsistencyChecks", + "heartbeatInterval", + "highIntegrity", + "includeDetailInExceptions", + "includePerformanceCountersInExceptions", + "keepAlive", + "LibraryName", + "loggerFactory", + "password", + "Protocol", + "proxy", + "reconnectRetryPolicy", + "resolveDns", + "responseTimeout", + "ServiceName", + "setClientLibrary", + "SocketManager", + "ssl", + #if !NETFRAMEWORK + "SslClientAuthenticationOptions", + #endif + "sslHost", + "SslProtocols", + "syncTimeout", + "tieBreaker", + "Tunnel", + "user", + }, + fields); + } + + [Fact] + public void SslProtocols_SingleValue() + { + var options = ConfigurationOptions.Parse("myhost,sslProtocols=Tls12"); + Assert.Equal(SslProtocols.Tls12, options.SslProtocols.GetValueOrDefault()); + } + + [Fact] + public void SslProtocols_MultipleValues() + { + var options = ConfigurationOptions.Parse("myhost,sslProtocols=Tls12|Tls13"); + Assert.Equal(SslProtocols.Tls12 | SslProtocols.Tls13, options.SslProtocols.GetValueOrDefault()); + } + + [Theory] + [InlineData("checkCertificateRevocation=false", false)] + [InlineData("checkCertificateRevocation=true", true)] + [InlineData("", true)] + public void ConfigurationOption_CheckCertificateRevocation(string conString, bool expectedValue) + { + var options = ConfigurationOptions.Parse($"host,{conString}"); + Assert.Equal(expectedValue, options.CheckCertificateRevocation); + var toString = options.ToString(); + Assert.Contains(conString, toString, StringComparison.CurrentCultureIgnoreCase); + } + + [Fact] + public void SslProtocols_UsingIntegerValue() + { + // The below scenario is for cases where the *targeted* + // .NET framework version (e.g. .NET 4.0) doesn't define an enum value (e.g. Tls11) + // but the OS has been patched with support + const int integerValue = (int)(SslProtocols.Tls12 | SslProtocols.Tls13); + var options = ConfigurationOptions.Parse("myhost,sslProtocols=" + integerValue); + Assert.Equal(SslProtocols.Tls12 | SslProtocols.Tls13, options.SslProtocols.GetValueOrDefault()); + } + + [Fact] + public void SslProtocols_InvalidValue() + { + Assert.Throws(() => ConfigurationOptions.Parse("myhost,sslProtocols=InvalidSslProtocol")); + } + + [Theory] + [InlineData("contoso.redis.cache.windows.net:6380", true)] + [InlineData("contoso.REDIS.CACHE.chinacloudapi.cn:6380", true)] // added a few upper case chars to validate comparison + [InlineData("contoso.redis.cache.usgovcloudapi.net:6380", true)] + [InlineData("contoso.redisenterprise.cache.azure.net:10000", false)] + [InlineData("contoso.redis.azure.net:10000", true)] + [InlineData("contoso.redis.chinacloudapi.cn:10000", true)] + [InlineData("contoso.redis.usgovcloudapi.net:10000", true)] + public void ConfigurationOptionsDefaultForAzure(string hostAndPort, bool sslShouldBeEnabled) + { + Version defaultAzureVersion = new(6, 0, 0); + var options = ConfigurationOptions.Parse(hostAndPort); + Assert.True(options.DefaultVersion.Equals(defaultAzureVersion)); + Assert.False(options.AbortOnConnectFail); + Assert.Equal(sslShouldBeEnabled, options.Ssl); + } + + [Fact] + public void ConfigurationOptionsForAzureWhenSpecified() + { + var options = ConfigurationOptions.Parse("contoso.redis.cache.windows.net,abortConnect=true, version=2.1.1"); + Assert.True(options.DefaultVersion.Equals(new Version(2, 1, 1))); + Assert.True(options.AbortOnConnectFail); + } + + [Fact] + public void ConfigurationOptionsDefaultForNonAzure() + { + var options = ConfigurationOptions.Parse("redis.contoso.com"); + Assert.True(options.DefaultVersion.Equals(DefaultVersion)); + Assert.True(options.AbortOnConnectFail); + } + + [Fact] + public void ConfigurationOptionsDefaultWhenNoEndpointsSpecifiedYet() + { + var options = new ConfigurationOptions(); + Assert.True(options.DefaultVersion.Equals(DefaultVersion)); + Assert.True(options.AbortOnConnectFail); + } + + [Fact] + public void ConfigurationOptionsSyncTimeout() + { + // Default check + var options = new ConfigurationOptions(); + Assert.Equal(5000, options.SyncTimeout); + + options = ConfigurationOptions.Parse("syncTimeout=20"); + Assert.Equal(20, options.SyncTimeout); + } + + [Theory] + [InlineData("127.1:6379", AddressFamily.InterNetwork, "127.0.0.1", 6379)] + [InlineData("127.0.0.1:6379", AddressFamily.InterNetwork, "127.0.0.1", 6379)] + [InlineData("2a01:9820:1:24::1:1:6379", AddressFamily.InterNetworkV6, "2a01:9820:1:24:0:1:1:6379", 0)] + [InlineData("[2a01:9820:1:24::1:1]:6379", AddressFamily.InterNetworkV6, "2a01:9820:1:24::1:1", 6379)] + public void ConfigurationOptionsIPv6Parsing(string configString, AddressFamily family, string address, int port) + { + var options = ConfigurationOptions.Parse(configString); + Assert.Single(options.EndPoints); + var ep = Assert.IsType(options.EndPoints[0]); + Assert.Equal(family, ep.AddressFamily); + Assert.Equal(address, ep.Address.ToString()); + Assert.Equal(port, ep.Port); + } + + [Fact] + public void CanParseAndFormatUnixDomainSocket() + { + const string ConfigString = "!/some/path,allowAdmin=True"; +#if NETFRAMEWORK + var ex = Assert.Throws(() => ConfigurationOptions.Parse(ConfigString)); + Assert.Equal("Unix domain sockets require .NET Core 3 or above", ex.Message); +#else + var config = ConfigurationOptions.Parse(ConfigString); + Assert.True(config.AllowAdmin); + var ep = Assert.IsType(Assert.Single(config.EndPoints)); + Assert.Equal("/some/path", ep.ToString()); + Assert.Equal(ConfigString, config.ToString()); +#endif + } + + [Fact] + public async Task TalkToNonsenseServer() + { + var config = new ConfigurationOptions + { + AbortOnConnectFail = false, + EndPoints = + { + { "127.0.0.1:1234" }, + }, + ConnectTimeout = 200, + }; + var log = new StringWriter(); + await using (var conn = ConnectionMultiplexer.Connect(config, log)) + { + Log(log.ToString()); + Assert.False(conn.IsConnected); + } + } + + [Fact] + public async Task TestManualHeartbeat() + { + var options = ConfigurationOptions.Parse(GetConfiguration()); + options.HeartbeatInterval = TimeSpan.FromMilliseconds(100); + await using var conn = await ConnectionMultiplexer.ConnectAsync(options); + + foreach (var ep in conn.GetServerSnapshot().ToArray()) + { + ep.WriteEverySeconds = 1; + } + + var db = conn.GetDatabase(); + await db.PingAsync(); + + var before = conn.OperationCount; + + Log("Sleeping to test heartbeat..."); + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => conn.OperationCount > before + 1).ForAwait(); + var after = conn.OperationCount; + + Assert.True(after >= before + 1, $"after: {after}, before: {before}"); + } + + [Theory] + [InlineData(0)] + [InlineData(10)] + [InlineData(100)] + [InlineData(200)] + public async Task GetSlowlog(int count) + { + await using var conn = Create(allowAdmin: true); + + var rows = GetAnyPrimary(conn).SlowlogGet(count); + Assert.NotNull(rows); + } + + [Fact] + public async Task ClearSlowlog() + { + await using var conn = Create(allowAdmin: true); + + GetAnyPrimary(conn).SlowlogReset(); + } + + [Fact] + public async Task ClientName() + { + await using var conn = Create(clientName: "Test Rig", allowAdmin: true, shared: false); + + Assert.Equal("Test Rig", conn.ClientName); + + var db = conn.GetDatabase(); + await db.PingAsync(); + + var name = (string?)(await GetAnyPrimary(conn).ExecuteAsync("CLIENT", "GETNAME")); + Assert.Equal("TestRig", name); + } + + [Fact] + public async Task ClientLibraryName() + { + await using var conn = Create(allowAdmin: true, shared: false); + var server = GetAnyPrimary(conn); + + await server.PingAsync(); + var possibleId = conn.GetConnectionId(server.EndPoint, ConnectionType.Interactive); + + if (possibleId is null) + { + Log("(client id not available)"); + return; + } + var id = possibleId.Value; + var libName = server.ClientList().Single(x => x.Id == id).LibraryName; + if (libName is not null) // server-version dependent + { + Log("library name: {0}", libName); + Assert.Equal("SE.Redis", libName); + + conn.AddLibraryNameSuffix("foo"); + conn.AddLibraryNameSuffix("bar"); + conn.AddLibraryNameSuffix("foo"); + + libName = (await server.ClientListAsync()).Single(x => x.Id == id).LibraryName; + Log($"library name: {libName}"); + Assert.Equal("SE.Redis-bar-foo", libName); + } + else + { + Log("(library name not available)"); + } + } + + [Fact] + public async Task DefaultClientName() + { + await using var conn = Create(allowAdmin: true, caller: "", shared: false); // force default naming to kick in + + Assert.Equal($"{Environment.MachineName}(SE.Redis-v{Utils.GetLibVersion()})", conn.ClientName); + var db = conn.GetDatabase(); + await db.PingAsync(); + + var name = (string?)GetAnyPrimary(conn).Execute("CLIENT", "GETNAME"); + Assert.Equal($"{Environment.MachineName}(SE.Redis-v{Utils.GetLibVersion()})", name); + } + + [Fact] + public async Task ReadConfigWithConfigDisabled() + { + await using var conn = Create(allowAdmin: true, disabledCommands: ["config", "info"]); + + var server = GetAnyPrimary(conn); + var ex = Assert.Throws(() => server.ConfigGet()); + Assert.Equal("This operation has been disabled in the command-map and cannot be used: CONFIG", ex.Message); + } + + [Fact] + public async Task ConnectWithSubscribeDisabled() + { + await using var conn = Create(allowAdmin: true, disabledCommands: ["subscribe"]); + + Assert.True(conn.IsConnected); + var servers = conn.GetServerSnapshot(); + Assert.True(servers[0].IsConnected); + if (!TestContext.Current.IsResp3()) + { + Assert.False(servers[0].IsSubscriberConnected); + } + + var ex = Assert.Throws(() => conn.GetSubscriber().Subscribe(RedisChannel.Literal(Me()), (_, _) => GC.KeepAlive(this))); + Assert.Equal("This operation has been disabled in the command-map and cannot be used: SUBSCRIBE", ex.Message); + } + + [Fact] + public async Task ReadConfig() + { + await using var conn = Create(allowAdmin: true); + + Log("about to get config"); + var server = GetAnyPrimary(conn); + var all = server.ConfigGet(); + Assert.True(all.Length > 0, "any"); + + var pairs = all.ToDictionary(x => (string)x.Key, x => (string)x.Value, StringComparer.InvariantCultureIgnoreCase); + + Assert.Equal(all.Length, pairs.Count); + Assert.True(pairs.ContainsKey("timeout"), "timeout"); + var val = int.Parse(pairs["timeout"]); + + Assert.True(pairs.ContainsKey("port"), "port"); + val = int.Parse(pairs["port"]); + Assert.Equal(TestConfig.Current.PrimaryPort, val); + } + + [Fact] + public async Task GetTime() + { + await using var conn = Create(); + + var server = GetAnyPrimary(conn); + var serverTime = server.Time(); + var localTime = DateTime.UtcNow; + Log("Server: " + serverTime.ToString(CultureInfo.InvariantCulture)); + Log("Local: " + localTime.ToString(CultureInfo.InvariantCulture)); + Assert.Equal(localTime, serverTime, TimeSpan.FromSeconds(10)); + } + + [Fact] + public async Task DebugObject() + { + await using var conn = Create(allowAdmin: true); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringIncrement(key, flags: CommandFlags.FireAndForget); + var debug = (string?)db.DebugObject(key); + Assert.NotNull(debug); + Assert.Contains("encoding:int serializedlength:2", debug); + } + + [Fact] + public async Task GetInfo() + { + await using var conn = Create(allowAdmin: true); + + var server = GetAnyPrimary(conn); + var info1 = server.Info(); + Assert.True(info1.Length > 5); + Log("All sections"); + foreach (var group in info1) + { + Log(group.Key); + } + var first = info1[0]; + Log("Full info for: " + first.Key); + foreach (var setting in first) + { + Log(" {0} ==> {1}", setting.Key, setting.Value); + } + + var info2 = server.Info("cpu"); + Assert.Single(info2); + var cpu = info2.Single(); + Log("Full info for: " + cpu.Key); + foreach (var setting in cpu) + { + Log(" {0} ==> {1}", setting.Key, setting.Value); + } + var cpuCount = cpu.Count(); + Assert.True(cpuCount > 2); + if (cpu.Key != "CPU") + { + // seem to be seeing this in logs; add lots of detail + var sb = new StringBuilder("Expected CPU, got ").AppendLine(cpu.Key); + foreach (var setting in cpu) + { + sb.Append(setting.Key).Append('=').AppendLine(setting.Value); + } + Assert.Fail(sb.ToString()); + } + Assert.Equal("CPU", cpu.Key); + Assert.Contains(cpu, x => x.Key == "used_cpu_sys"); + Assert.Contains(cpu, x => x.Key == "used_cpu_user"); + } + + [Fact] + public async Task GetInfoRaw() + { + await using var conn = Create(allowAdmin: true); + + var server = GetAnyPrimary(conn); + var info = server.InfoRaw(); + Assert.Contains("used_cpu_sys", info); + Assert.Contains("used_cpu_user", info); + } + + [Fact] + public async Task GetClients() + { + var name = Guid.NewGuid().ToString(); + await using var conn = Create(clientName: name, allowAdmin: true, shared: false); + + var server = GetAnyPrimary(conn); + var clients = server.ClientList(); + Assert.True(clients.Length > 0, "no clients"); // ourselves! + Assert.True(clients.Any(x => x.Name == name), "expected: " + name); + + if (server.Features.ClientId) + { + var id = conn.GetConnectionId(server.EndPoint, ConnectionType.Interactive); + Log("client id: " + id); + Assert.NotNull(id); + Assert.True(clients.Any(x => x.Id == id), "expected: " + id); + id = conn.GetConnectionId(server.EndPoint, ConnectionType.Subscription); + Assert.NotNull(id); + Assert.True(clients.Any(x => x.Id == id), "expected: " + id); + + var self = clients.First(x => x.Id == id); + if (server.Version.Major >= 7) + { + Assert.Equal(TestContext.Current.GetProtocol(), self.Protocol); + } + else + { + Assert.Null(self.Protocol); + } + } + } + + [Fact] + public async Task SlowLog() + { + await using var conn = Create(allowAdmin: true); + + var server = GetAnyPrimary(conn); + server.SlowlogGet(); + server.SlowlogReset(); + } + + [Fact] + public void EndpointIteratorIsReliableOverChanges() + { + var eps = new EndPointCollection + { + { IPAddress.Loopback, 7999 }, + { IPAddress.Loopback, 8000 }, + }; + + using var iter = eps.GetEnumerator(); + Assert.True(iter.MoveNext()); + Assert.Equal(7999, ((IPEndPoint)iter.Current).Port); + eps[1] = new IPEndPoint(IPAddress.Loopback, 8001); // boom + Assert.True(iter.MoveNext()); + Assert.Equal(8001, ((IPEndPoint)iter.Current).Port); + Assert.False(iter.MoveNext()); + } + + [Fact] + public async Task ThreadPoolManagerIsDetected() + { + var config = new ConfigurationOptions + { + EndPoints = { { IPAddress.Loopback, 6379 } }, + SocketManager = SocketManager.ThreadPool, + }; + + await using var conn = ConnectionMultiplexer.Connect(config); + + Assert.Same(PipeScheduler.ThreadPool, conn.SocketManager?.Scheduler); + } + + [Fact] + public async Task DefaultThreadPoolManagerIsDetected() + { + var config = new ConfigurationOptions + { + EndPoints = { { IPAddress.Loopback, 6379 } }, + }; + + await using var conn = ConnectionMultiplexer.Connect(config); + + Assert.Same(SocketManager.Shared.Scheduler, conn.SocketManager?.Scheduler); + } + + [Theory] + [InlineData("myDNS:myPort,password=myPassword,connectRetry=3,connectTimeout=15000,syncTimeout=15000,defaultDatabase=0,abortConnect=false,ssl=true,sslProtocols=Tls12", SslProtocols.Tls12)] + [InlineData("myDNS:myPort,password=myPassword,abortConnect=false,ssl=true,sslProtocols=Tls12", SslProtocols.Tls12)] +#pragma warning disable CS0618 // Type or member is obsolete + [InlineData("myDNS:myPort,password=myPassword,abortConnect=false,ssl=true,sslProtocols=Ssl3", SslProtocols.Ssl3)] +#pragma warning restore CS0618 + [InlineData("myDNS:myPort,password=myPassword,abortConnect=false,ssl=true,sslProtocols=Tls12 ", SslProtocols.Tls12)] + public void ParseTlsWithoutTrailingComma(string configString, SslProtocols expected) + { + var config = ConfigurationOptions.Parse(configString); + Assert.Equal(expected, config.SslProtocols); + } + + [Theory] + [InlineData("foo,sslProtocols=NotAThing", "Keyword 'sslProtocols' requires an SslProtocol value (multiple values separated by '|'); the value 'NotAThing' is not recognised.", "sslProtocols")] + [InlineData("foo,SyncTimeout=ten", "Keyword 'SyncTimeout' requires an integer value; the value 'ten' is not recognised.", "SyncTimeout")] + [InlineData("foo,syncTimeout=-42", "Keyword 'syncTimeout' has a minimum value of '1'; the value '-42' is not permitted.", "syncTimeout")] + [InlineData("foo,AllowAdmin=maybe", "Keyword 'AllowAdmin' requires a boolean value; the value 'maybe' is not recognised.", "AllowAdmin")] + [InlineData("foo,Version=current", "Keyword 'Version' requires a version value; the value 'current' is not recognised.", "Version")] + [InlineData("foo,proxy=epoxy", "Keyword 'proxy' requires a proxy value; the value 'epoxy' is not recognised.", "proxy")] + public void ConfigStringErrorsGiveMeaningfulMessages(string configString, string expected, string paramName) + { + var ex = Assert.Throws(() => ConfigurationOptions.Parse(configString)); + Assert.StartsWith(expected, ex.Message); // param name gets concatenated sometimes + Assert.Equal(paramName, ex.ParamName); // param name gets concatenated sometimes + } + + [Fact] + public void ConfigStringInvalidOptionErrorGiveMeaningfulMessages() + { + var ex = Assert.Throws(() => ConfigurationOptions.Parse("foo,flibble=value")); + Assert.StartsWith("Keyword 'flibble' is not supported.", ex.Message); // param name gets concatenated sometimes + Assert.Equal("flibble", ex.ParamName); + } + + [Fact] + public void NullApply() + { + var options = ConfigurationOptions.Parse("127.0.0.1,name=FooApply"); + Assert.Equal("FooApply", options.ClientName); + + // Doesn't go boom + var result = options.Apply(null!); + Assert.Equal("FooApply", options.ClientName); + Assert.Equal(result, options); + } + + [Fact] + public void Apply() + { + var options = ConfigurationOptions.Parse("127.0.0.1,name=FooApply"); + Assert.Equal("FooApply", options.ClientName); + + var randomName = Guid.NewGuid().ToString(); + var result = options.Apply(options => options.ClientName = randomName); + + Assert.Equal(randomName, options.ClientName); + Assert.Equal(randomName, result.ClientName); + Assert.Equal(result, options); + } + + [Fact] + public async Task BeforeSocketConnect() + { + var options = ConfigurationOptions.Parse(TestConfig.Current.PrimaryServerAndPort); + int count = 0; + options.BeforeSocketConnect = (endpoint, connType, socket) => + { + Interlocked.Increment(ref count); + Log($"Endpoint: {endpoint}, ConnType: {connType}, Socket: {socket}"); + socket.DontFragment = true; + socket.Ttl = (short)(connType == ConnectionType.Interactive ? 12 : 123); + }; + await using var conn = ConnectionMultiplexer.Connect(options); + Assert.True(conn.IsConnected); + Assert.Equal(2, count); + + var endpoint = conn.GetServerSnapshot()[0]; + var interactivePhysical = endpoint.GetBridge(ConnectionType.Interactive)?.TryConnect(null); + var subscriptionPhysical = endpoint.GetBridge(ConnectionType.Subscription)?.TryConnect(null); + Assert.NotNull(interactivePhysical); + Assert.NotNull(subscriptionPhysical); + + var interactiveSocket = interactivePhysical.VolatileSocket; + var subscriptionSocket = subscriptionPhysical.VolatileSocket; + Assert.NotNull(interactiveSocket); + Assert.NotNull(subscriptionSocket); + + Assert.Equal(12, interactiveSocket.Ttl); + Assert.Equal(123, subscriptionSocket.Ttl); + Assert.True(interactiveSocket.DontFragment); + Assert.True(subscriptionSocket.DontFragment); + } + + [Fact] + public async Task MutableOptions() + { + var options = ConfigurationOptions.Parse(TestConfig.Current.PrimaryServerAndPort + ",name=Details"); + options.LoggerFactory = NullLoggerFactory.Instance; + var originalConfigChannel = options.ConfigurationChannel = "originalConfig"; + var originalUser = options.User = "originalUser"; + var originalPassword = options.Password = "originalPassword"; + Assert.Equal("Details", options.ClientName); + await using var conn = await ConnectionMultiplexer.ConnectAsync(options); + + // Same instance + Assert.Same(options, conn.RawConfig); + // Copies + Assert.NotSame(options.EndPoints, conn.EndPoints); + + // Same until forked - it's not cloned + Assert.Same(options.CommandMap, conn.CommandMap); + options.CommandMap = CommandMap.Envoyproxy; + Assert.NotSame(options.CommandMap, conn.CommandMap); + +#pragma warning disable CS0618 // Type or member is obsolete + // Defaults true + Assert.True(options.IncludeDetailInExceptions); + Assert.True(conn.IncludeDetailInExceptions); + options.IncludeDetailInExceptions = false; + Assert.False(options.IncludeDetailInExceptions); + Assert.False(conn.IncludeDetailInExceptions); + + // Defaults false + Assert.False(options.IncludePerformanceCountersInExceptions); + Assert.False(conn.IncludePerformanceCountersInExceptions); + options.IncludePerformanceCountersInExceptions = true; + Assert.True(options.IncludePerformanceCountersInExceptions); + Assert.True(conn.IncludePerformanceCountersInExceptions); +#pragma warning restore CS0618 + + var newName = Guid.NewGuid().ToString(); + options.ClientName = newName; + Assert.Equal(newName, conn.ClientName); + + // TODO: This forks due to memoization of the byte[] for efficiency + // If we could cheaply detect change it'd be good to let this change + const string newConfigChannel = "newConfig"; + options.ConfigurationChannel = newConfigChannel; + Assert.Equal(newConfigChannel, options.ConfigurationChannel); + Assert.NotNull(conn.ConfigurationChangedChannel); + Assert.Equal(Encoding.UTF8.GetString(conn.ConfigurationChangedChannel), originalConfigChannel); + + Assert.Equal(originalUser, conn.RawConfig.User); + Assert.Equal(originalPassword, conn.RawConfig.Password); + var newPass = options.Password = "newPassword"; + Assert.Equal(newPass, conn.RawConfig.Password); + Assert.Equal(options.LoggerFactory, conn.RawConfig.LoggerFactory); + } + + [Theory] + [InlineData("http://somewhere:22", "http:somewhere:22")] + [InlineData("http:somewhere:22", "http:somewhere:22")] + public void HttpTunnelCanRoundtrip(string input, string expected) + { + var config = ConfigurationOptions.Parse($"127.0.0.1:6380,tunnel={input}"); + var ip = Assert.IsType(Assert.Single(config.EndPoints)); + Assert.Equal(6380, ip.Port); + Assert.Equal("127.0.0.1", ip.Address.ToString()); + + Assert.NotNull(config.Tunnel); + Assert.Equal(expected, config.Tunnel.ToString()); + + var cs = config.ToString(); + Assert.Equal($"127.0.0.1:6380,tunnel={expected}", cs); + } + + private sealed class CustomTunnel : Tunnel { } + + [Fact] + public void CustomTunnelCanRoundtripMinusTunnel() + { + // we don't expect to be able to parse custom tunnels, but we should still be able to round-trip + // the rest of the config, which means ignoring them *in both directions* (unless first party) + var options = ConfigurationOptions.Parse("127.0.0.1,Ssl=true"); + options.Tunnel = new CustomTunnel(); + var cs = options.ToString(); + Assert.Equal("127.0.0.1,ssl=True", cs); + options = ConfigurationOptions.Parse(cs); + Assert.Null(options.Tunnel); + } + + [Theory] + [InlineData("server:6379", true)] + [InlineData("server:6379,setlib=True", true)] + [InlineData("server:6379,setlib=False", false)] + public void DefaultConfigOptionsForSetLib(string configurationString, bool setlib) + { + var options = ConfigurationOptions.Parse(configurationString); + Assert.Equal(setlib, options.SetClientLibrary); + Assert.Equal(configurationString, options.ToString()); + options = options.Clone(); + Assert.Equal(setlib, options.SetClientLibrary); + Assert.Equal(configurationString, options.ToString()); + } + + [Theory] + [InlineData(null, false, "dummy")] + [InlineData(false, false, "dummy,highIntegrity=False")] + [InlineData(true, true, "dummy,highIntegrity=True")] + public void CheckHighIntegrity(bool? assigned, bool expected, string cs) + { + var options = ConfigurationOptions.Parse("dummy"); + if (assigned.HasValue) options.HighIntegrity = assigned.Value; + + Assert.Equal(expected, options.HighIntegrity); + Assert.Equal(cs, options.ToString()); + + var clone = options.Clone(); + Assert.Equal(expected, clone.HighIntegrity); + Assert.Equal(cs, clone.ToString()); + + var parsed = ConfigurationOptions.Parse(cs); + Assert.Equal(expected, parsed.HighIntegrity); + } +} diff --git a/tests/StackExchange.Redis.Tests/ConnectByIPTests.cs b/tests/StackExchange.Redis.Tests/ConnectByIPTests.cs new file mode 100644 index 000000000..3b6fc4d49 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ConnectByIPTests.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ConnectByIPTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public void ParseEndpoints() + { + var eps = new EndPointCollection + { + { "127.0.0.1", 1000 }, + { "::1", 1001 }, + { "localhost", 1002 }, + }; + + Assert.Equal(AddressFamily.InterNetwork, eps[0].AddressFamily); + Assert.Equal(AddressFamily.InterNetworkV6, eps[1].AddressFamily); + Assert.Equal(AddressFamily.Unspecified, eps[2].AddressFamily); + + Assert.Equal("127.0.0.1:1000", eps[0].ToString()); + Assert.Equal("[::1]:1001", eps[1].ToString()); + Assert.Equal("Unspecified/localhost:1002", eps[2].ToString()); + } + + [Fact] + public async Task IPv4Connection() + { + var config = new ConfigurationOptions + { + EndPoints = { { TestConfig.Current.IPv4Server, TestConfig.Current.IPv4Port } }, + }; + await using var conn = ConnectionMultiplexer.Connect(config); + + var server = conn.GetServer(config.EndPoints[0]); + Assert.Equal(AddressFamily.InterNetwork, server.EndPoint.AddressFamily); + await server.PingAsync(); + } + + [Fact] + public async Task IPv6Connection() + { + var config = new ConfigurationOptions + { + EndPoints = { { TestConfig.Current.IPv6Server, TestConfig.Current.IPv6Port } }, + }; + await using var conn = ConnectionMultiplexer.Connect(config); + + var server = conn.GetServer(config.EndPoints[0]); + Assert.Equal(AddressFamily.InterNetworkV6, server.EndPoint.AddressFamily); + await server.PingAsync(); + } + + [Theory] + [MemberData(nameof(ConnectByVariousEndpointsData))] + public async Task ConnectByVariousEndpoints(EndPoint ep, AddressFamily expectedFamily) + { + Assert.Equal(expectedFamily, ep.AddressFamily); + var config = new ConfigurationOptions + { + EndPoints = { ep }, + }; + if (ep.AddressFamily != AddressFamily.InterNetworkV6) // I don't have IPv6 servers + { + await using (var conn = ConnectionMultiplexer.Connect(config)) + { + var actual = conn.GetEndPoints().Single(); + var server = conn.GetServer(actual); + await server.PingAsync(); + } + } + } + + public static IEnumerable ConnectByVariousEndpointsData() + { + yield return new object[] { new IPEndPoint(IPAddress.Loopback, 6379), AddressFamily.InterNetwork }; + + yield return new object[] { new IPEndPoint(IPAddress.IPv6Loopback, 6379), AddressFamily.InterNetworkV6 }; + + yield return new object[] { new DnsEndPoint("localhost", 6379), AddressFamily.Unspecified }; + + yield return new object[] { new DnsEndPoint("localhost", 6379, AddressFamily.InterNetwork), AddressFamily.InterNetwork }; + + yield return new object[] { new DnsEndPoint("localhost", 6379, AddressFamily.InterNetworkV6), AddressFamily.InterNetworkV6 }; + + yield return new object[] { ConfigurationOptions.Parse("localhost:6379").EndPoints.Single(), AddressFamily.Unspecified }; + + yield return new object[] { ConfigurationOptions.Parse("localhost").EndPoints.Single(), AddressFamily.Unspecified }; + + yield return new object[] { ConfigurationOptions.Parse("127.0.0.1:6379").EndPoints.Single(), AddressFamily.InterNetwork }; + + yield return new object[] { ConfigurationOptions.Parse("127.0.0.1").EndPoints.Single(), AddressFamily.InterNetwork }; + + yield return new object[] { ConfigurationOptions.Parse("[::1]").EndPoints.Single(), AddressFamily.InterNetworkV6 }; + + yield return new object[] { ConfigurationOptions.Parse("[::1]:6379").EndPoints.Single(), AddressFamily.InterNetworkV6 }; + } +} diff --git a/tests/StackExchange.Redis.Tests/ConnectCustomConfigTests.cs b/tests/StackExchange.Redis.Tests/ConnectCustomConfigTests.cs new file mode 100644 index 000000000..d0e67f35f --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ConnectCustomConfigTests.cs @@ -0,0 +1,125 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ConnectCustomConfigTests(ITestOutputHelper output) : TestBase(output) +{ + // So we're triggering tiebreakers here + protected override string GetConfiguration() => TestConfig.Current.PrimaryServerAndPort + "," + TestConfig.Current.ReplicaServerAndPort; + + [Theory] + [InlineData("config")] + [InlineData("info")] + [InlineData("get")] + [InlineData("config,get")] + [InlineData("info,get")] + [InlineData("config,info,get")] + public async Task DisabledCommandsStillConnect(string disabledCommands) + { + await using var conn = Create(allowAdmin: true, disabledCommands: disabledCommands.Split(','), log: Writer); + + var db = conn.GetDatabase(); + await db.PingAsync(); + Assert.True(db.IsConnected(default(RedisKey))); + } + + [Theory] + [InlineData("config")] + [InlineData("info")] + [InlineData("get")] + [InlineData("cluster")] + [InlineData("config,get")] + [InlineData("info,get")] + [InlineData("config,info,get")] + [InlineData("config,info,get,cluster")] + public async Task DisabledCommandsStillConnectCluster(string disabledCommands) + { + await using var conn = Create(allowAdmin: true, configuration: TestConfig.Current.ClusterServersAndPorts, disabledCommands: disabledCommands.Split(','), log: Writer); + + var db = conn.GetDatabase(); + await db.PingAsync(); + Assert.True(db.IsConnected(default(RedisKey))); + } + + [Fact] + public async Task TieBreakerIntact() + { + await using var conn = Create(allowAdmin: true, log: Writer); + + var tiebreaker = conn.GetDatabase().StringGet(conn.RawConfig.TieBreaker); + Log($"Tiebreaker: {tiebreaker}"); + + foreach (var server in conn.GetServerSnapshot()) + { + Assert.Equal(tiebreaker, server.TieBreakerResult); + } + } + + [Fact] + public async Task TieBreakerSkips() + { + await using var conn = Create(allowAdmin: true, disabledCommands: ["get"], log: Writer); + Assert.Throws(() => conn.GetDatabase().StringGet(conn.RawConfig.TieBreaker)); + + foreach (var server in conn.GetServerSnapshot()) + { + Assert.True(server.IsConnected); + Assert.Null(server.TieBreakerResult); + } + } + + [Fact] + public async Task TiebreakerIncorrectType() + { + var tiebreakerKey = Me(); + await using var fubarConn = Create(allowAdmin: true, log: Writer); + // Store something nonsensical in the tiebreaker key: + fubarConn.GetDatabase().HashSet(tiebreakerKey, "foo", "bar"); + + // Ensure the next connection getting an invalid type still connects + await using var conn = Create(allowAdmin: true, tieBreaker: tiebreakerKey, log: Writer); + + var db = conn.GetDatabase(); + await db.PingAsync(); + Assert.True(db.IsConnected(default(RedisKey))); + + var ex = Assert.Throws(() => db.StringGet(tiebreakerKey)); + Assert.Contains("WRONGTYPE", ex.Message); + } + + [Theory] + [InlineData(true, 2, 15)] + [InlineData(false, 0, 0)] + public async Task HeartbeatConsistencyCheckPingsAsync(bool enableConsistencyChecks, int minExpected, int maxExpected) + { + var options = new ConfigurationOptions() + { + HeartbeatConsistencyChecks = enableConsistencyChecks, + HeartbeatInterval = TimeSpan.FromMilliseconds(100), + }; + options.EndPoints.Add(TestConfig.Current.PrimaryServerAndPort); + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer); + + var db = conn.GetDatabase(); + await db.PingAsync(); + Assert.True(db.IsConnected(default)); + + var preCount = conn.OperationCount; + Log("OperationCount (pre-delay): " + preCount); + + // Allow several heartbeats to happen, but don't need to be strict here + // e.g. allow thread pool starvation flex with the test suite's load (just check for a few) + await Task.Delay(TimeSpan.FromSeconds(1)); + + var postCount = conn.OperationCount; + Log("OperationCount (post-delay): " + postCount); + + var opCount = postCount - preCount; + Log("OperationCount (diff): " + opCount); + + Assert.True(minExpected <= opCount && opCount >= minExpected, $"Expected opcount ({opCount}) between {minExpected}-{maxExpected}"); + } +} diff --git a/tests/StackExchange.Redis.Tests/ConnectFailTimeoutTests.cs b/tests/StackExchange.Redis.Tests/ConnectFailTimeoutTests.cs new file mode 100644 index 000000000..6a7e253d7 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ConnectFailTimeoutTests.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ConnectFailTimeoutTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task NoticesConnectFail() + { + SetExpectedAmbientFailureCount(-1); + await using var conn = Create(allowAdmin: true, shared: false, backlogPolicy: BacklogPolicy.FailFast); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + + await RunBlockingSynchronousWithExtraThreadAsync(InnerScenario).ForAwait(); + + void InnerScenario() + { + conn.ConnectionFailed += (s, a) => + Log("Disconnected: " + EndPointCollection.ToString(a.EndPoint)); + conn.ConnectionRestored += (s, a) => + Log("Reconnected: " + EndPointCollection.ToString(a.EndPoint)); + + // No need to delay, we're going to try a disconnected connection immediately so it'll fail... + conn.IgnoreConnect = true; + Log("simulating failure"); + server.SimulateConnectionFailure(SimulatedFailureType.All); + Log("simulated failure"); + conn.IgnoreConnect = false; + Log("pinging - expect failure"); + Assert.Throws(() => server.Ping()); + Log("pinged"); + } + + // Heartbeat should reconnect by now + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => server.IsConnected); + + Log("pinging - expect success"); + var time = await server.PingAsync(); + Log("pinged"); + Log(time.ToString()); + } +} diff --git a/tests/StackExchange.Redis.Tests/ConnectToUnexistingHostTests.cs b/tests/StackExchange.Redis.Tests/ConnectToUnexistingHostTests.cs new file mode 100644 index 000000000..cc015c711 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ConnectToUnexistingHostTests.cs @@ -0,0 +1,91 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ConnectToUnexistingHostTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task FailsWithinTimeout() + { + const int timeout = 1000; + var sw = Stopwatch.StartNew(); + try + { + var config = new ConfigurationOptions + { + EndPoints = { { "invalid", 1234 } }, + ConnectTimeout = timeout, + }; + + await using (ConnectionMultiplexer.Connect(config, Writer)) + { + await Task.Delay(10000).ForAwait(); + } + + Assert.Fail("Connect should fail with RedisConnectionException exception"); + } + catch (RedisConnectionException) + { + var elapsed = sw.ElapsedMilliseconds; + Log("Elapsed time: " + elapsed); + Log("Timeout: " + timeout); + Assert.True(elapsed < 9000, "Connect should fail within ConnectTimeout, ElapsedMs: " + elapsed); + } + } + + [Fact] + public async Task CanNotOpenNonsenseConnection_IP() + { + await RunBlockingSynchronousWithExtraThreadAsync(InnerScenario).ForAwait(); + void InnerScenario() + { + var ex = Assert.Throws(() => + { + using (ConnectionMultiplexer.Connect(TestConfig.Current.PrimaryServer + ":6500,connectTimeout=1000,connectRetry=0", Writer)) { } + }); + Log(ex.ToString()); + } + } + + [Fact] + public async Task CanNotOpenNonsenseConnection_DNS() + { + var ex = await Assert.ThrowsAsync(async () => + { + using (await ConnectionMultiplexer.ConnectAsync($"doesnot.exist.ds.{Guid.NewGuid():N}.com:6500,connectTimeout=1000,connectRetry=0", Writer).ForAwait()) { } + }).ForAwait(); + Log(ex.ToString()); + } + + [Fact] + public async Task CreateDisconnectedNonsenseConnection_IP() + { + await RunBlockingSynchronousWithExtraThreadAsync(InnerScenario).ForAwait(); + void InnerScenario() + { + using (var conn = ConnectionMultiplexer.Connect(TestConfig.Current.PrimaryServer + ":6500,abortConnect=false,connectTimeout=1000,connectRetry=0", Writer)) + { + Assert.False(conn.GetServer(conn.GetEndPoints().Single()).IsConnected); + Assert.False(conn.GetDatabase().IsConnected(default(RedisKey))); + } + } + } + + [Fact] + public async Task CreateDisconnectedNonsenseConnection_DNS() + { + await RunBlockingSynchronousWithExtraThreadAsync(InnerScenario).ForAwait(); + void InnerScenario() + { + using (var conn = ConnectionMultiplexer.Connect($"doesnot.exist.ds.{Guid.NewGuid():N}.com:6500,abortConnect=false,connectTimeout=1000,connectRetry=0", Writer)) + { + Assert.False(conn.GetServer(conn.GetEndPoints().Single()).IsConnected); + Assert.False(conn.GetDatabase().IsConnected(default(RedisKey))); + } + } + } +} diff --git a/tests/StackExchange.Redis.Tests/ConnectingFailDetectionTests.cs b/tests/StackExchange.Redis.Tests/ConnectingFailDetectionTests.cs new file mode 100644 index 000000000..a905c613a --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ConnectingFailDetectionTests.cs @@ -0,0 +1,169 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ConnectingFailDetectionTests(ITestOutputHelper output) : TestBase(output) +{ + protected override string GetConfiguration() => TestConfig.Current.PrimaryServerAndPort + "," + TestConfig.Current.ReplicaServerAndPort; + + [Fact] + public async Task FastNoticesFailOnConnectingSyncCompletion() + { + try + { + await using var conn = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true, shared: false); + conn.RawConfig.ReconnectRetryPolicy = new LinearRetry(200); + + var db = conn.GetDatabase(); + await db.PingAsync(); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + var server2 = conn.GetServer(conn.GetEndPoints()[1]); + + conn.AllowConnect = false; + + // muxer.IsConnected is true of *any* are connected, simulate failure for all cases. + server.SimulateConnectionFailure(SimulatedFailureType.All); + Assert.False(server.IsConnected); + Assert.True(server2.IsConnected); + Assert.True(conn.IsConnected); + + server2.SimulateConnectionFailure(SimulatedFailureType.All); + Assert.False(server.IsConnected); + Assert.False(server2.IsConnected); + Assert.False(conn.IsConnected); + + // should reconnect within 1 keepalive interval + conn.AllowConnect = true; + Log("Waiting for reconnect"); + await UntilConditionAsync(TimeSpan.FromSeconds(2), () => conn.IsConnected).ForAwait(); + + Assert.True(conn.IsConnected); + } + finally + { + ClearAmbientFailures(); + } + } + + [Fact] + public async Task FastNoticesFailOnConnectingAsyncCompletion() + { + try + { + await using var conn = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true, shared: false); + conn.RawConfig.ReconnectRetryPolicy = new LinearRetry(200); + + var db = conn.GetDatabase(); + await db.PingAsync(); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + var server2 = conn.GetServer(conn.GetEndPoints()[1]); + + conn.AllowConnect = false; + + // muxer.IsConnected is true of *any* are connected, simulate failure for all cases. + server.SimulateConnectionFailure(SimulatedFailureType.All); + Assert.False(server.IsConnected); + Assert.True(server2.IsConnected); + Assert.True(conn.IsConnected); + + server2.SimulateConnectionFailure(SimulatedFailureType.All); + Assert.False(server.IsConnected); + Assert.False(server2.IsConnected); + Assert.False(conn.IsConnected); + + // should reconnect within 1 keepalive interval + conn.AllowConnect = true; + Log("Waiting for reconnect"); + await UntilConditionAsync(TimeSpan.FromSeconds(2), () => conn.IsConnected).ForAwait(); + + Assert.True(conn.IsConnected); + } + finally + { + ClearAmbientFailures(); + } + } + + [Fact] + public async Task Issue922_ReconnectRaised() + { + var config = ConfigurationOptions.Parse(TestConfig.Current.PrimaryServerAndPort); + config.AbortOnConnectFail = true; + config.KeepAlive = 1; + config.SyncTimeout = 1000; + config.AsyncTimeout = 1000; + config.ReconnectRetryPolicy = new ExponentialRetry(5000); + config.AllowAdmin = true; + config.BacklogPolicy = BacklogPolicy.FailFast; + + int failCount = 0, restoreCount = 0; + + await using var conn = await ConnectionMultiplexer.ConnectAsync(config); + + conn.ConnectionFailed += (s, e) => + { + Interlocked.Increment(ref failCount); + Log($"Connection Failed ({e.ConnectionType}, {e.FailureType}): {e.Exception}"); + }; + conn.ConnectionRestored += (s, e) => + { + Interlocked.Increment(ref restoreCount); + Log($"Connection Restored ({e.ConnectionType}, {e.FailureType})"); + }; + + conn.GetDatabase(); + Assert.Equal(0, Volatile.Read(ref failCount)); + Assert.Equal(0, Volatile.Read(ref restoreCount)); + + var server = conn.GetServer(TestConfig.Current.PrimaryServerAndPort); + server.SimulateConnectionFailure(SimulatedFailureType.All); + + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => Volatile.Read(ref failCount) >= 2 && Volatile.Read(ref restoreCount) >= 2); + + // interactive+subscriber = 2 + var failCountSnapshot = Volatile.Read(ref failCount); + Assert.True(failCountSnapshot >= 2, $"failCount {failCountSnapshot} >= 2"); + + var restoreCountSnapshot = Volatile.Read(ref restoreCount); + Assert.True(restoreCountSnapshot >= 2, $"restoreCount ({restoreCountSnapshot}) >= 2"); + } + + [Fact] + public async Task ConnectsWhenBeginConnectCompletesSynchronously() + { + try + { + await using var conn = Create(keepAlive: 1, connectTimeout: 3000); + + var db = conn.GetDatabase(); + await db.PingAsync(); + + Assert.True(conn.IsConnected); + } + finally + { + ClearAmbientFailures(); + } + } + + [Fact] + public async Task ConnectIncludesSubscriber() + { + await using var conn = Create(keepAlive: 1, connectTimeout: 3000, shared: false); + + var db = conn.GetDatabase(); + await db.PingAsync(); + Assert.True(conn.IsConnected); + + foreach (var server in conn.GetServerSnapshot()) + { + Assert.Equal(PhysicalBridge.State.ConnectedEstablished, server.InteractiveConnectionState); + Assert.Equal(PhysicalBridge.State.ConnectedEstablished, server.SubscriptionConnectionState); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/ConnectionFailedErrorsTests.cs b/tests/StackExchange.Redis.Tests/ConnectionFailedErrorsTests.cs new file mode 100644 index 000000000..ce1a31980 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ConnectionFailedErrorsTests.cs @@ -0,0 +1,205 @@ +using System; +using System.Linq; +using System.Security.Authentication; +using System.Threading.Tasks; +using StackExchange.Redis.Configuration; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ConnectionFailedErrorsTests(ITestOutputHelper output) : TestBase(output) +{ + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SSLCertificateValidationError(bool isCertValidationSucceeded) + { + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer); + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCachePassword), TestConfig.Current.AzureCachePassword); + + var options = new ConfigurationOptions(); + options.EndPoints.Add(TestConfig.Current.AzureCacheServer); + options.Ssl = true; + options.Password = TestConfig.Current.AzureCachePassword; + options.CertificateValidation += (sender, cert, chain, errors) => isCertValidationSucceeded; + options.AbortOnConnectFail = false; + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options); + + await RunBlockingSynchronousWithExtraThreadAsync(InnerScenario).ForAwait(); + + void InnerScenario() + { + conn.ConnectionFailed += (sender, e) => + Assert.Equal(ConnectionFailureType.AuthenticationFailure, e.FailureType); + if (!isCertValidationSucceeded) + { + // Validate that in this case it throws an certificatevalidation exception + var outer = Assert.Throws(() => conn.GetDatabase().Ping()); + Assert.Equal(ConnectionFailureType.UnableToResolvePhysicalConnection, outer.FailureType); + + Assert.NotNull(outer.InnerException); + var inner = Assert.IsType(outer.InnerException); + Assert.Equal(ConnectionFailureType.AuthenticationFailure, inner.FailureType); + + Assert.NotNull(inner.InnerException); + var innerMost = Assert.IsType(inner.InnerException); + Assert.Equal("The remote certificate is invalid according to the validation procedure.", innerMost.Message); + } + else + { + conn.GetDatabase().Ping(); + } + } + + // wait for a second for connectionfailed event to fire + await Task.Delay(1000).ForAwait(); + } + + [Fact] + public async Task AuthenticationFailureError() + { + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer); + + var options = new ConfigurationOptions(); + options.EndPoints.Add(TestConfig.Current.AzureCacheServer); + options.Ssl = true; + options.Password = ""; + options.AbortOnConnectFail = false; + options.CertificateValidation += SSLTests.ShowCertFailures(Writer); + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options); + + await RunBlockingSynchronousWithExtraThreadAsync(InnerScenario).ForAwait(); + void InnerScenario() + { + conn.ConnectionFailed += (sender, e) => + { + if (e.FailureType == ConnectionFailureType.SocketFailure) Assert.Skip("socket fail"); // this is OK too + Assert.Equal(ConnectionFailureType.AuthenticationFailure, e.FailureType); + }; + var ex = Assert.Throws(() => conn.GetDatabase().Ping()); + + Assert.NotNull(ex.InnerException); + var rde = Assert.IsType(ex.InnerException); + Assert.Equal(CommandStatus.WaitingToBeSent, ex.CommandStatus); + Assert.Equal(ConnectionFailureType.AuthenticationFailure, rde.FailureType); + Assert.NotNull(rde.InnerException); + Assert.Equal("Error: NOAUTH Authentication required. Verify if the Redis password provided is correct.", rde.InnerException.Message); + } + + // Wait for a second for connectionfailed event to fire + await Task.Delay(1000).ForAwait(); + } + + [Fact] + public async Task SocketFailureError() + { + await RunBlockingSynchronousWithExtraThreadAsync(InnerScenario).ForAwait(); + void InnerScenario() + { + var options = new ConfigurationOptions(); + options.EndPoints.Add($"{Guid.NewGuid():N}.redis.cache.windows.net"); + options.Ssl = true; + options.Password = ""; + options.AbortOnConnectFail = false; + options.ConnectTimeout = 1000; + options.BacklogPolicy = BacklogPolicy.FailFast; + var outer = Assert.Throws(() => + { + using var conn = ConnectionMultiplexer.Connect(options); + + conn.GetDatabase().Ping(); + }); + Assert.Equal(ConnectionFailureType.UnableToResolvePhysicalConnection, outer.FailureType); + + Assert.NotNull(outer.InnerException); + if (outer.InnerException is RedisConnectionException rce) + { + Assert.Equal(ConnectionFailureType.UnableToConnect, rce.FailureType); + } + else if (outer.InnerException is AggregateException ae + && ae.InnerExceptions.Any(e => e is RedisConnectionException rce2 + && rce2.FailureType == ConnectionFailureType.UnableToConnect)) + { + // fine; at least *one* of them is the one we were hoping to see + } + else + { + Log(outer.InnerException.ToString()); + if (outer.InnerException is AggregateException inner) + { + foreach (var ex in inner.InnerExceptions) + { + Log(ex.ToString()); + } + } + Assert.False(true); // force fail + } + } + } + + [Fact] + public async Task AbortOnConnectFailFalseConnectTimeoutError() + { + await RunBlockingSynchronousWithExtraThreadAsync(InnerScenario).ForAwait(); + void InnerScenario() + { + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer); + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCachePassword), TestConfig.Current.AzureCachePassword); + + var options = new ConfigurationOptions(); + options.EndPoints.Add(TestConfig.Current.AzureCacheServer); + options.Ssl = true; + options.ConnectTimeout = 0; + options.Password = TestConfig.Current.AzureCachePassword; + + using var conn = ConnectionMultiplexer.Connect(options); + + var ex = Assert.Throws(() => conn.GetDatabase().Ping()); + Assert.Contains("ConnectTimeout", ex.Message); + } + } + + [Fact] + public void TryGetAzureRoleInstanceIdNoThrow() + { + Assert.Null(DefaultOptionsProvider.TryGetAzureRoleInstanceIdNoThrow()); + } + +#if DEBUG + [Fact] + public async Task CheckFailureRecovered() + { + try + { + await using var conn = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true, log: Writer, shared: false); + + await RunBlockingSynchronousWithExtraThreadAsync(InnerScenario).ForAwait(); + void InnerScenario() + { + conn.GetDatabase(); + var server = conn.GetServer(conn.GetEndPoints()[0]); + + conn.AllowConnect = false; + + server.SimulateConnectionFailure(SimulatedFailureType.All); + + var lastFailure = ((RedisConnectionException?)conn.GetServerSnapshot()[0].LastException)!.FailureType; + // Depending on heartbeat races, the last exception will be a socket failure or an internal (follow-up) failure + Assert.Contains(lastFailure, new[] { ConnectionFailureType.SocketFailure, ConnectionFailureType.InternalFailure }); + + // should reconnect within 1 keepalive interval + conn.AllowConnect = true; + } + await Task.Delay(2000).ForAwait(); + + Assert.Null(conn.GetServerSnapshot()[0].LastException); + } + finally + { + ClearAmbientFailures(); + } + } +#endif +} diff --git a/tests/StackExchange.Redis.Tests/ConnectionReconnectRetryPolicyTests.cs b/tests/StackExchange.Redis.Tests/ConnectionReconnectRetryPolicyTests.cs new file mode 100644 index 000000000..db5f541b3 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ConnectionReconnectRetryPolicyTests.cs @@ -0,0 +1,49 @@ +using System; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class TransientErrorTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public void TestExponentialRetry() + { + IReconnectRetryPolicy exponentialRetry = new ExponentialRetry(5000); + Assert.False(exponentialRetry.ShouldRetry(0, 0)); + Assert.True(exponentialRetry.ShouldRetry(1, 5600)); + Assert.True(exponentialRetry.ShouldRetry(2, 6050)); + Assert.False(exponentialRetry.ShouldRetry(2, 4050)); + } + + [Fact] + public void TestExponentialMaxRetry() + { + IReconnectRetryPolicy exponentialRetry = new ExponentialRetry(5000); + Assert.True(exponentialRetry.ShouldRetry(long.MaxValue, (int)TimeSpan.FromSeconds(30).TotalMilliseconds)); + } + + [Fact] + public void TestExponentialRetryArgs() + { + _ = new ExponentialRetry(5000); + _ = new ExponentialRetry(5000, 10000); + + var ex = Assert.Throws(() => new ExponentialRetry(-1)); + Assert.Equal("deltaBackOffMilliseconds", ex.ParamName); + + ex = Assert.Throws(() => new ExponentialRetry(5000, -1)); + Assert.Equal("maxDeltaBackOffMilliseconds", ex.ParamName); + + ex = Assert.Throws(() => new ExponentialRetry(10000, 5000)); + Assert.Equal("maxDeltaBackOffMilliseconds", ex.ParamName); + } + + [Fact] + public void TestLinearRetry() + { + IReconnectRetryPolicy linearRetry = new LinearRetry(5000); + Assert.False(linearRetry.ShouldRetry(0, 0)); + Assert.False(linearRetry.ShouldRetry(2, 4999)); + Assert.True(linearRetry.ShouldRetry(1, 5000)); + } +} diff --git a/tests/StackExchange.Redis.Tests/ConnectionShutdownTests.cs b/tests/StackExchange.Redis.Tests/ConnectionShutdownTests.cs new file mode 100644 index 000000000..279d8bd23 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ConnectionShutdownTests.cs @@ -0,0 +1,50 @@ +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ConnectionShutdownTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact(Skip = "Unfriendly")] + public async Task ShutdownRaisesConnectionFailedAndRestore() + { + await using var conn = Create(allowAdmin: true, shared: false); + + int failed = 0, restored = 0; + Stopwatch watch = Stopwatch.StartNew(); + conn.ConnectionFailed += (sender, args) => + { + Log(watch.Elapsed + ": failed: " + EndPointCollection.ToString(args.EndPoint) + "/" + args.ConnectionType + ": " + args); + Interlocked.Increment(ref failed); + }; + conn.ConnectionRestored += (sender, args) => + { + Log(watch.Elapsed + ": restored: " + EndPointCollection.ToString(args.EndPoint) + "/" + args.ConnectionType + ": " + args); + Interlocked.Increment(ref restored); + }; + var db = conn.GetDatabase(); + await db.PingAsync(); + Assert.Equal(0, Interlocked.CompareExchange(ref failed, 0, 0)); + Assert.Equal(0, Interlocked.CompareExchange(ref restored, 0, 0)); + await Task.Delay(1).ForAwait(); // To make compiler happy in Release + + conn.AllowConnect = false; + var server = conn.GetServer(TestConfig.Current.PrimaryServer, TestConfig.Current.PrimaryPort); + + SetExpectedAmbientFailureCount(2); + server.SimulateConnectionFailure(SimulatedFailureType.All); + + db.Ping(CommandFlags.FireAndForget); + await Task.Delay(250).ForAwait(); + Assert.Equal(2, Interlocked.CompareExchange(ref failed, 0, 0)); + Assert.Equal(0, Interlocked.CompareExchange(ref restored, 0, 0)); + conn.AllowConnect = true; + db.Ping(CommandFlags.FireAndForget); + await Task.Delay(1500).ForAwait(); + Assert.Equal(2, Interlocked.CompareExchange(ref failed, 0, 0)); + Assert.Equal(2, Interlocked.CompareExchange(ref restored, 0, 0)); + watch.Stop(); + } +} diff --git a/tests/StackExchange.Redis.Tests/ConstraintsTests.cs b/tests/StackExchange.Redis.Tests/ConstraintsTests.cs new file mode 100644 index 000000000..6740fe2b3 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ConstraintsTests.cs @@ -0,0 +1,44 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ConstraintsTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public void ValueEquals() + { + RedisValue x = 1, y = "1"; + Assert.True(x.Equals(y), "equals"); + Assert.True(x == y, "operator"); + } + + [Fact] + public async Task TestManualIncr() + { + await using var conn = Create(syncTimeout: 120000); // big timeout while debugging + + var key = Me(); + var db = conn.GetDatabase(); + for (int i = 0; i < 10; i++) + { + db.KeyDelete(key, CommandFlags.FireAndForget); + Assert.Equal(1, await ManualIncrAsync(db, key).ForAwait()); + Assert.Equal(2, await ManualIncrAsync(db, key).ForAwait()); + Assert.Equal(2, (long)db.StringGet(key)); + } + } + + public static async Task ManualIncrAsync(IDatabase connection, RedisKey key) + { + var oldVal = (long?)await connection.StringGetAsync(key).ForAwait(); + var newVal = (oldVal ?? 0) + 1; + var tran = connection.CreateTransaction(); + { // check hasn't changed + tran.AddCondition(Condition.StringEqual(key, oldVal)); + _ = tran.StringSetAsync(key, newVal); + if (!await tran.ExecuteAsync().ForAwait()) return null; // aborted + return newVal; + } + } +} diff --git a/tests/StackExchange.Redis.Tests/CopyTests.cs b/tests/StackExchange.Redis.Tests/CopyTests.cs new file mode 100644 index 000000000..e0003136c --- /dev/null +++ b/tests/StackExchange.Redis.Tests/CopyTests.cs @@ -0,0 +1,65 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class CopyTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task Basic() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var src = Me(); + var dest = Me() + "2"; + _ = db.KeyDelete(dest); + + _ = db.StringSetAsync(src, "Heyyyyy"); + var ke1 = db.KeyCopyAsync(src, dest).ForAwait(); + var ku1 = db.StringGet(dest); + Assert.True(await ke1); + Assert.True(ku1.Equals("Heyyyyy")); + } + + [Fact] + public async Task CrossDB() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var dbDestId = TestConfig.GetDedicatedDB(conn); + var dbDest = conn.GetDatabase(dbDestId); + + var src = Me(); + var dest = Me() + "2"; + dbDest.KeyDelete(dest); + + _ = db.StringSetAsync(src, "Heyyyyy"); + var ke1 = db.KeyCopyAsync(src, dest, dbDestId).ForAwait(); + var ku1 = dbDest.StringGet(dest); + Assert.True(await ke1); + Assert.True(ku1.Equals("Heyyyyy")); + + await Assert.ThrowsAsync(() => db.KeyCopyAsync(src, dest, destinationDatabase: -10)); + } + + [Fact] + public async Task WithReplace() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var src = Me(); + var dest = Me() + "2"; + _ = db.StringSetAsync(src, "foo1"); + _ = db.StringSetAsync(dest, "foo2"); + var ke1 = db.KeyCopyAsync(src, dest).ForAwait(); + var ke2 = db.KeyCopyAsync(src, dest, replace: true).ForAwait(); + var ku1 = db.StringGet(dest); + Assert.False(await ke1); // Should fail when not using replace and destination key exist + Assert.True(await ke2); + Assert.True(ku1.Equals("foo1")); + } +} diff --git a/tests/StackExchange.Redis.Tests/DatabaseTests.cs b/tests/StackExchange.Redis.Tests/DatabaseTests.cs new file mode 100644 index 000000000..bb134c4fd --- /dev/null +++ b/tests/StackExchange.Redis.Tests/DatabaseTests.cs @@ -0,0 +1,207 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class DatabaseTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task CommandCount() + { + await using var conn = Create(); + var server = GetAnyPrimary(conn); + var count = server.CommandCount(); + Assert.True(count > 100); + + count = await server.CommandCountAsync(); + Assert.True(count > 100); + } + + [Fact] + public async Task CommandGetKeys() + { + await using var conn = Create(); + var server = GetAnyPrimary(conn); + + RedisValue[] command = ["MSET", "a", "b", "c", "d", "e", "f"]; + + RedisKey[] keys = server.CommandGetKeys(command); + RedisKey[] expected = ["a", "c", "e"]; + Assert.Equal(keys, expected); + + keys = await server.CommandGetKeysAsync(command); + Assert.Equal(keys, expected); + } + + [Fact] + public async Task CommandList() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + var server = GetAnyPrimary(conn); + + var commands = server.CommandList(); + Assert.True(commands.Length > 100); + commands = await server.CommandListAsync(); + Assert.True(commands.Length > 100); + + commands = server.CommandList(moduleName: "JSON"); + Assert.Empty(commands); + commands = await server.CommandListAsync(moduleName: "JSON"); + Assert.Empty(commands); + + commands = server.CommandList(category: "admin"); + Assert.True(commands.Length > 10); + commands = await server.CommandListAsync(category: "admin"); + Assert.True(commands.Length > 10); + + commands = server.CommandList(pattern: "a*"); + Assert.True(commands.Length > 10); + commands = await server.CommandListAsync(pattern: "a*"); + Assert.True(commands.Length > 10); + + Assert.Throws(() => server.CommandList(moduleName: "JSON", pattern: "a*")); + await Assert.ThrowsAsync(() => server.CommandListAsync(moduleName: "JSON", pattern: "a*")); + } + + [Fact] + public async Task CountKeys() + { + var db1Id = TestConfig.GetDedicatedDB(); + var db2Id = TestConfig.GetDedicatedDB(); + await using (var conn = Create(allowAdmin: true)) + { + Skip.IfMissingDatabase(conn, db1Id); + Skip.IfMissingDatabase(conn, db2Id); + var server = GetAnyPrimary(conn); + server.FlushDatabase(db1Id, CommandFlags.FireAndForget); + server.FlushDatabase(db2Id, CommandFlags.FireAndForget); + } + await using (var conn = Create(defaultDatabase: db2Id)) + { + Skip.IfMissingDatabase(conn, db1Id); + Skip.IfMissingDatabase(conn, db2Id); + RedisKey key = Me(); + var dba = conn.GetDatabase(db1Id); + var dbb = conn.GetDatabase(db2Id); + dba.StringSet("abc", "def", flags: CommandFlags.FireAndForget); + dba.StringIncrement(key, flags: CommandFlags.FireAndForget); + dbb.StringIncrement(key, flags: CommandFlags.FireAndForget); + + var server = GetAnyPrimary(conn); + var c0 = server.DatabaseSizeAsync(db1Id); + var c1 = server.DatabaseSizeAsync(db2Id); + var c2 = server.DatabaseSizeAsync(); // using default DB, which is db2Id + + Assert.Equal(2, await c0); + Assert.Equal(1, await c1); + Assert.Equal(1, await c2); + } + } + + [Fact] + public async Task DatabaseCount() + { + await using var conn = Create(allowAdmin: true); + + var server = GetAnyPrimary(conn); + var count = server.DatabaseCount; + Log("Count: " + count); + var configVal = server.ConfigGet("databases")[0].Value; + Log("Config databases: " + configVal); + Assert.Equal(int.Parse(configVal), count); + } + + [Fact] + public async Task MultiDatabases() + { + await using var conn = Create(); + + RedisKey key = Me(); + var db0 = conn.GetDatabase(TestConfig.GetDedicatedDB(conn)); + var db1 = conn.GetDatabase(TestConfig.GetDedicatedDB(conn)); + var db2 = conn.GetDatabase(TestConfig.GetDedicatedDB(conn)); + + db0.KeyDelete(key, CommandFlags.FireAndForget); + db1.KeyDelete(key, CommandFlags.FireAndForget); + db2.KeyDelete(key, CommandFlags.FireAndForget); + + db0.StringSet(key, "a", flags: CommandFlags.FireAndForget); + db1.StringSet(key, "b", flags: CommandFlags.FireAndForget); + db2.StringSet(key, "c", flags: CommandFlags.FireAndForget); + + var a = db0.StringGetAsync(key); + var b = db1.StringGetAsync(key); + var c = db2.StringGetAsync(key); + + Assert.Equal("a", await a); // db:0 + Assert.Equal("b", await b); // db:1 + Assert.Equal("c", await c); // db:2 + } + + [Fact] + public async Task SwapDatabases() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v4_0_0); + + RedisKey key = Me(); + var db0id = TestConfig.GetDedicatedDB(conn); + var db0 = conn.GetDatabase(db0id); + var db1id = TestConfig.GetDedicatedDB(conn); + var db1 = conn.GetDatabase(db1id); + + db0.KeyDelete(key, CommandFlags.FireAndForget); + db1.KeyDelete(key, CommandFlags.FireAndForget); + + db0.StringSet(key, "a", flags: CommandFlags.FireAndForget); + db1.StringSet(key, "b", flags: CommandFlags.FireAndForget); + + var a = db0.StringGetAsync(key); + var b = db1.StringGetAsync(key); + + Assert.Equal("a", await a); // db:0 + Assert.Equal("b", await b); // db:1 + + var server = GetServer(conn); + server.SwapDatabases(db0id, db1id); + + var aNew = db1.StringGetAsync(key); + var bNew = db0.StringGetAsync(key); + + Assert.Equal("a", await aNew); // db:1 + Assert.Equal("b", await bNew); // db:0 + } + + [Fact] + public async Task SwapDatabasesAsync() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v4_0_0); + + RedisKey key = Me(); + var db0id = TestConfig.GetDedicatedDB(conn); + var db0 = conn.GetDatabase(db0id); + var db1id = TestConfig.GetDedicatedDB(conn); + var db1 = conn.GetDatabase(db1id); + + db0.KeyDelete(key, CommandFlags.FireAndForget); + db1.KeyDelete(key, CommandFlags.FireAndForget); + + db0.StringSet(key, "a", flags: CommandFlags.FireAndForget); + db1.StringSet(key, "b", flags: CommandFlags.FireAndForget); + + var a = db0.StringGetAsync(key); + var b = db1.StringGetAsync(key); + + Assert.Equal("a", await a); // db:0 + Assert.Equal("b", await b); // db:1 + + var server = GetServer(conn); + _ = server.SwapDatabasesAsync(db0id, db1id).ForAwait(); + + var aNew = db1.StringGetAsync(key); + var bNew = db0.StringGetAsync(key); + + Assert.Equal("a", await aNew); // db:1 + Assert.Equal("b", await bNew); // db:0 + } +} diff --git a/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs b/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs new file mode 100644 index 000000000..be80fd9c5 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using StackExchange.Redis.Configuration; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class DefaultOptionsTests(ITestOutputHelper output) : TestBase(output) +{ + public class TestOptionsProvider(string domainSuffix) : DefaultOptionsProvider + { + private readonly string _domainSuffix = domainSuffix; + + public override bool AbortOnConnectFail => true; + public override TimeSpan? ConnectTimeout => TimeSpan.FromSeconds(123); + public override bool AllowAdmin => true; + public override BacklogPolicy BacklogPolicy => BacklogPolicy.FailFast; + public override bool CheckCertificateRevocation => true; + public override CommandMap CommandMap => CommandMap.Create(new HashSet() { "SELECT" }); + public override TimeSpan ConfigCheckInterval => TimeSpan.FromSeconds(124); + public override string ConfigurationChannel => "TestConfigChannel"; + public override int ConnectRetry => 123; + public override Version DefaultVersion => new Version(1, 2, 3, 4); + protected override string GetDefaultClientName() => "TestPrefix-" + base.GetDefaultClientName(); + public override bool HeartbeatConsistencyChecks => true; + public override TimeSpan HeartbeatInterval => TimeSpan.FromMilliseconds(500); + public override bool IsMatch(EndPoint endpoint) => endpoint is DnsEndPoint dnsep && dnsep.Host.EndsWith(_domainSuffix); + public override TimeSpan KeepAliveInterval => TimeSpan.FromSeconds(125); + public override ILoggerFactory? LoggerFactory => NullLoggerFactory.Instance; + public override Proxy Proxy => Proxy.Twemproxy; + public override IReconnectRetryPolicy ReconnectRetryPolicy => new TestRetryPolicy(); + public override bool ResolveDns => true; + public override TimeSpan SyncTimeout => TimeSpan.FromSeconds(126); + public override string TieBreaker => "TestTiebreaker"; + public override string? User => "TestUser"; + public override string? Password => "TestPassword"; + } + + public class TestRetryPolicy : IReconnectRetryPolicy + { + public bool ShouldRetry(long currentRetryCount, int timeElapsedMillisecondsSinceLastRetry) => false; + } + + [Fact] + public void IsMatchOnDomain() + { + DefaultOptionsProvider.AddProvider(new TestOptionsProvider(".testdomain")); + + var epc = new EndPointCollection(new List() { new DnsEndPoint("local.testdomain", 0) }); + var provider = DefaultOptionsProvider.GetProvider(epc); + Assert.IsType(provider); + + epc = new EndPointCollection(new List() { new DnsEndPoint("local.nottestdomain", 0) }); + provider = DefaultOptionsProvider.GetProvider(epc); + Assert.IsType(provider); + } + + [Theory] + [InlineData("contoso.redis.cache.windows.net")] + [InlineData("contoso.REDIS.CACHE.chinacloudapi.cn")] // added a few upper case chars to validate comparison + [InlineData("contoso.redis.cache.usgovcloudapi.net")] + [InlineData("contoso.redisenterprise.cache.azure.net")] + [InlineData("contoso.redis.azure.net")] + [InlineData("contoso.redis.chinacloudapi.cn")] + [InlineData("contoso.redis.usgovcloudapi.net")] + public void IsMatchOnAzureDomain(string hostName) + { + var epc = new EndPointCollection(new List() { new DnsEndPoint(hostName, 0) }); + var provider = DefaultOptionsProvider.GetProvider(epc); + Assert.IsType(provider); + } + + [Fact] + public void AllOverridesFromDefaultsProp() + { + var options = ConfigurationOptions.Parse("localhost"); + Assert.IsType(options.Defaults); + options.Defaults = new TestOptionsProvider(""); + Assert.IsType(options.Defaults); + AssertAllOverrides(options); + } + + [Fact] + public void AllOverridesFromEndpointsParse() + { + DefaultOptionsProvider.AddProvider(new TestOptionsProvider(".parse")); + var options = ConfigurationOptions.Parse("localhost.parse:6379"); + Assert.IsType(options.Defaults); + AssertAllOverrides(options); + } + + private static void AssertAllOverrides(ConfigurationOptions options) + { + Assert.True(options.AbortOnConnectFail); + Assert.Equal(TimeSpan.FromSeconds(123), TimeSpan.FromMilliseconds(options.ConnectTimeout)); + + Assert.True(options.AllowAdmin); + Assert.Equal(BacklogPolicy.FailFast, options.BacklogPolicy); + Assert.True(options.CheckCertificateRevocation); + + Assert.True(options.CommandMap.IsAvailable(RedisCommand.SELECT)); + Assert.False(options.CommandMap.IsAvailable(RedisCommand.GET)); + + Assert.Equal(TimeSpan.FromSeconds(124), TimeSpan.FromSeconds(options.ConfigCheckSeconds)); + Assert.Equal("TestConfigChannel", options.ConfigurationChannel); + Assert.Equal(123, options.ConnectRetry); + Assert.Equal(new Version(1, 2, 3, 4), options.DefaultVersion); + + Assert.True(options.HeartbeatConsistencyChecks); + Assert.Equal(TimeSpan.FromMilliseconds(500), options.HeartbeatInterval); + + Assert.Equal(TimeSpan.FromSeconds(125), TimeSpan.FromSeconds(options.KeepAlive)); + Assert.Equal(NullLoggerFactory.Instance, options.LoggerFactory); + Assert.Equal(Proxy.Twemproxy, options.Proxy); + Assert.IsType(options.ReconnectRetryPolicy); + Assert.True(options.ResolveDns); + Assert.Equal(TimeSpan.FromSeconds(126), TimeSpan.FromMilliseconds(options.SyncTimeout)); + Assert.Equal("TestTiebreaker", options.TieBreaker); + Assert.Equal("TestUser", options.User); + Assert.Equal("TestPassword", options.Password); + } + + public class TestAfterConnectOptionsProvider : DefaultOptionsProvider + { + public int Calls; + + public override Task AfterConnectAsync(ConnectionMultiplexer muxer, Action log) + { + Interlocked.Increment(ref Calls); + log("TestAfterConnectOptionsProvider.AfterConnectAsync!"); + return Task.CompletedTask; + } + } + + [Fact] + public async Task AfterConnectAsyncHandler() + { + var options = ConfigurationOptions.Parse(GetConfiguration()); + var provider = new TestAfterConnectOptionsProvider(); + options.Defaults = provider; + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer); + + Assert.True(conn.IsConnected); + Assert.Equal(1, provider.Calls); + } + + public class TestClientNameOptionsProvider : DefaultOptionsProvider + { + protected override string GetDefaultClientName() => "Hey there"; + } + + [Fact] + public async Task ClientNameOverride() + { + var options = ConfigurationOptions.Parse(GetConfiguration()); + options.Defaults = new TestClientNameOptionsProvider(); + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer); + + Assert.True(conn.IsConnected); + Assert.Equal("Hey there", conn.ClientName); + } + + [Fact] + public async Task ClientNameExplicitWins() + { + var options = ConfigurationOptions.Parse(GetConfiguration() + ",name=FooBar"); + options.Defaults = new TestClientNameOptionsProvider(); + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer); + + Assert.True(conn.IsConnected); + Assert.Equal("FooBar", conn.ClientName); + } + + public class TestLibraryNameOptionsProvider : DefaultOptionsProvider + { + public string Id { get; } = Guid.NewGuid().ToString(); + public override string LibraryName => Id; + } + + [Fact] + public async Task LibraryNameOverride() + { + var options = ConfigurationOptions.Parse(GetConfiguration()); + var defaults = new TestLibraryNameOptionsProvider(); + options.AllowAdmin = true; + options.Defaults = defaults; + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer); + // CLIENT SETINFO is in 7.2.0+ + TestBase.ThrowIfBelowMinVersion(conn, RedisFeatures.v7_2_0_rc1); + + var clients = await GetServer(conn).ClientListAsync(); + foreach (var client in clients) + { + Log("Library name: " + client.LibraryName); + } + + Assert.True(conn.IsConnected); + Assert.True(clients.Any(c => c.LibraryName == defaults.LibraryName), "Did not find client with name: " + defaults.Id); + } +} diff --git a/tests/StackExchange.Redis.Tests/DefaultPortsTests.cs b/tests/StackExchange.Redis.Tests/DefaultPortsTests.cs new file mode 100644 index 000000000..965bc6ef1 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/DefaultPortsTests.cs @@ -0,0 +1,57 @@ +using System.Linq; +using System.Net; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class DefaultPortsTests +{ + [Theory] + [InlineData("foo", 6379)] + [InlineData("foo:6379", 6379)] + [InlineData("foo:6380", 6380)] + [InlineData("foo,ssl=false", 6379)] + [InlineData("foo:6379,ssl=false", 6379)] + [InlineData("foo:6380,ssl=false", 6380)] + + [InlineData("foo,ssl=true", 6380)] + [InlineData("foo:6379,ssl=true", 6379)] + [InlineData("foo:6380,ssl=true", 6380)] + [InlineData("foo:6381,ssl=true", 6381)] + public void ConfigStringRoundTripWithDefaultPorts(string config, int expectedPort) + { + var options = ConfigurationOptions.Parse(config); + string backAgain = options.ToString(); + Assert.Equal(config, backAgain.Replace("=True", "=true").Replace("=False", "=false")); + + options.SetDefaultPorts(); // normally it is the multiplexer that calls this, not us + Assert.Equal(expectedPort, ((DnsEndPoint)options.EndPoints.Single()).Port); + } + + [Theory] + [InlineData("foo", 0, false, 6379)] + [InlineData("foo", 6379, false, 6379)] + [InlineData("foo", 6380, false, 6380)] + + [InlineData("foo", 0, true, 6380)] + [InlineData("foo", 6379, true, 6379)] + [InlineData("foo", 6380, true, 6380)] + [InlineData("foo", 6381, true, 6381)] + + public void ConfigManualWithDefaultPorts(string host, int port, bool useSsl, int expectedPort) + { + var options = new ConfigurationOptions(); + if (port == 0) + { + options.EndPoints.Add(host); + } + else + { + options.EndPoints.Add(host, port); + } + if (useSsl) options.Ssl = true; + + options.SetDefaultPorts(); // normally it is the multiplexer that calls this, not us + Assert.Equal(expectedPort, ((DnsEndPoint)options.EndPoints.Single()).Port); + } +} diff --git a/tests/StackExchange.Redis.Tests/DeprecatedTests.cs b/tests/StackExchange.Redis.Tests/DeprecatedTests.cs new file mode 100644 index 000000000..ab909ea16 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/DeprecatedTests.cs @@ -0,0 +1,70 @@ +using System; +using Xunit; + +namespace StackExchange.Redis.Tests; + +/// +/// Testing that things we deprecate still parse, but are otherwise defaults. +/// +public class DeprecatedTests(ITestOutputHelper output) : TestBase(output) +{ +#pragma warning disable CS0618 // Type or member is obsolete + [Fact] + public void HighPrioritySocketThreads() + { + Assert.True(Attribute.IsDefined(typeof(ConfigurationOptions).GetProperty(nameof(ConfigurationOptions.HighPrioritySocketThreads))!, typeof(ObsoleteAttribute))); + + var options = ConfigurationOptions.Parse("name=Hello"); + Assert.False(options.HighPrioritySocketThreads); + + options = ConfigurationOptions.Parse("highPriorityThreads=true"); + Assert.Equal("", options.ToString()); + Assert.False(options.HighPrioritySocketThreads); + + options = ConfigurationOptions.Parse("highPriorityThreads=false"); + Assert.Equal("", options.ToString()); + Assert.False(options.HighPrioritySocketThreads); + } + + [Fact] + public void PreserveAsyncOrder() + { + Assert.True(Attribute.IsDefined(typeof(ConfigurationOptions).GetProperty(nameof(ConfigurationOptions.PreserveAsyncOrder))!, typeof(ObsoleteAttribute))); + + var options = ConfigurationOptions.Parse("name=Hello"); + Assert.False(options.PreserveAsyncOrder); + + options = ConfigurationOptions.Parse("preserveAsyncOrder=true"); + Assert.Equal("", options.ToString()); + Assert.False(options.PreserveAsyncOrder); + + options = ConfigurationOptions.Parse("preserveAsyncOrder=false"); + Assert.Equal("", options.ToString()); + Assert.False(options.PreserveAsyncOrder); + } + + [Fact] + public void WriteBufferParse() + { + Assert.True(Attribute.IsDefined(typeof(ConfigurationOptions).GetProperty(nameof(ConfigurationOptions.WriteBuffer))!, typeof(ObsoleteAttribute))); + + var options = ConfigurationOptions.Parse("name=Hello"); + Assert.Equal(0, options.WriteBuffer); + + options = ConfigurationOptions.Parse("writeBuffer=8092"); + Assert.Equal(0, options.WriteBuffer); + } + + [Fact] + public void ResponseTimeout() + { + Assert.True(Attribute.IsDefined(typeof(ConfigurationOptions).GetProperty(nameof(ConfigurationOptions.ResponseTimeout))!, typeof(ObsoleteAttribute))); + + var options = ConfigurationOptions.Parse("name=Hello"); + Assert.Equal(0, options.ResponseTimeout); + + options = ConfigurationOptions.Parse("responseTimeout=1000"); + Assert.Equal(0, options.ResponseTimeout); + } +#pragma warning restore CS0618 +} diff --git a/tests/StackExchange.Redis.Tests/EnvoyTests.cs b/tests/StackExchange.Redis.Tests/EnvoyTests.cs new file mode 100644 index 000000000..d9d22c801 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/EnvoyTests.cs @@ -0,0 +1,47 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class EnvoyTests(ITestOutputHelper output) : TestBase(output) +{ + protected override string GetConfiguration() => TestConfig.Current.ProxyServerAndPort; + + /// + /// Tests basic envoy connection with the ability to set and get a key. + /// + [Fact] + public async Task TestBasicEnvoyConnection() + { + var sb = new StringBuilder(); + Writer.EchoTo(sb); + try + { + await using var conn = Create(configuration: GetConfiguration(), keepAlive: 1, connectTimeout: 2000, allowAdmin: true, shared: false, proxy: Proxy.Envoyproxy, log: Writer); + + var db = conn.GetDatabase(); + + var key = Me() + "foobar"; + const string value = "barfoo"; + db.StringSet(key, value); + + var expectedVal = db.StringGet(key); + + Assert.Equal(value, expectedVal); + } + catch (TimeoutException ex) when (ex.Message == "Connect timeout" || sb.ToString().Contains("Returned, but incorrectly")) + { + Assert.Skip($"Envoy server not found: {ex}."); + } + catch (AggregateException ex) + { + Assert.Skip($"Envoy server not found: {ex}."); + } + catch (RedisConnectionException ex) when (sb.ToString().Contains("It was not possible to connect to the redis server(s)")) + { + Assert.Skip($"Envoy server not found: {ex}."); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/EventArgsTests.cs b/tests/StackExchange.Redis.Tests/EventArgsTests.cs new file mode 100644 index 000000000..74b5e369a --- /dev/null +++ b/tests/StackExchange.Redis.Tests/EventArgsTests.cs @@ -0,0 +1,81 @@ +using System; +using NSubstitute; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class EventArgsTests +{ + [Fact] + public void EventArgsCanBeSubstituted() + { + EndPointEventArgs endpointArgsMock + = Substitute.For(default, default); + + RedisErrorEventArgs redisErrorArgsMock + = Substitute.For(default, default, default); + + ConnectionFailedEventArgs connectionFailedArgsMock + = Substitute.For(default, default, default, default, default, default); + + InternalErrorEventArgs internalErrorArgsMock + = Substitute.For(default, default, default, default, default); + + HashSlotMovedEventArgs hashSlotMovedArgsMock + = Substitute.For(default, default, default, default); + + DiagnosticStub stub = new DiagnosticStub(); + + stub.ConfigurationChangedBroadcastHandler(default, endpointArgsMock); + Assert.Equal(DiagnosticStub.ConfigurationChangedBroadcastHandlerMessage, stub.Message); + + stub.ErrorMessageHandler(default, redisErrorArgsMock); + Assert.Equal(DiagnosticStub.ErrorMessageHandlerMessage, stub.Message); + + stub.ConnectionFailedHandler(default, connectionFailedArgsMock); + Assert.Equal(DiagnosticStub.ConnectionFailedHandlerMessage, stub.Message); + + stub.InternalErrorHandler(default, internalErrorArgsMock); + Assert.Equal(DiagnosticStub.InternalErrorHandlerMessage, stub.Message); + + stub.ConnectionRestoredHandler(default, connectionFailedArgsMock); + Assert.Equal(DiagnosticStub.ConnectionRestoredHandlerMessage, stub.Message); + + stub.ConfigurationChangedHandler(default, endpointArgsMock); + Assert.Equal(DiagnosticStub.ConfigurationChangedHandlerMessage, stub.Message); + + stub.HashSlotMovedHandler(default, hashSlotMovedArgsMock); + Assert.Equal(DiagnosticStub.HashSlotMovedHandlerMessage, stub.Message); + } + + public class DiagnosticStub + { + public const string ConfigurationChangedBroadcastHandlerMessage = "ConfigurationChangedBroadcastHandler invoked"; + public const string ErrorMessageHandlerMessage = "ErrorMessageHandler invoked"; + public const string ConnectionFailedHandlerMessage = "ConnectionFailedHandler invoked"; + public const string InternalErrorHandlerMessage = "InternalErrorHandler invoked"; + public const string ConnectionRestoredHandlerMessage = "ConnectionRestoredHandler invoked"; + public const string ConfigurationChangedHandlerMessage = "ConfigurationChangedHandler invoked"; + public const string HashSlotMovedHandlerMessage = "HashSlotMovedHandler invoked"; + + public DiagnosticStub() + { + ConfigurationChangedBroadcastHandler = (obj, args) => Message = ConfigurationChangedBroadcastHandlerMessage; + ErrorMessageHandler = (obj, args) => Message = ErrorMessageHandlerMessage; + ConnectionFailedHandler = (obj, args) => Message = ConnectionFailedHandlerMessage; + InternalErrorHandler = (obj, args) => Message = InternalErrorHandlerMessage; + ConnectionRestoredHandler = (obj, args) => Message = ConnectionRestoredHandlerMessage; + ConfigurationChangedHandler = (obj, args) => Message = ConfigurationChangedHandlerMessage; + HashSlotMovedHandler = (obj, args) => Message = HashSlotMovedHandlerMessage; + } + + public string? Message { get; private set; } + public Action ConfigurationChangedBroadcastHandler { get; } + public Action ErrorMessageHandler { get; } + public Action ConnectionFailedHandler { get; } + public Action InternalErrorHandler { get; } + public Action ConnectionRestoredHandler { get; } + public Action ConfigurationChangedHandler { get; } + public Action HashSlotMovedHandler { get; } + } +} diff --git a/tests/StackExchange.Redis.Tests/ExceptionFactoryTests.cs b/tests/StackExchange.Redis.Tests/ExceptionFactoryTests.cs new file mode 100644 index 000000000..53f28f163 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ExceptionFactoryTests.cs @@ -0,0 +1,255 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ExceptionFactoryTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task NullLastException() + { + await using var conn = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true); + + conn.GetDatabase(); + Assert.Null(conn.GetServerSnapshot()[0].LastException); + var ex = ExceptionFactory.NoConnectionAvailable(conn.UnderlyingMultiplexer, null, null); + Assert.Null(ex.InnerException); + } + + [Fact] + public void CanGetVersion() + { + var libVer = Utils.GetLibVersion(); + Assert.Matches(@"2\.[0-9]+\.[0-9]+(\.[0-9]+)?", libVer); + } + +#if DEBUG + [Fact] + public async Task MultipleEndpointsThrowConnectionException() + { + try + { + await using var conn = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true, shared: false); + + conn.GetDatabase(); + conn.AllowConnect = false; + + foreach (var endpoint in conn.GetEndPoints()) + { + conn.GetServer(endpoint).SimulateConnectionFailure(SimulatedFailureType.All); + } + + var ex = ExceptionFactory.NoConnectionAvailable(conn.UnderlyingMultiplexer, null, null); + var outer = Assert.IsType(ex); + Assert.Equal(ConnectionFailureType.UnableToResolvePhysicalConnection, outer.FailureType); + var inner = Assert.IsType(outer.InnerException); + Assert.True(inner.FailureType == ConnectionFailureType.SocketFailure + || inner.FailureType == ConnectionFailureType.InternalFailure); + } + finally + { + ClearAmbientFailures(); + } + } +#endif + + [Fact] + public async Task ServerTakesPrecendenceOverSnapshot() + { + try + { + await using var conn = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true, shared: false, backlogPolicy: BacklogPolicy.FailFast); + + conn.GetDatabase(); + conn.AllowConnect = false; + + conn.GetServer(conn.GetEndPoints()[0]).SimulateConnectionFailure(SimulatedFailureType.All); + + var ex = ExceptionFactory.NoConnectionAvailable(conn.UnderlyingMultiplexer, null, conn.GetServerSnapshot()[0]); + Assert.IsType(ex); + Assert.IsType(ex.InnerException); + Assert.Equal(ex.InnerException, conn.GetServerSnapshot()[0].LastException); + } + finally + { + ClearAmbientFailures(); + } + } + + [Fact] + public async Task NullInnerExceptionForMultipleEndpointsWithNoLastException() + { + try + { + await using var conn = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true); + + conn.GetDatabase(); + conn.AllowConnect = false; + var ex = ExceptionFactory.NoConnectionAvailable(conn.UnderlyingMultiplexer, null, null); + Assert.IsType(ex); + Assert.Null(ex.InnerException); + } + finally + { + ClearAmbientFailures(); + } + } + + [Fact] + public async Task TimeoutException() + { + try + { + await using var conn = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true, shared: false); + + var server = GetServer(conn); + conn.AllowConnect = false; + var msg = Message.Create(-1, CommandFlags.None, RedisCommand.PING); + var rawEx = ExceptionFactory.Timeout(conn.UnderlyingMultiplexer, "Test Timeout", msg, new ServerEndPoint(conn.UnderlyingMultiplexer, server.EndPoint)); + var ex = Assert.IsType(rawEx); + Log("Exception: " + ex.Message); + + // Example format: "Test Timeout, command=PING, inst: 0, qu: 0, qs: 0, aw: False, in: 0, in-pipe: 0, out-pipe: 0, last-in: 0, cur-in: 0, serverEndpoint: 127.0.0.1:6379, mgr: 10 of 10 available, clientName: TimeoutException, IOCP: (Busy=0,Free=1000,Min=8,Max=1000), WORKER: (Busy=2,Free=2045,Min=8,Max=2047), v: 2.1.0 (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts)"; + Assert.StartsWith("Test Timeout, command=PING", ex.Message); + Assert.Contains("clientName: " + nameof(TimeoutException), ex.Message); + // Ensure our pipe numbers are in place + Assert.Contains("inst: 0, qu: 0, qs: 0, aw: False, bw: Inactive, in: 0, in-pipe: 0, out-pipe: 0, last-in: 0, cur-in: 0", ex.Message); + Assert.Contains("mc: 1/1/0", ex.Message); + Assert.Contains("serverEndpoint: " + server.EndPoint, ex.Message); + Assert.Contains("IOCP: ", ex.Message); + Assert.Contains("WORKER: ", ex.Message); + Assert.Contains("sync-ops: ", ex.Message); + Assert.Contains("async-ops: ", ex.Message); + Assert.Contains("conn-sec: n/a", ex.Message); + Assert.Contains("aoc: 1", ex.Message); +#if NETCOREAPP + // ...POOL: (Threads=33,QueuedItems=0,CompletedItems=5547,Timers=60)... + Assert.Contains("POOL: ", ex.Message); + Assert.Contains("Threads=", ex.Message); + Assert.Contains("QueuedItems=", ex.Message); + Assert.Contains("CompletedItems=", ex.Message); + Assert.Contains("Timers=", ex.Message); +#endif + Assert.DoesNotContain("Unspecified/", ex.Message); + Assert.EndsWith(" (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts)", ex.Message); + Assert.Null(ex.InnerException); + } + finally + { + ClearAmbientFailures(); + } + } + + [Theory] + [InlineData(false, 0, 0, true, "Connection to Redis never succeeded (attempts: 0 - connection likely in-progress), unable to service operation: PING")] + [InlineData(false, 1, 0, true, "Connection to Redis never succeeded (attempts: 1 - connection likely in-progress), unable to service operation: PING")] + [InlineData(false, 12, 0, true, "Connection to Redis never succeeded (attempts: 12 - check your config), unable to service operation: PING")] + [InlineData(false, 0, 0, false, "Connection to Redis never succeeded (attempts: 0 - connection likely in-progress), unable to service operation: PING")] + [InlineData(false, 1, 0, false, "Connection to Redis never succeeded (attempts: 1 - connection likely in-progress), unable to service operation: PING")] + [InlineData(false, 12, 0, false, "Connection to Redis never succeeded (attempts: 12 - check your config), unable to service operation: PING")] + [InlineData(true, 0, 0, true, "No connection is active/available to service this operation: PING")] + [InlineData(true, 1, 0, true, "No connection is active/available to service this operation: PING")] + [InlineData(true, 12, 0, true, "No connection is active/available to service this operation: PING")] + public async Task NoConnectionException(bool abortOnConnect, int connCount, int completeCount, bool hasDetail, string messageStart) + { + try + { + var options = new ConfigurationOptions() + { + AbortOnConnectFail = abortOnConnect, + BacklogPolicy = BacklogPolicy.FailFast, + ConnectTimeout = 1000, + SyncTimeout = 500, + KeepAlive = 5000, + }; + + ConnectionMultiplexer conn; + if (abortOnConnect) + { + options.EndPoints.Add(TestConfig.Current.PrimaryServerAndPort); + conn = ConnectionMultiplexer.Connect(options, Writer); + } + else + { + options.EndPoints.Add($"doesnot.exist.{Guid.NewGuid():N}:6379"); + conn = ConnectionMultiplexer.Connect(options, Writer); + } + + await using (conn) + { + var server = conn.GetServer(conn.GetEndPoints()[0]); + conn.AllowConnect = false; + conn._connectAttemptCount = connCount; + conn._connectCompletedCount = completeCount; + options.IncludeDetailInExceptions = hasDetail; + options.IncludePerformanceCountersInExceptions = hasDetail; + + var msg = Message.Create(-1, CommandFlags.None, RedisCommand.PING); + var rawEx = ExceptionFactory.NoConnectionAvailable(conn, msg, new ServerEndPoint(conn, server.EndPoint)); + var ex = Assert.IsType(rawEx); + Log("Exception: " + ex.Message); + + // Example format: "Exception: No connection is active/available to service this operation: PING, inst: 0, qu: 0, qs: 0, aw: False, in: 0, in-pipe: 0, out-pipe: 0, last-in: 0, cur-in: 0, serverEndpoint: 127.0.0.1:6379, mc: 1/1/0, mgr: 10 of 10 available, clientName: NoConnectionException, IOCP: (Busy=0,Free=1000,Min=8,Max=1000), WORKER: (Busy=2,Free=2045,Min=8,Max=2047), Local-CPU: 100%, v: 2.1.0.5"; + Assert.StartsWith(messageStart, ex.Message); + + // Ensure our pipe numbers are in place if they should be + if (hasDetail) + { + Assert.Contains("inst: 0, qu: 0, qs: 0, aw: False, bw: Inactive, in: 0, in-pipe: 0, out-pipe: 0, last-in: 0, cur-in: 0", ex.Message); + Assert.Contains($"mc: {connCount}/{completeCount}/0", ex.Message); + Assert.Contains("serverEndpoint: " + server.EndPoint.ToString()?.Replace("Unspecified/", ""), ex.Message); + } + else + { + Assert.DoesNotContain("inst: 0, qu: 0, qs: 0, aw: False, bw: Inactive, in: 0, in-pipe: 0, out-pipe: 0, last-in: 0, cur-in: 0", ex.Message); + Assert.DoesNotContain($"mc: {connCount}/{completeCount}/0", ex.Message); + Assert.DoesNotContain("serverEndpoint: " + server.EndPoint.ToString()?.Replace("Unspecified/", ""), ex.Message); + } + Assert.DoesNotContain("Unspecified/", ex.Message); + } + } + finally + { + ClearAmbientFailures(); + } + } + + [Fact] + public async Task NoConnectionPrimaryOnlyException() + { + await using var conn = await ConnectionMultiplexer.ConnectAsync(TestConfig.Current.ReplicaServerAndPort, Writer); + + var msg = Message.Create(0, CommandFlags.None, RedisCommand.SET, (RedisKey)Me(), (RedisValue)"test"); + Assert.True(msg.IsPrimaryOnly()); + var rawEx = ExceptionFactory.NoConnectionAvailable(conn, msg, null); + var ex = Assert.IsType(rawEx); + Log("Exception: " + ex.Message); + + // Ensure a primary-only operation like SET gives the additional context + Assert.StartsWith("No connection (requires writable - not eligible for replica) is active/available to service this operation: SET", ex.Message); + } + + [Theory] + [InlineData(true, ConnectionFailureType.ProtocolFailure, "ProtocolFailure on [0]:GET myKey (StringProcessor), my annotation")] + [InlineData(true, ConnectionFailureType.ConnectionDisposed, "ConnectionDisposed on [0]:GET myKey (StringProcessor), my annotation")] + [InlineData(false, ConnectionFailureType.ProtocolFailure, "ProtocolFailure on [0]:GET (StringProcessor), my annotation")] + [InlineData(false, ConnectionFailureType.ConnectionDisposed, "ConnectionDisposed on [0]:GET (StringProcessor), my annotation")] + public async Task MessageFail(bool includeDetail, ConnectionFailureType failType, string messageStart) + { + await using var conn = Create(shared: false); + + conn.RawConfig.IncludeDetailInExceptions = includeDetail; + + var message = Message.Create(0, CommandFlags.None, RedisCommand.GET, (RedisKey)"myKey"); + var resultBox = SimpleResultBox.Create(); + message.SetSource(ResultProcessor.String, resultBox); + + message.Fail(failType, null, "my annotation", conn.UnderlyingMultiplexer); + + resultBox.GetResult(out var ex); + Assert.NotNull(ex); + + Assert.StartsWith(messageStart, ex.Message); + } +} diff --git a/tests/StackExchange.Redis.Tests/ExecuteTests.cs b/tests/StackExchange.Redis.Tests/ExecuteTests.cs new file mode 100644 index 000000000..1e1f10bd4 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ExecuteTests.cs @@ -0,0 +1,37 @@ +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ExecuteTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task DBExecute() + { + await using var conn = Create(); + + var db = conn.GetDatabase(4); + RedisKey key = Me(); + db.StringSet(key, "some value"); + + var actual = (string?)db.Execute("GET", key); + Assert.Equal("some value", actual); + + actual = (string?)await db.ExecuteAsync("GET", key).ForAwait(); + Assert.Equal("some value", actual); + } + + [Fact] + public async Task ServerExecute() + { + await using var conn = Create(); + + var server = conn.GetServer(conn.GetEndPoints().First()); + var actual = (string?)server.Execute("echo", "some value"); + Assert.Equal("some value", actual); + + actual = (string?)await server.ExecuteAsync("echo", "some value").ForAwait(); + Assert.Equal("some value", actual); + } +} diff --git a/tests/StackExchange.Redis.Tests/ExpiryTests.cs b/tests/StackExchange.Redis.Tests/ExpiryTests.cs new file mode 100644 index 000000000..fab26586f --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ExpiryTests.cs @@ -0,0 +1,192 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ExpiryTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + private static string[]? GetMap(bool disablePTimes) => disablePTimes ? ["pexpire", "pexpireat", "pttl"] : null; + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task TestBasicExpiryTimeSpan(bool disablePTimes) + { + await using var conn = Create(disabledCommands: GetMap(disablePTimes)); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + var a = db.KeyTimeToLiveAsync(key); + db.KeyExpire(key, TimeSpan.FromHours(1), CommandFlags.FireAndForget); + var b = db.KeyTimeToLiveAsync(key); + db.KeyExpire(key, (TimeSpan?)null, CommandFlags.FireAndForget); + var c = db.KeyTimeToLiveAsync(key); + db.KeyExpire(key, TimeSpan.FromHours(1.5), CommandFlags.FireAndForget); + var d = db.KeyTimeToLiveAsync(key); + db.KeyExpire(key, TimeSpan.MaxValue, CommandFlags.FireAndForget); + var e = db.KeyTimeToLiveAsync(key); + db.KeyDelete(key, CommandFlags.FireAndForget); + var f = db.KeyTimeToLiveAsync(key); + + Assert.Null(await a); + var time = await b; + Assert.NotNull(time); + Assert.True(time > TimeSpan.FromMinutes(59.9) && time <= TimeSpan.FromMinutes(60)); + Assert.Null(await c); + time = await d; + Assert.NotNull(time); + Assert.True(time > TimeSpan.FromMinutes(89.9) && time <= TimeSpan.FromMinutes(90)); + Assert.Null(await e); + Assert.Null(await f); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task TestExpiryOptions(bool disablePTimes) + { + await using var conn = Create(disabledCommands: GetMap(disablePTimes), require: RedisFeatures.v7_0_0_rc1); + + var key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key); + db.StringSet(key, "value"); + + // The key has no expiry + Assert.False(await db.KeyExpireAsync(key, TimeSpan.FromHours(1), ExpireWhen.HasExpiry)); + Assert.True(await db.KeyExpireAsync(key, TimeSpan.FromHours(1), ExpireWhen.HasNoExpiry)); + + // The key has an existing expiry + Assert.True(await db.KeyExpireAsync(key, TimeSpan.FromHours(1), ExpireWhen.HasExpiry)); + Assert.False(await db.KeyExpireAsync(key, TimeSpan.FromHours(1), ExpireWhen.HasNoExpiry)); + + // Set only when the new expiry is greater than current one + Assert.True(await db.KeyExpireAsync(key, TimeSpan.FromHours(1.5), ExpireWhen.GreaterThanCurrentExpiry)); + Assert.False(await db.KeyExpireAsync(key, TimeSpan.FromHours(0.5), ExpireWhen.GreaterThanCurrentExpiry)); + + // Set only when the new expiry is less than current one + Assert.True(await db.KeyExpireAsync(key, TimeSpan.FromHours(0.5), ExpireWhen.LessThanCurrentExpiry)); + Assert.False(await db.KeyExpireAsync(key, TimeSpan.FromHours(1.5), ExpireWhen.LessThanCurrentExpiry)); + } + + [Theory] + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public async Task TestBasicExpiryDateTime(bool disablePTimes, bool utc) + { + await using var conn = Create(disabledCommands: GetMap(disablePTimes)); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var now = utc ? DateTime.UtcNow : DateTime.Now; + var serverTime = GetServer(conn).Time(); + Log("Server time: {0}", serverTime); + var offset = DateTime.UtcNow - serverTime; + + Log("Now (local time): {0}", now); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + var a = db.KeyTimeToLiveAsync(key); + db.KeyExpire(key, now.AddHours(1), CommandFlags.FireAndForget); + var b = db.KeyTimeToLiveAsync(key); + db.KeyExpire(key, (DateTime?)null, CommandFlags.FireAndForget); + var c = db.KeyTimeToLiveAsync(key); + db.KeyExpire(key, now.AddHours(1.5), CommandFlags.FireAndForget); + var d = db.KeyTimeToLiveAsync(key); + db.KeyExpire(key, DateTime.MaxValue, CommandFlags.FireAndForget); + var e = db.KeyTimeToLiveAsync(key); + db.KeyDelete(key, CommandFlags.FireAndForget); + var f = db.KeyTimeToLiveAsync(key); + + Assert.Null(await a); + var timeResult = await b; + Assert.NotNull(timeResult); + TimeSpan time = timeResult.Value; + + // Adjust for server time offset, if any when checking expectations + time -= offset; + + Log("Time: {0}, Expected: {1}-{2}", time, TimeSpan.FromMinutes(59), TimeSpan.FromMinutes(60)); + Assert.True(time >= TimeSpan.FromMinutes(59)); + Assert.True(time <= TimeSpan.FromMinutes(60.1)); + Assert.Null(await c); + + timeResult = await d; + Assert.NotNull(timeResult); + time = timeResult.Value; + + Assert.True(time >= TimeSpan.FromMinutes(89)); + Assert.True(time <= TimeSpan.FromMinutes(90.1)); + Assert.Null(await e); + Assert.Null(await f); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task KeyExpiryTime(bool disablePTimes) + { + await using var conn = Create(disabledCommands: GetMap(disablePTimes), require: RedisFeatures.v7_0_0_rc1); + + var key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var expireTime = DateTime.UtcNow.AddHours(1); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + db.KeyExpire(key, expireTime, CommandFlags.FireAndForget); + + var time = db.KeyExpireTime(key); + Assert.NotNull(time); + Assert.Equal(expireTime, time!.Value, TimeSpan.FromSeconds(30)); + + // Without associated expiration time + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + time = db.KeyExpireTime(key); + Assert.Null(time); + + // Non existing key + db.KeyDelete(key, CommandFlags.FireAndForget); + time = db.KeyExpireTime(key); + Assert.Null(time); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task KeyExpiryTimeAsync(bool disablePTimes) + { + await using var conn = Create(disabledCommands: GetMap(disablePTimes), require: RedisFeatures.v7_0_0_rc1); + + var key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var expireTime = DateTime.UtcNow.AddHours(1); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + db.KeyExpire(key, expireTime, CommandFlags.FireAndForget); + + var time = await db.KeyExpireTimeAsync(key); + Assert.NotNull(time); + Assert.Equal(expireTime, time.Value, TimeSpan.FromSeconds(30)); + + // Without associated expiration time + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + time = await db.KeyExpireTimeAsync(key); + Assert.Null(time); + + // Non existing key + db.KeyDelete(key, CommandFlags.FireAndForget); + time = await db.KeyExpireTimeAsync(key); + Assert.Null(time); + } +} diff --git a/tests/StackExchange.Redis.Tests/ExpiryTokenTests.cs b/tests/StackExchange.Redis.Tests/ExpiryTokenTests.cs new file mode 100644 index 000000000..3f0d39f28 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ExpiryTokenTests.cs @@ -0,0 +1,117 @@ +using System; +using Xunit; +using static StackExchange.Redis.RedisDatabase; +using static StackExchange.Redis.RedisDatabase.ExpiryToken; +namespace StackExchange.Redis.Tests; + +public class ExpiryTokenTests // pure tests, no DB +{ + [Fact] + public void Persist_Seconds() + { + TimeSpan? time = TimeSpan.FromMilliseconds(5000); + var ex = Persist(time, false); + Assert.Equal(2, ex.Tokens); + Assert.Equal("EX 5", ex.ToString()); + } + + [Fact] + public void Persist_Milliseconds() + { + TimeSpan? time = TimeSpan.FromMilliseconds(5001); + var ex = Persist(time, false); + Assert.Equal(2, ex.Tokens); + Assert.Equal("PX 5001", ex.ToString()); + } + + [Fact] + public void Persist_None_False() + { + TimeSpan? time = null; + var ex = Persist(time, false); + Assert.Equal(0, ex.Tokens); + Assert.Equal("", ex.ToString()); + } + + [Fact] + public void Persist_None_True() + { + TimeSpan? time = null; + var ex = Persist(time, true); + Assert.Equal(1, ex.Tokens); + Assert.Equal("PERSIST", ex.ToString()); + } + + [Fact] + public void Persist_Both() + { + TimeSpan? time = TimeSpan.FromMilliseconds(5000); + var ex = Assert.Throws(() => Persist(time, true)); + Assert.Equal("persist", ex.ParamName); + Assert.StartsWith("Cannot specify both expiry and persist", ex.Message); + } + + [Fact] + public void KeepTtl_Seconds() + { + TimeSpan? time = TimeSpan.FromMilliseconds(5000); + var ex = KeepTtl(time, false); + Assert.Equal(2, ex.Tokens); + Assert.Equal("EX 5", ex.ToString()); + } + + [Fact] + public void KeepTtl_Milliseconds() + { + TimeSpan? time = TimeSpan.FromMilliseconds(5001); + var ex = KeepTtl(time, false); + Assert.Equal(2, ex.Tokens); + Assert.Equal("PX 5001", ex.ToString()); + } + + [Fact] + public void KeepTtl_None_False() + { + TimeSpan? time = null; + var ex = KeepTtl(time, false); + Assert.Equal(0, ex.Tokens); + Assert.Equal("", ex.ToString()); + } + + [Fact] + public void KeepTtl_None_True() + { + TimeSpan? time = null; + var ex = KeepTtl(time, true); + Assert.Equal(1, ex.Tokens); + Assert.Equal("KEEPTTL", ex.ToString()); + } + + [Fact] + public void KeepTtl_Both() + { + TimeSpan? time = TimeSpan.FromMilliseconds(5000); + var ex = Assert.Throws(() => KeepTtl(time, true)); + Assert.Equal("keepTtl", ex.ParamName); + Assert.StartsWith("Cannot specify both expiry and keepTtl", ex.Message); + } + + [Fact] + public void DateTime_Seconds() + { + var when = new DateTime(2025, 7, 23, 10, 4, 14, DateTimeKind.Utc); + var ex = new ExpiryToken(when); + Assert.Equal(2, ex.Tokens); + Assert.Equal("EXAT 1753265054", ex.ToString()); + } + + [Fact] + public void DateTime_Milliseconds() + { + var when = new DateTime(2025, 7, 23, 10, 4, 14, DateTimeKind.Utc); + when = when.AddMilliseconds(14); + var ex = new ExpiryToken(when); + Assert.Equal(2, ex.Tokens); + Assert.Equal("PXAT 1753265054014", ex.ToString()); + } +} diff --git a/tests/StackExchange.Redis.Tests/FSharpCompatTests.cs b/tests/StackExchange.Redis.Tests/FSharpCompatTests.cs new file mode 100644 index 000000000..192234f2d --- /dev/null +++ b/tests/StackExchange.Redis.Tests/FSharpCompatTests.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class FSharpCompatTests(ITestOutputHelper output) : TestBase(output) +{ +#pragma warning disable SA1129 // Do not use default value type constructor + [Fact] + public void RedisKeyConstructor() + { + Assert.Equal(default, new RedisKey()); + Assert.Equal((RedisKey)"MyKey", new RedisKey("MyKey")); + Assert.Equal((RedisKey)"MyKey2", new RedisKey(null, "MyKey2")); + } + + [Fact] + public void RedisValueConstructor() + { + Assert.Equal(default, new RedisValue()); + Assert.Equal((RedisValue)"MyKey", new RedisValue("MyKey")); + Assert.Equal((RedisValue)"MyKey2", new RedisValue("MyKey2", 0)); + } +#pragma warning restore SA1129 // Do not use default value type constructor +} diff --git a/tests/StackExchange.Redis.Tests/FailoverTests.cs b/tests/StackExchange.Redis.Tests/FailoverTests.cs new file mode 100644 index 000000000..1f33275b5 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/FailoverTests.cs @@ -0,0 +1,441 @@ +#if NET6_0_OR_GREATER +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class FailoverTests(ITestOutputHelper output) : TestBase(output), IAsyncLifetime +{ + protected override string GetConfiguration() => GetPrimaryReplicaConfig().ToString(); + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + + public async ValueTask InitializeAsync() + { + await using var conn = Create(); + + var shouldBePrimary = conn.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort); + if (shouldBePrimary.IsReplica) + { + Log(shouldBePrimary.EndPoint + " should be primary, fixing..."); + await shouldBePrimary.MakePrimaryAsync(ReplicationChangeOptions.SetTiebreaker); + } + + var shouldBeReplica = conn.GetServer(TestConfig.Current.FailoverReplicaServerAndPort); + if (!shouldBeReplica.IsReplica) + { + Log(shouldBeReplica.EndPoint + " should be a replica, fixing..."); + await shouldBeReplica.ReplicaOfAsync(shouldBePrimary.EndPoint); + await Task.Delay(2000).ForAwait(); + } + } + + private static ConfigurationOptions GetPrimaryReplicaConfig() + { + return new ConfigurationOptions + { + AllowAdmin = true, + SyncTimeout = 100000, + EndPoints = + { + { TestConfig.Current.FailoverPrimaryServer, TestConfig.Current.FailoverPrimaryPort }, + { TestConfig.Current.FailoverReplicaServer, TestConfig.Current.FailoverReplicaPort }, + }, + }; + } + + [Fact] + public async Task ConfigureAsync() + { + await using var conn = Create(); + + await Task.Delay(1000).ForAwait(); + Log("About to reconfigure....."); + await conn.ConfigureAsync().ForAwait(); + Log("Reconfigured"); + } + + [Fact] + public async Task ConfigureSync() + { + await using var conn = Create(); + + await Task.Delay(1000).ForAwait(); + Log("About to reconfigure....."); + conn.Configure(); + Log("Reconfigured"); + } + + [Fact] + public async Task ConfigVerifyReceiveConfigChangeBroadcast() + { + _ = GetConfiguration(); + await using var senderConn = Create(allowAdmin: true); + await using var receiverConn = Create(syncTimeout: 2000); + + int total = 0; + receiverConn.ConfigurationChangedBroadcast += (s, a) => + { + Log("Config changed: " + (a.EndPoint == null ? "(none)" : a.EndPoint.ToString())); + Interlocked.Increment(ref total); + }; + // send a reconfigure/reconnect message + long count = senderConn.PublishReconfigure(); + await GetServer(receiverConn).PingAsync(); + await GetServer(receiverConn).PingAsync(); + await Task.Delay(1000).ConfigureAwait(false); + Assert.True(count == -1 || count >= 2, "subscribers"); + Assert.True(Interlocked.CompareExchange(ref total, 0, 0) >= 1, "total (1st)"); + + Interlocked.Exchange(ref total, 0); + + // and send a second time via a re-primary operation + var server = GetServer(senderConn); + if (server.IsReplica) Assert.Skip("didn't expect a replica"); + await server.MakePrimaryAsync(ReplicationChangeOptions.Broadcast); + await Task.Delay(1000).ConfigureAwait(false); + await GetServer(receiverConn).PingAsync(); + await GetServer(receiverConn).PingAsync(); + Assert.True(Interlocked.CompareExchange(ref total, 0, 0) >= 1, "total (2nd)"); + } + + [Fact] + public async Task DereplicateGoesToPrimary() + { + ConfigurationOptions config = GetPrimaryReplicaConfig(); + config.ConfigCheckSeconds = 5; + + await using var conn = await ConnectionMultiplexer.ConnectAsync(config); + + var primary = conn.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort); + var secondary = conn.GetServer(TestConfig.Current.FailoverReplicaServerAndPort); + + await primary.PingAsync(); + await secondary.PingAsync(); + + await primary.MakePrimaryAsync(ReplicationChangeOptions.SetTiebreaker); + await secondary.MakePrimaryAsync(ReplicationChangeOptions.None); + + await Task.Delay(100).ConfigureAwait(false); + + await primary.PingAsync(); + await secondary.PingAsync(); + + using (var writer = new StringWriter()) + { + conn.Configure(writer); + string log = writer.ToString(); + Log(log); + bool isUnanimous = log.Contains("tie-break is unanimous at " + TestConfig.Current.FailoverPrimaryServerAndPort); + if (!isUnanimous) Assert.Skip("this is timing sensitive; unable to verify this time"); + } + + // k, so we know everyone loves 6379; is that what we get? + var db = conn.GetDatabase(); + RedisKey key = Me(); + + Assert.Equal(primary.EndPoint, db.IdentifyEndpoint(key, CommandFlags.PreferMaster)); + Assert.Equal(primary.EndPoint, db.IdentifyEndpoint(key, CommandFlags.DemandMaster)); + Assert.Equal(primary.EndPoint, db.IdentifyEndpoint(key, CommandFlags.PreferReplica)); + + var ex = Assert.Throws(() => db.IdentifyEndpoint(key, CommandFlags.DemandReplica)); + Assert.StartsWith("No connection is active/available to service this operation: EXISTS " + Me(), ex.Message); + Log("Invoking MakePrimaryAsync()..."); + await primary.MakePrimaryAsync(ReplicationChangeOptions.Broadcast | ReplicationChangeOptions.ReplicateToOtherEndpoints | ReplicationChangeOptions.SetTiebreaker, Writer); + Log("Finished MakePrimaryAsync() call."); + + await Task.Delay(100).ConfigureAwait(false); + + Log("Invoking Ping() (post-primary)"); + await primary.PingAsync(); + await secondary.PingAsync(); + Log("Finished Ping() (post-primary)"); + + Assert.True(primary.IsConnected, $"{primary.EndPoint} is not connected."); + Assert.True(secondary.IsConnected, $"{secondary.EndPoint} is not connected."); + + Log($"{primary.EndPoint}: {primary.ServerType}, Mode: {(primary.IsReplica ? "Replica" : "Primary")}"); + Log($"{secondary.EndPoint}: {secondary.ServerType}, Mode: {(secondary.IsReplica ? "Replica" : "Primary")}"); + + // Create a separate multiplexer with a valid view of the world to distinguish between failures of + // server topology changes from failures to recognize those changes + Log("Connecting to secondary validation connection."); + using (var conn2 = ConnectionMultiplexer.Connect(config)) + { + var primary2 = conn2.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort); + var secondary2 = conn2.GetServer(TestConfig.Current.FailoverReplicaServerAndPort); + + Log($"Check: {primary2.EndPoint}: {primary2.ServerType}, Mode: {(primary2.IsReplica ? "Replica" : "Primary")}"); + Log($"Check: {secondary2.EndPoint}: {secondary2.ServerType}, Mode: {(secondary2.IsReplica ? "Replica" : "Primary")}"); + + Assert.False(primary2.IsReplica, $"{primary2.EndPoint} should be a primary (verification connection)."); + Assert.True(secondary2.IsReplica, $"{secondary2.EndPoint} should be a replica (verification connection)."); + + var db2 = conn2.GetDatabase(); + + Assert.Equal(primary2.EndPoint, db2.IdentifyEndpoint(key, CommandFlags.PreferMaster)); + Assert.Equal(primary2.EndPoint, db2.IdentifyEndpoint(key, CommandFlags.DemandMaster)); + Assert.Equal(secondary2.EndPoint, db2.IdentifyEndpoint(key, CommandFlags.PreferReplica)); + Assert.Equal(secondary2.EndPoint, db2.IdentifyEndpoint(key, CommandFlags.DemandReplica)); + } + + await UntilConditionAsync(TimeSpan.FromSeconds(20), () => !primary.IsReplica && secondary.IsReplica); + + Assert.False(primary.IsReplica, $"{primary.EndPoint} should be a primary."); + Assert.True(secondary.IsReplica, $"{secondary.EndPoint} should be a replica."); + + Assert.Equal(primary.EndPoint, db.IdentifyEndpoint(key, CommandFlags.PreferMaster)); + Assert.Equal(primary.EndPoint, db.IdentifyEndpoint(key, CommandFlags.DemandMaster)); + Assert.Equal(secondary.EndPoint, db.IdentifyEndpoint(key, CommandFlags.PreferReplica)); + Assert.Equal(secondary.EndPoint, db.IdentifyEndpoint(key, CommandFlags.DemandReplica)); + } + +#if DEBUG + [Fact] + public async Task SubscriptionsSurviveConnectionFailureAsync() + { + await using var conn = Create(allowAdmin: true, shared: false, log: Writer, syncTimeout: 1000); + + var profiler = conn.AddProfiler(); + RedisChannel channel = RedisChannel.Literal(Me()); + var sub = conn.GetSubscriber(); + int counter = 0; + Assert.True(sub.IsConnected()); + await sub.SubscribeAsync(channel, (arg1, arg2) => Interlocked.Increment(ref counter)).ConfigureAwait(false); + + var profile1 = Log(profiler); + + Assert.Equal(1, conn.GetSubscriptionsCount()); + + await Task.Delay(200).ConfigureAwait(false); + + await sub.PublishAsync(channel, "abc").ConfigureAwait(false); + await sub.PingAsync(); + await Task.Delay(200).ConfigureAwait(false); + + var counter1 = Volatile.Read(ref counter); + Log($"Expecting 1 message, got {counter1}"); + Assert.Equal(1, counter1); + + var server = GetServer(conn); + var socketCount = server.GetCounters().Subscription.SocketCount; + Log($"Expecting 1 socket, got {socketCount}"); + Assert.Equal(1, socketCount); + + // We might fail both connections or just the primary in the time period + SetExpectedAmbientFailureCount(-1); + + // Make sure we fail all the way + conn.AllowConnect = false; + Log("Failing connection"); + // Fail all connections + server.SimulateConnectionFailure(SimulatedFailureType.All); + // Trigger failure (RedisTimeoutException or RedisConnectionException because + // of backlog behavior) + var ex = Assert.ThrowsAny(() => sub.Ping()); + Assert.True(ex is RedisTimeoutException or RedisConnectionException); + Assert.False(sub.IsConnected(channel)); + + // Now reconnect... + conn.AllowConnect = true; + Log("Waiting on reconnect"); + // Wait until we're reconnected + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => sub.IsConnected(channel)); + Log("Reconnected"); + // Ensure we're reconnected + Assert.True(sub.IsConnected(channel)); + + // Ensure we've sent the subscribe command after reconnecting + var profile2 = Log(profiler); + // Assert.Equal(1, profile2.Count(p => p.Command == nameof(RedisCommand.SUBSCRIBE))); + Log("Issuing ping after reconnected"); + await sub.PingAsync(); + + var muxerSubCount = conn.GetSubscriptionsCount(); + Log($"Muxer thinks we have {muxerSubCount} subscriber(s)."); + Assert.Equal(1, muxerSubCount); + + var muxerSubs = conn.GetSubscriptions(); + foreach (var pair in muxerSubs) + { + var muxerSub = pair.Value; + Log($" Muxer Sub: {pair.Key}: (EndPoint: {muxerSub.GetCurrentServer()}, Connected: {muxerSub.IsConnected})"); + } + + Log("Publishing"); + var published = await sub.PublishAsync(channel, "abc").ConfigureAwait(false); + + Log($"Published to {published} subscriber(s)."); + Assert.Equal(1, published); + + // Give it a few seconds to get our messages + Log("Waiting for 2 messages"); + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => Volatile.Read(ref counter) == 2); + + var counter2 = Volatile.Read(ref counter); + Log($"Expecting 2 messages, got {counter2}"); + Assert.Equal(2, counter2); + + // Log all commands at the end + Log("All commands since connecting:"); + var profile3 = profiler.FinishProfiling(); + foreach (var command in profile3) + { + Log($"{command.EndPoint}: {command}"); + } + } + + [Fact] + public async Task SubscriptionsSurvivePrimarySwitchAsync() + { + static void TopologyFail() => Assert.Skip("Replication topology change failed...and that's both inconsistent and not what we're testing."); + + await using var aConn = Create(allowAdmin: true, shared: false); + await using var bConn = Create(allowAdmin: true, shared: false); + + RedisChannel channel = RedisChannel.Literal(Me()); + Log("Using Channel: " + channel); + var subA = aConn.GetSubscriber(); + var subB = bConn.GetSubscriber(); + + long primaryChanged = 0, aCount = 0, bCount = 0; + aConn.ConfigurationChangedBroadcast += (s, args) => Log("A noticed config broadcast: " + Interlocked.Increment(ref primaryChanged) + " (Endpoint:" + args.EndPoint + ")"); + bConn.ConfigurationChangedBroadcast += (s, args) => Log("B noticed config broadcast: " + Interlocked.Increment(ref primaryChanged) + " (Endpoint:" + args.EndPoint + ")"); + subA.Subscribe(channel, (_, message) => + { + Log("A got message: " + message); + Interlocked.Increment(ref aCount); + }); + subB.Subscribe(channel, (_, message) => + { + Log("B got message: " + message); + Interlocked.Increment(ref bCount); + }); + + Assert.False(aConn.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort).IsReplica, $"A Connection: {TestConfig.Current.FailoverPrimaryServerAndPort} should be a primary"); + if (!aConn.GetServer(TestConfig.Current.FailoverReplicaServerAndPort).IsReplica) + { + TopologyFail(); + } + Assert.True(aConn.GetServer(TestConfig.Current.FailoverReplicaServerAndPort).IsReplica, $"A Connection: {TestConfig.Current.FailoverReplicaServerAndPort} should be a replica"); + Assert.False(bConn.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort).IsReplica, $"B Connection: {TestConfig.Current.FailoverPrimaryServerAndPort} should be a primary"); + Assert.True(bConn.GetServer(TestConfig.Current.FailoverReplicaServerAndPort).IsReplica, $"B Connection: {TestConfig.Current.FailoverReplicaServerAndPort} should be a replica"); + + Log("Failover 1 Complete"); + var epA = subA.SubscribedEndpoint(channel); + var epB = subB.SubscribedEndpoint(channel); + Log(" A: " + EndPointCollection.ToString(epA)); + Log(" B: " + EndPointCollection.ToString(epB)); + subA.Publish(channel, "A1"); + subB.Publish(channel, "B1"); + Log(" SubA ping: " + subA.Ping()); + Log(" SubB ping: " + subB.Ping()); + // If redis is under load due to this suite, it may take a moment to send across. + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => Interlocked.Read(ref aCount) == 2 && Interlocked.Read(ref bCount) == 2).ForAwait(); + + Assert.Equal(2, Interlocked.Read(ref aCount)); + Assert.Equal(2, Interlocked.Read(ref bCount)); + Assert.Equal(0, Interlocked.Read(ref primaryChanged)); + + try + { + Interlocked.Exchange(ref primaryChanged, 0); + Interlocked.Exchange(ref aCount, 0); + Interlocked.Exchange(ref bCount, 0); + Log("Changing primary..."); + using (var sw = new StringWriter()) + { + await aConn.GetServer(TestConfig.Current.FailoverReplicaServerAndPort).MakePrimaryAsync(ReplicationChangeOptions.All, sw); + Log(sw.ToString()); + } + Log("Waiting for connection B to detect..."); + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => bConn.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort).IsReplica).ForAwait(); + await subA.PingAsync(); + await subB.PingAsync(); + Log("Failover 2 Attempted. Pausing..."); + Log(" A " + TestConfig.Current.FailoverPrimaryServerAndPort + " status: " + (aConn.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort).IsReplica ? "Replica" : "Primary")); + Log(" A " + TestConfig.Current.FailoverReplicaServerAndPort + " status: " + (aConn.GetServer(TestConfig.Current.FailoverReplicaServerAndPort).IsReplica ? "Replica" : "Primary")); + Log(" B " + TestConfig.Current.FailoverPrimaryServerAndPort + " status: " + (bConn.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort).IsReplica ? "Replica" : "Primary")); + Log(" B " + TestConfig.Current.FailoverReplicaServerAndPort + " status: " + (bConn.GetServer(TestConfig.Current.FailoverReplicaServerAndPort).IsReplica ? "Replica" : "Primary")); + + if (!aConn.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort).IsReplica) + { + TopologyFail(); + } + Log("Failover 2 Complete."); + + Assert.True(aConn.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort).IsReplica, $"A Connection: {TestConfig.Current.FailoverPrimaryServerAndPort} should be a replica"); + Assert.False(aConn.GetServer(TestConfig.Current.FailoverReplicaServerAndPort).IsReplica, $"A Connection: {TestConfig.Current.FailoverReplicaServerAndPort} should be a primary"); + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => bConn.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort).IsReplica).ForAwait(); + var sanityCheck = bConn.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort).IsReplica; + if (!sanityCheck) + { + Log("FAILURE: B has not detected the topology change."); + foreach (var server in bConn.GetServerSnapshot().ToArray()) + { + Log(" Server: " + server.EndPoint); + Log(" State (Interactive): " + server.InteractiveConnectionState); + Log(" State (Subscription): " + server.SubscriptionConnectionState); + Log(" IsReplica: " + !server.IsReplica); + Log(" Type: " + server.ServerType); + } + // Assert.Skip("Not enough latency."); + } + Assert.True(sanityCheck, $"B Connection: {TestConfig.Current.FailoverPrimaryServerAndPort} should be a replica"); + Assert.False(bConn.GetServer(TestConfig.Current.FailoverReplicaServerAndPort).IsReplica, $"B Connection: {TestConfig.Current.FailoverReplicaServerAndPort} should be a primary"); + + Log("Pause complete"); + Log(" A outstanding: " + aConn.GetCounters().TotalOutstanding); + Log(" B outstanding: " + bConn.GetCounters().TotalOutstanding); + await subA.PingAsync(); + await subB.PingAsync(); + await Task.Delay(5000).ForAwait(); + epA = subA.SubscribedEndpoint(channel); + epB = subB.SubscribedEndpoint(channel); + Log("Subscription complete"); + Log(" A: " + EndPointCollection.ToString(epA)); + Log(" B: " + EndPointCollection.ToString(epB)); + var aSentTo = subA.Publish(channel, "A2"); + var bSentTo = subB.Publish(channel, "B2"); + Log(" A2 sent to: " + aSentTo); + Log(" B2 sent to: " + bSentTo); + await subA.PingAsync(); + await subB.PingAsync(); + Log("Ping Complete. Checking..."); + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => Interlocked.Read(ref aCount) == 2 && Interlocked.Read(ref bCount) == 2).ForAwait(); + + Log("Counts so far:"); + Log(" aCount: " + Interlocked.Read(ref aCount)); + Log(" bCount: " + Interlocked.Read(ref bCount)); + Log(" primaryChanged: " + Interlocked.Read(ref primaryChanged)); + + Assert.Equal(2, Interlocked.Read(ref aCount)); + Assert.Equal(2, Interlocked.Read(ref bCount)); + // Expect 12, because a sees a, but b sees a and b due to replication, but contenders may add their own + Assert.True(Interlocked.CompareExchange(ref primaryChanged, 0, 0) >= 12); + } + catch + { + Log(""); + Log("ERROR: Something went bad - see above! Roooooolling back. Back it up. Baaaaaack it on up."); + Log(""); + throw; + } + finally + { + Log("Restoring configuration..."); + try + { + await aConn.GetServer(TestConfig.Current.FailoverPrimaryServerAndPort).MakePrimaryAsync(ReplicationChangeOptions.All); + await Task.Delay(1000).ForAwait(); + } + catch { /* Don't bomb here */ } + } + } +#endif +} +#endif diff --git a/tests/StackExchange.Redis.Tests/FastHashTests.cs b/tests/StackExchange.Redis.Tests/FastHashTests.cs new file mode 100644 index 000000000..418198cfd --- /dev/null +++ b/tests/StackExchange.Redis.Tests/FastHashTests.cs @@ -0,0 +1,112 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using Xunit; + +#pragma warning disable CS8981, SA1134, SA1300, SA1303, SA1502 // names are weird in this test! +// ReSharper disable InconsistentNaming - to better represent expected literals +// ReSharper disable IdentifierTypo +namespace StackExchange.Redis.Tests; + +public partial class FastHashTests +{ + // note: if the hashing algorithm changes, we can update the last parameter freely; it doesn't matter + // what it *is* - what matters is that we can see that it has entropy between different values + [Theory] + [InlineData(1, a.Length, a.Text, a.Hash, 97)] + [InlineData(2, ab.Length, ab.Text, ab.Hash, 25185)] + [InlineData(3, abc.Length, abc.Text, abc.Hash, 6513249)] + [InlineData(4, abcd.Length, abcd.Text, abcd.Hash, 1684234849)] + [InlineData(5, abcde.Length, abcde.Text, abcde.Hash, 435475931745)] + [InlineData(6, abcdef.Length, abcdef.Text, abcdef.Hash, 112585661964897)] + [InlineData(7, abcdefg.Length, abcdefg.Text, abcdefg.Hash, 29104508263162465)] + [InlineData(8, abcdefgh.Length, abcdefgh.Text, abcdefgh.Hash, 7523094288207667809)] + + [InlineData(1, x.Length, x.Text, x.Hash, 120)] + [InlineData(2, xx.Length, xx.Text, xx.Hash, 30840)] + [InlineData(3, xxx.Length, xxx.Text, xxx.Hash, 7895160)] + [InlineData(4, xxxx.Length, xxxx.Text, xxxx.Hash, 2021161080)] + [InlineData(5, xxxxx.Length, xxxxx.Text, xxxxx.Hash, 517417236600)] + [InlineData(6, xxxxxx.Length, xxxxxx.Text, xxxxxx.Hash, 132458812569720)] + [InlineData(7, xxxxxxx.Length, xxxxxxx.Text, xxxxxxx.Hash, 33909456017848440)] + [InlineData(8, xxxxxxxx.Length, xxxxxxxx.Text, xxxxxxxx.Hash, 8680820740569200760)] + + [InlineData(3, 窓.Length, 窓.Text, 窓.Hash, 9677543, "窓")] + [InlineData(20, abcdefghijklmnopqrst.Length, abcdefghijklmnopqrst.Text, abcdefghijklmnopqrst.Hash, 7523094288207667809)] + + // show that foo_bar is interpreted as foo-bar + [InlineData(7, foo_bar.Length, foo_bar.Text, foo_bar.Hash, 32195221641981798, "foo-bar", nameof(foo_bar))] + [InlineData(7, foo_bar_hyphen.Length, foo_bar_hyphen.Text, foo_bar_hyphen.Hash, 32195221641981798, "foo-bar", nameof(foo_bar_hyphen))] + [InlineData(7, foo_bar_underscore.Length, foo_bar_underscore.Text, foo_bar_underscore.Hash, 32195222480842598, "foo_bar", nameof(foo_bar_underscore))] + public void Validate(int expectedLength, int actualLength, string actualValue, long actualHash, long expectedHash, string? expectedValue = null, string originForDisambiguation = "") + { + _ = originForDisambiguation; // to allow otherwise-identical test data to coexist + Assert.Equal(expectedLength, actualLength); + Assert.Equal(expectedHash, actualHash); + var bytes = Encoding.UTF8.GetBytes(actualValue); + Assert.Equal(expectedLength, bytes.Length); + Assert.Equal(expectedHash, FastHash.Hash64(bytes)); +#pragma warning disable CS0618 // Type or member is obsolete + Assert.Equal(expectedHash, FastHash.Hash64Fallback(bytes)); +#pragma warning restore CS0618 // Type or member is obsolete + if (expectedValue is not null) + { + Assert.Equal(expectedValue, actualValue); + } + } + + [Fact] + public void FastHashIs_Short() + { + ReadOnlySpan value = "abc"u8; + var hash = value.Hash64(); + Assert.Equal(abc.Hash, hash); + Assert.True(abc.Is(hash, value)); + + value = "abz"u8; + hash = value.Hash64(); + Assert.NotEqual(abc.Hash, hash); + Assert.False(abc.Is(hash, value)); + } + + [Fact] + public void FastHashIs_Long() + { + ReadOnlySpan value = "abcdefghijklmnopqrst"u8; + var hash = value.Hash64(); + Assert.Equal(abcdefghijklmnopqrst.Hash, hash); + Assert.True(abcdefghijklmnopqrst.Is(hash, value)); + + value = "abcdefghijklmnopqrsz"u8; + hash = value.Hash64(); + Assert.Equal(abcdefghijklmnopqrst.Hash, hash); // hash collision, fine + Assert.False(abcdefghijklmnopqrst.Is(hash, value)); + } + + [FastHash] private static partial class a { } + [FastHash] private static partial class ab { } + [FastHash] private static partial class abc { } + [FastHash] private static partial class abcd { } + [FastHash] private static partial class abcde { } + [FastHash] private static partial class abcdef { } + [FastHash] private static partial class abcdefg { } + [FastHash] private static partial class abcdefgh { } + + [FastHash] private static partial class abcdefghijklmnopqrst { } + + // show that foo_bar and foo-bar are different + [FastHash] private static partial class foo_bar { } + [FastHash("foo-bar")] private static partial class foo_bar_hyphen { } + [FastHash("foo_bar")] private static partial class foo_bar_underscore { } + + [FastHash] private static partial class 窓 { } + + [FastHash] private static partial class x { } + [FastHash] private static partial class xx { } + [FastHash] private static partial class xxx { } + [FastHash] private static partial class xxxx { } + [FastHash] private static partial class xxxxx { } + [FastHash] private static partial class xxxxxx { } + [FastHash] private static partial class xxxxxxx { } + [FastHash] private static partial class xxxxxxxx { } +} diff --git a/tests/StackExchange.Redis.Tests/FeatureFlagTests.cs b/tests/StackExchange.Redis.Tests/FeatureFlagTests.cs new file mode 100644 index 000000000..bf5aacc13 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/FeatureFlagTests.cs @@ -0,0 +1,25 @@ +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class FeatureFlagTests +{ + [Fact] + public void UnknownFlagToggle() + { + Assert.False(ConnectionMultiplexer.GetFeatureFlag("nope")); + ConnectionMultiplexer.SetFeatureFlag("nope", true); + Assert.False(ConnectionMultiplexer.GetFeatureFlag("nope")); + } + + [Fact] + public void KnownFlagToggle() + { + Assert.False(ConnectionMultiplexer.GetFeatureFlag("preventthreadtheft")); + ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", true); + Assert.True(ConnectionMultiplexer.GetFeatureFlag("preventthreadtheft")); + ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", false); + Assert.False(ConnectionMultiplexer.GetFeatureFlag("preventthreadtheft")); + } +} diff --git a/tests/StackExchange.Redis.Tests/FloatingPointTests.cs b/tests/StackExchange.Redis.Tests/FloatingPointTests.cs new file mode 100644 index 000000000..9b4b2bd7e --- /dev/null +++ b/tests/StackExchange.Redis.Tests/FloatingPointTests.cs @@ -0,0 +1,160 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class FloatingPointTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + private static bool Within(double x, double y, double delta) => Math.Abs(x - y) <= delta; + + [Fact] + public async Task IncrDecrFloatingPoint() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + double[] incr = + [ + 12.134, + -14561.0000002, + 125.3421, + -2.49892498, + ], + decr = + [ + 99.312, + 12, + -35, + ]; + double sum = 0; + foreach (var value in incr) + { + db.StringIncrement(key, value, CommandFlags.FireAndForget); + sum += value; + } + foreach (var value in decr) + { + db.StringDecrement(key, value, CommandFlags.FireAndForget); + sum -= value; + } + var val = (double)db.StringGet(key); + + Assert.True(Within(sum, val, 0.0001)); + } + + [Fact] + public async Task IncrDecrFloatingPointAsync() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + double[] incr = + [ + 12.134, + -14561.0000002, + 125.3421, + -2.49892498, + ], + decr = + [ + 99.312, + 12, + -35, + ]; + double sum = 0; + foreach (var value in incr) + { + await db.StringIncrementAsync(key, value).ForAwait(); + sum += value; + } + foreach (var value in decr) + { + await db.StringDecrementAsync(key, value).ForAwait(); + sum -= value; + } + var val = (double)await db.StringGetAsync(key).ForAwait(); + + Assert.True(Within(sum, val, 0.0001)); + } + + [Fact] + public async Task HashIncrDecrFloatingPoint() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + RedisValue field = "foo"; + db.KeyDelete(key, CommandFlags.FireAndForget); + double[] incr = + [ + 12.134, + -14561.0000002, + 125.3421, + -2.49892498, + ], + decr = + [ + 99.312, + 12, + -35, + ]; + double sum = 0; + foreach (var value in incr) + { + db.HashIncrement(key, field, value, CommandFlags.FireAndForget); + sum += value; + } + foreach (var value in decr) + { + db.HashDecrement(key, field, value, CommandFlags.FireAndForget); + sum -= value; + } + var val = (double)db.HashGet(key, field); + + Assert.True(Within(sum, val, 0.0001), $"{sum} not within 0.0001 of {val}"); + } + + [Fact] + public async Task HashIncrDecrFloatingPointAsync() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + RedisValue field = "bar"; + db.KeyDelete(key, CommandFlags.FireAndForget); + double[] incr = + [ + 12.134, + -14561.0000002, + 125.3421, + -2.49892498, + ], + decr = + [ + 99.312, + 12, + -35, + ]; + double sum = 0; + foreach (var value in incr) + { + _ = db.HashIncrementAsync(key, field, value); + sum += value; + } + foreach (var value in decr) + { + _ = db.HashDecrementAsync(key, field, value); + sum -= value; + } + var val = (double)await db.HashGetAsync(key, field).ForAwait(); + + Assert.True(Within(sum, val, 0.0001)); + } +} diff --git a/tests/StackExchange.Redis.Tests/FormatTests.cs b/tests/StackExchange.Redis.Tests/FormatTests.cs new file mode 100644 index 000000000..0054ce11d --- /dev/null +++ b/tests/StackExchange.Redis.Tests/FormatTests.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class FormatTests(ITestOutputHelper output) : TestBase(output) +{ + public static IEnumerable EndpointData() + { + // note: the 3rd arg is for formatting; null means "expect the original string" + + // DNS + yield return new object?[] { "localhost", new DnsEndPoint("localhost", 0), null }; + yield return new object?[] { "localhost:6390", new DnsEndPoint("localhost", 6390), null }; + yield return new object?[] { "bob.the.builder.com", new DnsEndPoint("bob.the.builder.com", 0), null }; + yield return new object?[] { "bob.the.builder.com:6390", new DnsEndPoint("bob.the.builder.com", 6390), null }; + // IPv4 + yield return new object?[] { "0.0.0.0", new IPEndPoint(IPAddress.Parse("0.0.0.0"), 0), null }; + yield return new object?[] { "127.0.0.1", new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0), null }; + yield return new object?[] { "127.1", new IPEndPoint(IPAddress.Parse("127.1"), 0), "127.0.0.1" }; + yield return new object?[] { "127.1:6389", new IPEndPoint(IPAddress.Parse("127.1"), 6389), "127.0.0.1:6389" }; + yield return new object?[] { "127.0.0.1:6389", new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6389), null }; + yield return new object?[] { "127.0.0.1:1", new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1), null }; + yield return new object?[] { "127.0.0.1:2", new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2), null }; + yield return new object?[] { "10.10.9.18:2", new IPEndPoint(IPAddress.Parse("10.10.9.18"), 2), null }; + // IPv6 + yield return new object?[] { "::1", new IPEndPoint(IPAddress.Parse("::1"), 0), null }; + yield return new object?[] { "::1:6379", new IPEndPoint(IPAddress.Parse("::0.1.99.121"), 0), "::0.1.99.121" }; // remember your brackets! + yield return new object?[] { "[::1]:6379", new IPEndPoint(IPAddress.Parse("::1"), 6379), null }; + yield return new object?[] { "[::1]", new IPEndPoint(IPAddress.Parse("::1"), 0), "::1" }; + yield return new object?[] { "[::1]:1000", new IPEndPoint(IPAddress.Parse("::1"), 1000), null }; + yield return new object?[] { "2001:db7:85a3:8d2:1319:8a2e:370:7348", new IPEndPoint(IPAddress.Parse("2001:db7:85a3:8d2:1319:8a2e:370:7348"), 0), null }; + yield return new object?[] { "[2001:db7:85a3:8d2:1319:8a2e:370:7348]", new IPEndPoint(IPAddress.Parse("2001:db7:85a3:8d2:1319:8a2e:370:7348"), 0), "2001:db7:85a3:8d2:1319:8a2e:370:7348" }; + yield return new object?[] { "[2001:db7:85a3:8d2:1319:8a2e:370:7348]:1000", new IPEndPoint(IPAddress.Parse("2001:db7:85a3:8d2:1319:8a2e:370:7348"), 1000), null }; + } + + [Theory] + [MemberData(nameof(EndpointData))] + public void ParseEndPoint(string data, EndPoint expected, string? expectedFormat) + { + Assert.True(Format.TryParseEndPoint(data, out var result)); + Assert.Equal(expected, result); + + // and write again + var s = Format.ToString(result); + expectedFormat ??= data; + Assert.Equal(expectedFormat, s); + } + + [Theory] + [InlineData(CommandFlags.None, "None")] +#if NETFRAMEWORK + [InlineData(CommandFlags.PreferReplica, "PreferMaster, PreferReplica")] // 2-bit flag is hit-and-miss + [InlineData(CommandFlags.DemandReplica, "PreferMaster, DemandReplica")] // 2-bit flag is hit-and-miss +#else + [InlineData(CommandFlags.PreferReplica, "PreferReplica")] // 2-bit flag is hit-and-miss + [InlineData(CommandFlags.DemandReplica, "DemandReplica")] // 2-bit flag is hit-and-miss +#endif + +#if NET8_0_OR_GREATER + [InlineData(CommandFlags.PreferReplica | CommandFlags.FireAndForget, "FireAndForget, PreferReplica")] // 2-bit flag is hit-and-miss + [InlineData(CommandFlags.DemandReplica | CommandFlags.FireAndForget, "FireAndForget, DemandReplica")] // 2-bit flag is hit-and-miss +#else + [InlineData(CommandFlags.PreferReplica | CommandFlags.FireAndForget, "PreferMaster, FireAndForget, PreferReplica")] // 2-bit flag is hit-and-miss + [InlineData(CommandFlags.DemandReplica | CommandFlags.FireAndForget, "PreferMaster, FireAndForget, DemandReplica")] // 2-bit flag is hit-and-miss +#endif + public void CommandFlagsFormatting(CommandFlags value, string expected) + { + Assert.SkipWhen(Runtime.IsMono, "Mono has different enum flag behavior"); + Assert.Equal(expected, value.ToString()); + } + + [Theory] + [InlineData(ClientType.Normal, "Normal")] + [InlineData(ClientType.Replica, "Replica")] + [InlineData(ClientType.PubSub, "PubSub")] + public void ClientTypeFormatting(ClientType value, string expected) + => Assert.Equal(expected, value.ToString()); + + [Theory] + [InlineData(ClientFlags.None, "None")] + [InlineData(ClientFlags.Replica | ClientFlags.Transaction, "Replica, Transaction")] + [InlineData(ClientFlags.Transaction | ClientFlags.ReplicaMonitor | ClientFlags.UnixDomainSocket, "ReplicaMonitor, Transaction, UnixDomainSocket")] + public void ClientFlagsFormatting(ClientFlags value, string expected) + => Assert.Equal(expected, value.ToString()); + + [Theory] + [InlineData(ReplicationChangeOptions.None, "None")] + [InlineData(ReplicationChangeOptions.ReplicateToOtherEndpoints, "ReplicateToOtherEndpoints")] + [InlineData(ReplicationChangeOptions.SetTiebreaker | ReplicationChangeOptions.ReplicateToOtherEndpoints, "SetTiebreaker, ReplicateToOtherEndpoints")] + [InlineData(ReplicationChangeOptions.Broadcast | ReplicationChangeOptions.SetTiebreaker | ReplicationChangeOptions.ReplicateToOtherEndpoints, "All")] + public void ReplicationChangeOptionsFormatting(ReplicationChangeOptions value, string expected) + => Assert.Equal(expected, value.ToString()); + + [Theory] + [InlineData(0, "0")] + [InlineData(1, "1")] + [InlineData(-1, "-1")] + [InlineData(100, "100")] + [InlineData(-100, "-100")] + [InlineData(int.MaxValue, "2147483647")] + [InlineData(int.MinValue, "-2147483648")] + public unsafe void FormatInt32(int value, string expectedValue) + { + Span dest = stackalloc byte[expectedValue.Length]; + Assert.Equal(expectedValue.Length, Format.FormatInt32(value, dest)); + fixed (byte* s = dest) + { + Assert.Equal(expectedValue, Encoding.ASCII.GetString(s, expectedValue.Length)); + } + } + + [Theory] + [InlineData(0, "0")] + [InlineData(1, "1")] + [InlineData(-1, "-1")] + [InlineData(100, "100")] + [InlineData(-100, "-100")] + [InlineData(long.MaxValue, "9223372036854775807")] + [InlineData(long.MinValue, "-9223372036854775808")] + public unsafe void FormatInt64(long value, string expectedValue) + { + Assert.Equal(expectedValue.Length, Format.MeasureInt64(value)); + Span dest = stackalloc byte[expectedValue.Length]; + Assert.Equal(expectedValue.Length, Format.FormatInt64(value, dest)); + fixed (byte* s = dest) + { + Assert.Equal(expectedValue, Encoding.ASCII.GetString(s, expectedValue.Length)); + } + } + + [Theory] + [InlineData(0, "0")] + [InlineData(1, "1")] + [InlineData(100, "100")] + [InlineData(ulong.MaxValue, "18446744073709551615")] + public unsafe void FormatUInt64(ulong value, string expectedValue) + { + Assert.Equal(expectedValue.Length, Format.MeasureUInt64(value)); + Span dest = stackalloc byte[expectedValue.Length]; + Assert.Equal(expectedValue.Length, Format.FormatUInt64(value, dest)); + fixed (byte* s = dest) + { + Assert.Equal(expectedValue, Encoding.ASCII.GetString(s, expectedValue.Length)); + } + } + + [Theory] + [InlineData(0, "0")] + [InlineData(1, "1")] + [InlineData(-1, "-1")] + [InlineData(0.5, "0.5")] + [InlineData(0.50001, "0.50000999999999995")] + [InlineData(Math.PI, "3.1415926535897931")] + [InlineData(100, "100")] + [InlineData(-100, "-100")] + [InlineData(double.MaxValue, "1.7976931348623157E+308")] + [InlineData(double.MinValue, "-1.7976931348623157E+308")] + [InlineData(double.Epsilon, "4.9406564584124654E-324")] + [InlineData(double.PositiveInfinity, "+inf")] + [InlineData(double.NegativeInfinity, "-inf")] + [InlineData(double.NaN, "NaN")] // never used in normal code + + public unsafe void FormatDouble(double value, string expectedValue) + { + Assert.Equal(expectedValue.Length, Format.MeasureDouble(value)); + Span dest = stackalloc byte[expectedValue.Length]; + Assert.Equal(expectedValue.Length, Format.FormatDouble(value, dest)); + fixed (byte* s = dest) + { + Assert.Equal(expectedValue, Encoding.ASCII.GetString(s, expectedValue.Length)); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/GarbageCollectionTests.cs b/tests/StackExchange.Redis.Tests/GarbageCollectionTests.cs new file mode 100644 index 000000000..ef28ed6e9 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/GarbageCollectionTests.cs @@ -0,0 +1,103 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] // because I need to measure some things that could get confused +public class GarbageCollectionTests(ITestOutputHelper helper) : TestBase(helper) +{ + private static void ForceGC() + { + for (int i = 0; i < 3; i++) + { + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); + GC.WaitForPendingFinalizers(); + } + } + + [Fact] + public async Task MuxerIsCollected() + { +#if DEBUG + Assert.Skip("Only predictable in release builds"); +#endif + // this is more nuanced than it looks; multiple sockets with + // async callbacks, plus a heartbeat on a timer + + // deliberately not "using" - we *want* to leak this + var conn = Create(); + await conn.GetDatabase().PingAsync(); // smoke-test + + ForceGC(); + +// #if DEBUG // this counter only exists in debug +// int before = ConnectionMultiplexer.CollectedWithoutDispose; +// #endif + var wr = new WeakReference(conn); + conn = null; + + ForceGC(); + await Task.Delay(2000).ForAwait(); // GC is twitchy + ForceGC(); + + // should be collectable + Assert.Null(wr.Target); + +// #if DEBUG // this counter only exists in debug +// int after = ConnectionMultiplexer.CollectedWithoutDispose; +// Assert.Equal(before + 1, after); +// #endif + } + + [Fact] + public async Task UnrootedBackloggedAsyncTaskIsCompletedOnTimeout() + { + Skip.UnlessLongRunning(); + // Run the test on a separate thread without keeping a reference to the task to ensure + // that there are no references to the variables in test task from the main thread. + // WithTimeout must not be used within Task.Run because timers are rooted and would keep everything alive. + var startGC = new TaskCompletionSource(); + Task? completedTestTask = null; + _ = Task.Run(async () => + { + await using var conn = await ConnectionMultiplexer.ConnectAsync( + new ConfigurationOptions() + { + BacklogPolicy = BacklogPolicy.Default, + AbortOnConnectFail = false, + ConnectTimeout = 50, + SyncTimeout = 1000, + AllowAdmin = true, + EndPoints = { GetConfiguration() }, + }, + Writer); + var db = conn.GetDatabase(); + + // Disconnect and don't allow re-connection + conn.AllowConnect = false; + var server = conn.GetServerSnapshot()[0]; + server.SimulateConnectionFailure(SimulatedFailureType.All); + Assert.False(conn.IsConnected); + + var pingTask = Assert.ThrowsAsync(() => db.PingAsync()); + startGC.SetResult(true); + await pingTask; + }).ContinueWith(testTask => Volatile.Write(ref completedTestTask, testTask)); + + // Use sync wait and sleep to ensure a more timely GC. + var timeoutTask = Task.Delay(5000); + Task.WaitAny(startGC.Task, timeoutTask); + while (Volatile.Read(ref completedTestTask) == null && !timeoutTask.IsCompleted) + { + ForceGC(); + Thread.Sleep(200); + } + + var testTask = Volatile.Read(ref completedTestTask); + if (testTask == null) Assert.Fail("Timeout."); + + await testTask; + } +} diff --git a/tests/StackExchange.Redis.Tests/GeoTests.cs b/tests/StackExchange.Redis.Tests/GeoTests.cs new file mode 100644 index 000000000..c46f65be7 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/GeoTests.cs @@ -0,0 +1,624 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class GeoTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + private static readonly GeoEntry + Palermo = new GeoEntry(13.361389, 38.115556, "Palermo"), + Catania = new GeoEntry(15.087269, 37.502669, "Catania"), + Agrigento = new GeoEntry(13.5765, 37.311, "Agrigento"), + Cefalù = new GeoEntry(14.0188, 38.0084, "Cefalù"); + + private static readonly GeoEntry[] All = [Palermo, Catania, Agrigento, Cefalù]; + + [Fact] + public async Task GeoAdd() + { + await using var conn = Create(require: RedisFeatures.v3_2_0); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + // add while not there + Assert.True(db.GeoAdd(key, Cefalù.Longitude, Cefalù.Latitude, Cefalù.Member)); + Assert.Equal(2, db.GeoAdd(key, [Palermo, Catania])); + Assert.True(db.GeoAdd(key, Agrigento)); + + // now add again + Assert.False(db.GeoAdd(key, Cefalù.Longitude, Cefalù.Latitude, Cefalù.Member)); + Assert.Equal(0, db.GeoAdd(key, [Palermo, Catania])); + Assert.False(db.GeoAdd(key, Agrigento)); + + // Validate + var pos = db.GeoPosition(key, Palermo.Member); + Assert.NotNull(pos); + Assert.Equal(Palermo.Longitude, pos!.Value.Longitude, 5); + Assert.Equal(Palermo.Latitude, pos!.Value.Latitude, 5); + } + + [Fact] + public async Task GetDistance() + { + await using var conn = Create(require: RedisFeatures.v3_2_0); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.GeoAdd(key, All, CommandFlags.FireAndForget); + var val = db.GeoDistance(key, "Palermo", "Catania", GeoUnit.Meters); + Assert.True(val.HasValue); + Assert.Equal(166274.1516, val); + + val = db.GeoDistance(key, "Palermo", "Nowhere", GeoUnit.Meters); + Assert.False(val.HasValue); + } + + [Fact] + public async Task GeoHash() + { + await using var conn = Create(require: RedisFeatures.v3_2_0); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.GeoAdd(key, All, CommandFlags.FireAndForget); + + var hashes = db.GeoHash(key, [Palermo.Member, "Nowhere", Agrigento.Member]); + Assert.NotNull(hashes); + Assert.Equal(3, hashes.Length); + Assert.Equal("sqc8b49rny0", hashes[0]); + Assert.Null(hashes[1]); + Assert.Equal("sq9skbq0760", hashes[2]); + + var hash = db.GeoHash(key, "Palermo"); + Assert.Equal("sqc8b49rny0", hash); + + hash = db.GeoHash(key, "Nowhere"); + Assert.Null(hash); + } + + [Fact] + public async Task GeoGetPosition() + { + await using var conn = Create(require: RedisFeatures.v3_2_0); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.GeoAdd(key, All, CommandFlags.FireAndForget); + + var pos = db.GeoPosition(key, Palermo.Member); + Assert.True(pos.HasValue); + Assert.Equal(Math.Round(Palermo.Longitude, 6), Math.Round(pos.Value.Longitude, 6)); + Assert.Equal(Math.Round(Palermo.Latitude, 6), Math.Round(pos.Value.Latitude, 6)); + + pos = db.GeoPosition(key, "Nowhere"); + Assert.False(pos.HasValue); + } + + [Fact] + public async Task GeoRemove() + { + await using var conn = Create(require: RedisFeatures.v3_2_0); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.GeoAdd(key, All, CommandFlags.FireAndForget); + + var pos = db.GeoPosition(key, "Palermo"); + Assert.True(pos.HasValue); + + Assert.False(db.GeoRemove(key, "Nowhere")); + Assert.True(db.GeoRemove(key, "Palermo")); + Assert.False(db.GeoRemove(key, "Palermo")); + + pos = db.GeoPosition(key, "Palermo"); + Assert.False(pos.HasValue); + } + + [Fact] + public async Task GeoRadius() + { + await using var conn = Create(require: RedisFeatures.v3_2_0); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.GeoAdd(key, All, CommandFlags.FireAndForget); + + var results = db.GeoRadius(key, Cefalù.Member, 60, GeoUnit.Miles, 2, Order.Ascending); + Assert.Equal(2, results.Length); + + Assert.Equal(results[0].Member, Cefalù.Member); + Assert.Equal(0, results[0].Distance); + var position0 = results[0].Position; + Assert.NotNull(position0); + Assert.Equal(Math.Round(position0!.Value.Longitude, 5), Math.Round(Cefalù.Position.Longitude, 5)); + Assert.Equal(Math.Round(position0!.Value.Latitude, 5), Math.Round(Cefalù.Position.Latitude, 5)); + Assert.False(results[0].Hash.HasValue); + + Assert.Equal(results[1].Member, Palermo.Member); + var distance1 = results[1].Distance; + Assert.NotNull(distance1); + Assert.Equal(Math.Round(36.5319, 6), Math.Round(distance1!.Value, 6)); + var position1 = results[1].Position; + Assert.NotNull(position1); + Assert.Equal(Math.Round(position1!.Value.Longitude, 5), Math.Round(Palermo.Position.Longitude, 5)); + Assert.Equal(Math.Round(position1!.Value.Latitude, 5), Math.Round(Palermo.Position.Latitude, 5)); + Assert.False(results[1].Hash.HasValue); + + results = db.GeoRadius(key, Cefalù.Member, 60, GeoUnit.Miles, 2, Order.Ascending, GeoRadiusOptions.None); + Assert.Equal(2, results.Length); + Assert.Equal(results[0].Member, Cefalù.Member); + Assert.False(results[0].Position.HasValue); + Assert.False(results[0].Distance.HasValue); + Assert.False(results[0].Hash.HasValue); + + Assert.Equal(results[1].Member, Palermo.Member); + Assert.False(results[1].Position.HasValue); + Assert.False(results[1].Distance.HasValue); + Assert.False(results[1].Hash.HasValue); + } + + [Fact] + public async Task GeoRadiusOverloads() + { + await using var conn = Create(require: RedisFeatures.v3_2_0); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + Assert.True(db.GeoAdd(key, -1.759925, 52.19493, "steve")); + Assert.True(db.GeoAdd(key, -3.360655, 54.66395, "dave")); + + // Invalid overload + // Since this would throw ERR could not decode requested zset member, we catch and return something more useful to the user earlier. + var ex = Assert.Throws(() => db.GeoRadius(key, -1.759925, 52.19493, GeoUnit.Miles, 500, Order.Ascending, GeoRadiusOptions.WithDistance)); + Assert.StartsWith("Member should not be a double, you likely want the GeoRadius(RedisKey, double, double, ...) overload.", ex.Message); + Assert.Equal("member", ex.ParamName); + ex = await Assert.ThrowsAsync(() => db.GeoRadiusAsync(key, -1.759925, 52.19493, GeoUnit.Miles, 500, Order.Ascending, GeoRadiusOptions.WithDistance)).ForAwait(); + Assert.StartsWith("Member should not be a double, you likely want the GeoRadius(RedisKey, double, double, ...) overload.", ex.Message); + Assert.Equal("member", ex.ParamName); + + // The good stuff + GeoRadiusResult[] result = db.GeoRadius(key, -1.759925, 52.19493, 500, unit: GeoUnit.Miles, order: Order.Ascending, options: GeoRadiusOptions.WithDistance); + Assert.NotNull(result); + + result = await db.GeoRadiusAsync(key, -1.759925, 52.19493, 500, unit: GeoUnit.Miles, order: Order.Ascending, options: GeoRadiusOptions.WithDistance).ForAwait(); + Assert.NotNull(result); + } + + private async Task GeoSearchSetupAsync(RedisKey key, IDatabase db) + { + await db.KeyDeleteAsync(key); + await db.GeoAddAsync(key, 82.6534, 27.7682, "rays"); + await db.GeoAddAsync(key, 79.3891, 43.6418, "blue jays"); + await db.GeoAddAsync(key, 76.6217, 39.2838, "orioles"); + await db.GeoAddAsync(key, 71.0927, 42.3467, "red sox"); + await db.GeoAddAsync(key, 73.9262, 40.8296, "yankees"); + } + + private void GeoSearchSetup(RedisKey key, IDatabase db) + { + db.KeyDelete(key); + db.GeoAdd(key, 82.6534, 27.7682, "rays"); + db.GeoAdd(key, 79.3891, 43.6418, "blue jays"); + db.GeoAdd(key, 76.6217, 39.2838, "orioles"); + db.GeoAdd(key, 71.0927, 42.3467, "red sox"); + db.GeoAdd(key, 73.9262, 40.8296, "yankees"); + } + + [Fact] + public async Task GeoSearchCircleMemberAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + await GeoSearchSetupAsync(key, db); + + var circle = new GeoSearchCircle(500, GeoUnit.Miles); + var res = await db.GeoSearchAsync(key, "yankees", circle); + Assert.Contains(res, x => x.Member == "yankees"); + Assert.Contains(res, x => x.Member == "red sox"); + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Contains(res, x => x.Member == "blue jays"); + Assert.NotNull(res[0].Distance); + Assert.NotNull(res[0].Position); + Assert.Null(res[0].Hash); + Assert.Equal(4, res.Length); + } + + [Fact] + public async Task GeoSearchCircleMemberAsyncOnlyHash() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + await GeoSearchSetupAsync(key, db); + + var circle = new GeoSearchCircle(500, GeoUnit.Miles); + var res = await db.GeoSearchAsync(key, "yankees", circle, options: GeoRadiusOptions.WithGeoHash); + Assert.Contains(res, x => x.Member == "yankees"); + Assert.Contains(res, x => x.Member == "red sox"); + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Contains(res, x => x.Member == "blue jays"); + Assert.Null(res[0].Distance); + Assert.Null(res[0].Position); + Assert.NotNull(res[0].Hash); + Assert.Equal(4, res.Length); + } + + [Fact] + public async Task GeoSearchCircleMemberAsyncHashAndDistance() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + await GeoSearchSetupAsync(key, db); + + var circle = new GeoSearchCircle(500, GeoUnit.Miles); + var res = await db.GeoSearchAsync(key, "yankees", circle, options: GeoRadiusOptions.WithGeoHash | GeoRadiusOptions.WithDistance); + Assert.Contains(res, x => x.Member == "yankees"); + Assert.Contains(res, x => x.Member == "red sox"); + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Contains(res, x => x.Member == "blue jays"); + Assert.NotNull(res[0].Distance); + Assert.Null(res[0].Position); + Assert.NotNull(res[0].Hash); + Assert.Equal(4, res.Length); + } + + [Fact] + public async Task GeoSearchCircleLonLatAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + await GeoSearchSetupAsync(key, db); + + var circle = new GeoSearchCircle(500, GeoUnit.Miles); + var res = await db.GeoSearchAsync(key, 73.9262, 40.8296, circle); + Assert.Contains(res, x => x.Member == "yankees"); + Assert.Contains(res, x => x.Member == "red sox"); + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Contains(res, x => x.Member == "blue jays"); + Assert.Equal(4, res.Length); + } + + [Fact] + public async Task GeoSearchCircleMember() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + GeoSearchSetup(key, db); + + var circle = new GeoSearchCircle(500 * 1609); + var res = db.GeoSearch(key, "yankees", circle); + Assert.Contains(res, x => x.Member == "yankees"); + Assert.Contains(res, x => x.Member == "red sox"); + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Contains(res, x => x.Member == "blue jays"); + Assert.Equal(4, res.Length); + } + + [Fact] + public async Task GeoSearchCircleLonLat() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + GeoSearchSetup(key, db); + + var circle = new GeoSearchCircle(500 * 5280, GeoUnit.Feet); + var res = db.GeoSearch(key, 73.9262, 40.8296, circle); + Assert.Contains(res, x => x.Member == "yankees"); + Assert.Contains(res, x => x.Member == "red sox"); + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Contains(res, x => x.Member == "blue jays"); + Assert.Equal(4, res.Length); + } + + [Fact] + public async Task GeoSearchBoxMemberAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + await GeoSearchSetupAsync(key, db); + + var box = new GeoSearchBox(500, 500, GeoUnit.Kilometers); + var res = await db.GeoSearchAsync(key, "yankees", box); + Assert.Contains(res, x => x.Member == "yankees"); + Assert.Contains(res, x => x.Member == "red sox"); + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Equal(3, res.Length); + } + + [Fact] + public async Task GeoSearchBoxLonLatAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + await GeoSearchSetupAsync(key, db); + + var box = new GeoSearchBox(500, 500, GeoUnit.Kilometers); + var res = await db.GeoSearchAsync(key, 73.9262, 40.8296, box); + Assert.Contains(res, x => x.Member == "yankees"); + Assert.Contains(res, x => x.Member == "red sox"); + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Equal(3, res.Length); + } + + [Fact] + public async Task GeoSearchBoxMember() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + GeoSearchSetup(key, db); + + var box = new GeoSearchBox(500, 500, GeoUnit.Kilometers); + var res = db.GeoSearch(key, "yankees", box); + Assert.Contains(res, x => x.Member == "yankees"); + Assert.Contains(res, x => x.Member == "red sox"); + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Equal(3, res.Length); + } + + [Fact] + public async Task GeoSearchBoxLonLat() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + GeoSearchSetup(key, db); + + var box = new GeoSearchBox(500, 500, GeoUnit.Kilometers); + var res = db.GeoSearch(key, 73.9262, 40.8296, box); + Assert.Contains(res, x => x.Member == "yankees"); + Assert.Contains(res, x => x.Member == "red sox"); + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Equal(3, res.Length); + } + + [Fact] + public async Task GeoSearchLimitCount() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + GeoSearchSetup(key, db); + + var box = new GeoSearchBox(500, 500, GeoUnit.Kilometers); + var res = db.GeoSearch(key, 73.9262, 40.8296, box, count: 2); + Assert.Contains(res, x => x.Member == "yankees"); + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Equal(2, res.Length); + } + + [Fact] + public async Task GeoSearchLimitCountMakeNoDemands() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + GeoSearchSetup(key, db); + + var box = new GeoSearchBox(500, 500, GeoUnit.Kilometers); + var res = db.GeoSearch(key, 73.9262, 40.8296, box, count: 2, demandClosest: false); + Assert.Contains(res, x => x.Member == "red sox"); // this order MIGHT not be fully deterministic, seems to work for our purposes. + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Equal(2, res.Length); + } + + [Fact] + public async Task GeoSearchBoxLonLatDescending() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + await GeoSearchSetupAsync(key, db); + + var box = new GeoSearchBox(500, 500, GeoUnit.Kilometers); + var res = await db.GeoSearchAsync(key, 73.9262, 40.8296, box, order: Order.Descending); + Assert.Contains(res, x => x.Member == "yankees"); + Assert.Contains(res, x => x.Member == "red sox"); + Assert.Contains(res, x => x.Member == "orioles"); + Assert.Equal(3, res.Length); + Assert.Equal("red sox", res[0].Member); + } + + [Fact] + public async Task GeoSearchBoxMemberAndStoreAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var me = Me(); + var db = conn.GetDatabase(); + RedisKey sourceKey = $"{me}:source"; + RedisKey destinationKey = $"{me}:destination"; + await db.KeyDeleteAsync(destinationKey); + await GeoSearchSetupAsync(sourceKey, db); + + var box = new GeoSearchBox(500, 500, GeoUnit.Kilometers); + var res = await db.GeoSearchAndStoreAsync(sourceKey, destinationKey, "yankees", box); + var set = await db.GeoSearchAsync(destinationKey, "yankees", new GeoSearchCircle(10000, GeoUnit.Miles)); + Assert.Contains(set, x => x.Member == "yankees"); + Assert.Contains(set, x => x.Member == "red sox"); + Assert.Contains(set, x => x.Member == "orioles"); + Assert.Equal(3, set.Length); + Assert.Equal(3, res); + } + + [Fact] + public async Task GeoSearchBoxLonLatAndStoreAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var me = Me(); + var db = conn.GetDatabase(); + RedisKey sourceKey = $"{me}:source"; + RedisKey destinationKey = $"{me}:destination"; + await db.KeyDeleteAsync(destinationKey); + await GeoSearchSetupAsync(sourceKey, db); + + var box = new GeoSearchBox(500, 500, GeoUnit.Kilometers); + var res = await db.GeoSearchAndStoreAsync(sourceKey, destinationKey, 73.9262, 40.8296, box); + var set = await db.GeoSearchAsync(destinationKey, "yankees", new GeoSearchCircle(10000, GeoUnit.Miles)); + Assert.Contains(set, x => x.Member == "yankees"); + Assert.Contains(set, x => x.Member == "red sox"); + Assert.Contains(set, x => x.Member == "orioles"); + Assert.Equal(3, set.Length); + Assert.Equal(3, res); + } + + [Fact] + public async Task GeoSearchCircleMemberAndStoreAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var me = Me(); + var db = conn.GetDatabase(); + RedisKey sourceKey = $"{me}:source"; + RedisKey destinationKey = $"{me}:destination"; + await db.KeyDeleteAsync(destinationKey); + await GeoSearchSetupAsync(sourceKey, db); + + var circle = new GeoSearchCircle(500, GeoUnit.Kilometers); + var res = await db.GeoSearchAndStoreAsync(sourceKey, destinationKey, "yankees", circle); + var set = await db.GeoSearchAsync(destinationKey, "yankees", new GeoSearchCircle(10000, GeoUnit.Miles)); + Assert.Contains(set, x => x.Member == "yankees"); + Assert.Contains(set, x => x.Member == "red sox"); + Assert.Contains(set, x => x.Member == "orioles"); + Assert.Equal(3, set.Length); + Assert.Equal(3, res); + } + + [Fact] + public async Task GeoSearchCircleLonLatAndStoreAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var me = Me(); + var db = conn.GetDatabase(); + RedisKey sourceKey = $"{me}:source"; + RedisKey destinationKey = $"{me}:destination"; + await db.KeyDeleteAsync(destinationKey); + await GeoSearchSetupAsync(sourceKey, db); + + var circle = new GeoSearchCircle(500, GeoUnit.Kilometers); + var res = await db.GeoSearchAndStoreAsync(sourceKey, destinationKey, 73.9262, 40.8296, circle); + var set = await db.GeoSearchAsync(destinationKey, "yankees", new GeoSearchCircle(10000, GeoUnit.Miles)); + Assert.Contains(set, x => x.Member == "yankees"); + Assert.Contains(set, x => x.Member == "red sox"); + Assert.Contains(set, x => x.Member == "orioles"); + Assert.Equal(3, set.Length); + Assert.Equal(3, res); + } + + [Fact] + public async Task GeoSearchCircleMemberAndStore() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var me = Me(); + var db = conn.GetDatabase(); + RedisKey sourceKey = $"{me}:source"; + RedisKey destinationKey = $"{me}:destination"; + db.KeyDelete(destinationKey); + GeoSearchSetup(sourceKey, db); + + var circle = new GeoSearchCircle(500, GeoUnit.Kilometers); + var res = db.GeoSearchAndStore(sourceKey, destinationKey, "yankees", circle); + var set = db.GeoSearch(destinationKey, "yankees", new GeoSearchCircle(10000, GeoUnit.Miles)); + Assert.Contains(set, x => x.Member == "yankees"); + Assert.Contains(set, x => x.Member == "red sox"); + Assert.Contains(set, x => x.Member == "orioles"); + Assert.Equal(3, set.Length); + Assert.Equal(3, res); + } + + [Fact] + public async Task GeoSearchCircleLonLatAndStore() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var me = Me(); + var db = conn.GetDatabase(); + RedisKey sourceKey = $"{me}:source"; + RedisKey destinationKey = $"{me}:destination"; + db.KeyDelete(destinationKey); + GeoSearchSetup(sourceKey, db); + + var circle = new GeoSearchCircle(500, GeoUnit.Kilometers); + var res = db.GeoSearchAndStore(sourceKey, destinationKey, 73.9262, 40.8296, circle); + var set = db.GeoSearch(destinationKey, "yankees", new GeoSearchCircle(10000, GeoUnit.Miles)); + Assert.Contains(set, x => x.Member == "yankees"); + Assert.Contains(set, x => x.Member == "red sox"); + Assert.Contains(set, x => x.Member == "orioles"); + Assert.Equal(3, set.Length); + Assert.Equal(3, res); + } + + [Fact] + public async Task GeoSearchCircleAndStoreDistOnly() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var me = Me(); + var db = conn.GetDatabase(); + RedisKey sourceKey = $"{me}:source"; + RedisKey destinationKey = $"{me}:destination"; + db.KeyDelete(destinationKey); + GeoSearchSetup(sourceKey, db); + + var circle = new GeoSearchCircle(500, GeoUnit.Kilometers); + var res = db.GeoSearchAndStore(sourceKey, destinationKey, 73.9262, 40.8296, circle, storeDistances: true); + var set = db.SortedSetRangeByRankWithScores(destinationKey); + Assert.Contains(set, x => x.Element == "yankees"); + Assert.Contains(set, x => x.Element == "red sox"); + Assert.Contains(set, x => x.Element == "orioles"); + Assert.InRange(Array.Find(set, x => x.Element == "yankees").Score, 0, .2); + Assert.InRange(Array.Find(set, x => x.Element == "orioles").Score, 286, 287); + Assert.InRange(Array.Find(set, x => x.Element == "red sox").Score, 289, 290); + Assert.Equal(3, set.Length); + Assert.Equal(3, res); + } + + [Fact] + public async Task GeoSearchBadArgs() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key); + var circle = new GeoSearchCircle(500, GeoUnit.Kilometers); + var exception = Assert.Throws(() => + db.GeoSearch(key, "irrelevant", circle, demandClosest: false)); + + Assert.Contains("demandClosest must be true if you are not limiting the count for a GEOSEARCH", exception.Message); + } +} diff --git a/tests/StackExchange.Redis.Tests/GetServerTests.cs b/tests/StackExchange.Redis.Tests/GetServerTests.cs new file mode 100644 index 000000000..50cb9e7ef --- /dev/null +++ b/tests/StackExchange.Redis.Tests/GetServerTests.cs @@ -0,0 +1,150 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public abstract class GetServerTestsBase(ITestOutputHelper output, SharedConnectionFixture fixture) + : TestBase(output, fixture) +{ + protected abstract bool IsCluster { get; } + + [Fact] + public async Task GetServersMemoization() + { + await using var conn = Create(); + + var servers0 = conn.GetServers(); + var servers1 = conn.GetServers(); + + // different array, exact same contents + Assert.NotSame(servers0, servers1); + Assert.NotEmpty(servers0); + Assert.NotNull(servers0); + Assert.NotNull(servers1); + Assert.Equal(servers0.Length, servers1.Length); + for (int i = 0; i < servers0.Length; i++) + { + Assert.Same(servers0[i], servers1[i]); + } + } + + [Fact] + public async Task GetServerByEndpointMemoization() + { + await using var conn = Create(); + var ep = conn.GetEndPoints().First(); + + IServer x = conn.GetServer(ep), y = conn.GetServer(ep); + Assert.Same(x, y); + + object asyncState = "whatever"; + x = conn.GetServer(ep, asyncState); + y = conn.GetServer(ep, asyncState); + Assert.NotSame(x, y); + } + + [Fact] + public async Task GetServerByKeyMemoization() + { + await using var conn = Create(); + RedisKey key = Me(); + string value = $"{key}:value"; + await conn.GetDatabase().StringSetAsync(key, value); + + IServer x = conn.GetServer(key), y = conn.GetServer(key); + Assert.False(y.IsReplica, "IsReplica"); + Assert.Same(x, y); + + y = conn.GetServer(key, flags: CommandFlags.DemandMaster); + Assert.Same(x, y); + + // async state demands separate instance + y = conn.GetServer(key, "async state", flags: CommandFlags.DemandMaster); + Assert.NotSame(x, y); + + // primary and replica should be different + y = conn.GetServer(key, flags: CommandFlags.DemandReplica); + Assert.NotSame(x, y); + Assert.True(y.IsReplica, "IsReplica"); + + // replica again: same + var z = conn.GetServer(key, flags: CommandFlags.DemandReplica); + Assert.Same(y, z); + + // check routed correctly + var actual = (string?)await x.ExecuteAsync(null, "get", [key], CommandFlags.NoRedirect); + Assert.Equal(value, actual); // check value against primary + + // for replica, don't check the value, because of replication delay - just: no error + _ = y.ExecuteAsync(null, "get", [key], CommandFlags.NoRedirect); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task GetServerWithDefaultKey(bool explicitNull) + { + await using var conn = Create(); + bool isCluster = conn.ServerSelectionStrategy.ServerType == ServerType.Cluster; + Assert.Equal(IsCluster, isCluster); // check our assumptions! + + // we expect explicit null and default to act the same, but: check + RedisKey key = explicitNull ? RedisKey.Null : default(RedisKey); + + IServer primary = conn.GetServer(key); + Assert.False(primary.IsReplica); + + IServer replica = conn.GetServer(key, flags: CommandFlags.DemandReplica); + Assert.True(replica.IsReplica); + + // check multiple calls + HashSet uniques = []; + for (int i = 0; i < 100; i++) + { + uniques.Add(conn.GetServer(key)); + } + + if (isCluster) + { + Assert.True(uniques.Count > 1); // should be able to get arbitrary servers + } + else + { + Assert.Single(uniques); + } + + uniques.Clear(); + for (int i = 0; i < 100; i++) + { + uniques.Add(conn.GetServer(key, flags: CommandFlags.DemandReplica)); + } + + if (isCluster) + { + Assert.True(uniques.Count > 1); // should be able to get arbitrary servers + } + else + { + Assert.Single(uniques); + } + } +} + +[RunPerProtocol] +public class GetServerTestsCluster(ITestOutputHelper output, SharedConnectionFixture fixture) : GetServerTestsBase(output, fixture) +{ + protected override string GetConfiguration() => TestConfig.Current.ClusterServersAndPorts; + + protected override bool IsCluster => true; +} + +[RunPerProtocol] +public class GetServerTestsStandalone(ITestOutputHelper output, SharedConnectionFixture fixture) : GetServerTestsBase(output, fixture) +{ + protected override string GetConfiguration() => // we want to test flags usage including replicas + TestConfig.Current.PrimaryServerAndPort + "," + TestConfig.Current.ReplicaServerAndPort; + + protected override bool IsCluster => false; +} diff --git a/tests/StackExchange.Redis.Tests/GlobalSuppressions.cs b/tests/StackExchange.Redis.Tests/GlobalSuppressions.cs new file mode 100644 index 000000000..05be64b21 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/GlobalSuppressions.cs @@ -0,0 +1,21 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Redundancy", "RCS1163:Unused parameter.", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.ConnectionFailedErrorsTests.SSLCertificateValidationError(System.Boolean)")] +[assembly: SuppressMessage("Redundancy", "RCS1163:Unused parameter.", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.PubSubTests.ExplicitPublishMode")] +[assembly: SuppressMessage("Redundancy", "RCS1163:Unused parameter.", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.SSLTests.ConnectToSSLServer(System.Boolean,System.Boolean)")] +[assembly: SuppressMessage("Redundancy", "RCS1163:Unused parameter.", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.SSLTests.ShowCertFailures(StackExchange.Redis.Tests.Helpers.TextWriterOutputHelper)~System.Net.Security.RemoteCertificateValidationCallback")] +[assembly: SuppressMessage("Usage", "xUnit1004:Test methods should not be skipped", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.ConnectionShutdownTests.ShutdownRaisesConnectionFailedAndRestore")] +[assembly: SuppressMessage("Usage", "xUnit1004:Test methods should not be skipped", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.Issues.BgSaveResponseTests.ShouldntThrowException(StackExchange.Redis.SaveType)")] +[assembly: SuppressMessage("Roslynator", "RCS1077:Optimize LINQ method call.", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.SentinelTests.PrimaryConnectTest~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Roslynator", "RCS1077:Optimize LINQ method call.", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.SentinelTests.PrimaryConnectAsyncTest~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Roslynator", "RCS1077:Optimize LINQ method call.", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.SentinelBase.WaitForReplicationAsync(StackExchange.Redis.IServer,System.Nullable{System.TimeSpan})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Roslynator", "RCS1077:Optimize LINQ method call.", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.SentinelFailoverTests.ManagedPrimaryConnectionEndToEndWithFailoverTest~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Performance", "CA1846:Prefer 'AsSpan' over 'Substring'", Justification = "Pending", Scope = "member", Target = "~M:RedisSharp.Redis.ReadData~System.Byte[]")] +[assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.NamingTests.IgnoreMethodConventions(System.Reflection.MethodInfo)~System.Boolean")] +[assembly: SuppressMessage("Roslynator", "RCS1075:Avoid empty catch clause that catches System.Exception.", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.SentinelBase.WaitForReadyAsync(System.Net.EndPoint,System.Boolean,System.Nullable{System.TimeSpan})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Roslynator", "RCS1075:Avoid empty catch clause that catches System.Exception.", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.Tests.SentinelBase.WaitForRoleAsync(StackExchange.Redis.IServer,System.String,System.Nullable{System.TimeSpan})~System.Threading.Tasks.Task")] diff --git a/tests/StackExchange.Redis.Tests/HashFieldTests.cs b/tests/StackExchange.Redis.Tests/HashFieldTests.cs new file mode 100644 index 000000000..2bb98eb85 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/HashFieldTests.cs @@ -0,0 +1,571 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +/// +/// Tests for . +/// +[RunPerProtocol] +public class HashFieldTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + private readonly DateTime nextCentury = new DateTime(2101, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private readonly TimeSpan oneYearInMs = TimeSpan.FromMilliseconds(31536000000); + + private readonly HashEntry[] entries = [new("f1", 1), new("f2", 2)]; + + private readonly RedisValue[] fields = ["f1", "f2"]; + + private readonly RedisValue[] values = [1, 2]; + + [Fact] + public void HashFieldExpire() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var fieldsResult = db.HashFieldExpire(hashKey, fields, oneYearInMs); + Assert.Equal([ExpireResult.Success, ExpireResult.Success], fieldsResult); + + fieldsResult = db.HashFieldExpire(hashKey, fields, nextCentury); + Assert.Equal([ExpireResult.Success, ExpireResult.Success,], fieldsResult); + } + + [Fact] + public void HashFieldExpireNoKey() + { + var db = Create(require: RedisFeatures.v7_4_0_rc2).GetDatabase(); + var hashKey = Me(); + + var fieldsResult = db.HashFieldExpire(hashKey, fields, oneYearInMs); + Assert.Equal([ExpireResult.NoSuchField, ExpireResult.NoSuchField], fieldsResult); + + fieldsResult = db.HashFieldExpire(hashKey, fields, nextCentury); + Assert.Equal([ExpireResult.NoSuchField, ExpireResult.NoSuchField], fieldsResult); + } + + [Fact] + public async Task HashFieldExpireAsync() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var fieldsResult = await db.HashFieldExpireAsync(hashKey, fields, oneYearInMs); + Assert.Equal([ExpireResult.Success, ExpireResult.Success], fieldsResult); + + fieldsResult = await db.HashFieldExpireAsync(hashKey, fields, nextCentury); + Assert.Equal([ExpireResult.Success, ExpireResult.Success], fieldsResult); + } + + [Fact] + public async Task HashFieldExpireAsyncNoKey() + { + var db = Create(require: RedisFeatures.v7_4_0_rc2).GetDatabase(); + var hashKey = Me(); + + var fieldsResult = await db.HashFieldExpireAsync(hashKey, fields, oneYearInMs); + Assert.Equal([ExpireResult.NoSuchField, ExpireResult.NoSuchField], fieldsResult); + + fieldsResult = await db.HashFieldExpireAsync(hashKey, fields, nextCentury); + Assert.Equal([ExpireResult.NoSuchField, ExpireResult.NoSuchField], fieldsResult); + } + + [Fact] + public void HashFieldGetExpireDateTimeIsDue() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var result = db.HashFieldExpire(hashKey, ["f1"], new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + Assert.Equal([ExpireResult.Due], result); + } + + [Fact] + public void HashFieldExpireNoField() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var result = db.HashFieldExpire(hashKey, ["nonExistingField"], oneYearInMs); + Assert.Equal([ExpireResult.NoSuchField], result); + } + + [Fact] + public void HashFieldExpireConditionsSatisfied() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.KeyDelete(hashKey); + db.HashSet(hashKey, entries); + db.HashSet(hashKey, [new("f3", 3), new("f4", 4)]); + var initialExpire = db.HashFieldExpire(hashKey, ["f2", "f3", "f4"], new DateTime(2050, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + Assert.Equal([ExpireResult.Success, ExpireResult.Success, ExpireResult.Success], initialExpire); + + var result = db.HashFieldExpire(hashKey, ["f1"], oneYearInMs, ExpireWhen.HasNoExpiry); + Assert.Equal([ExpireResult.Success], result); + + result = db.HashFieldExpire(hashKey, ["f2"], oneYearInMs, ExpireWhen.HasExpiry); + Assert.Equal([ExpireResult.Success], result); + + result = db.HashFieldExpire(hashKey, ["f3"], nextCentury, ExpireWhen.GreaterThanCurrentExpiry); + Assert.Equal([ExpireResult.Success], result); + + result = db.HashFieldExpire(hashKey, ["f4"], oneYearInMs, ExpireWhen.LessThanCurrentExpiry); + Assert.Equal([ExpireResult.Success], result); + } + + [Fact] + public void HashFieldExpireConditionsNotSatisfied() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.KeyDelete(hashKey); + db.HashSet(hashKey, entries); + db.HashSet(hashKey, [new("f3", 3), new("f4", 4)]); + var initialExpire = db.HashFieldExpire(hashKey, ["f2", "f3", "f4"], new DateTime(2050, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + Assert.Equal([ExpireResult.Success, ExpireResult.Success, ExpireResult.Success], initialExpire); + + var result = db.HashFieldExpire(hashKey, ["f1"], oneYearInMs, ExpireWhen.HasExpiry); + Assert.Equal([ExpireResult.ConditionNotMet], result); + + result = db.HashFieldExpire(hashKey, ["f2"], oneYearInMs, ExpireWhen.HasNoExpiry); + Assert.Equal([ExpireResult.ConditionNotMet], result); + + result = db.HashFieldExpire(hashKey, ["f3"], nextCentury, ExpireWhen.LessThanCurrentExpiry); + Assert.Equal([ExpireResult.ConditionNotMet], result); + + result = db.HashFieldExpire(hashKey, ["f4"], oneYearInMs, ExpireWhen.GreaterThanCurrentExpiry); + Assert.Equal([ExpireResult.ConditionNotMet], result); + } + + [Fact] + public void HashFieldGetExpireDateTime() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, nextCentury); + long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds(); + + var result = db.HashFieldGetExpireDateTime(hashKey, ["f1"]); + Assert.Equal([ms], result); + + var fieldsResult = db.HashFieldGetExpireDateTime(hashKey, fields); + Assert.Equal([ms, ms], fieldsResult); + } + + [Fact] + public void HashFieldExpireFieldNoExpireTime() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var result = db.HashFieldGetExpireDateTime(hashKey, ["f1"]); + Assert.Equal([-1L], result); + + var fieldsResult = db.HashFieldGetExpireDateTime(hashKey, fields); + Assert.Equal([-1, -1,], fieldsResult); + } + + [Fact] + public void HashFieldGetExpireDateTimeNoKey() + { + var db = Create(require: RedisFeatures.v7_4_0_rc2).GetDatabase(); + var hashKey = Me(); + + var fieldsResult = db.HashFieldGetExpireDateTime(hashKey, fields); + Assert.Equal([-2, -2,], fieldsResult); + } + + [Fact] + public void HashFieldGetExpireDateTimeNoField() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, oneYearInMs); + + var fieldsResult = db.HashFieldGetExpireDateTime(hashKey, ["notExistingField1", "notExistingField2"]); + Assert.Equal([-2, -2,], fieldsResult); + } + + [Fact] + public void HashFieldGetTimeToLive() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, oneYearInMs); + long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds(); + + var result = db.HashFieldGetTimeToLive(hashKey, ["f1"]); + Assert.NotNull(result); + Assert.True(result.Length == 1); + Assert.True(result[0] > 0); + + var fieldsResult = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.NotNull(fieldsResult); + Assert.True(fieldsResult.Length > 0); + Assert.True(fieldsResult.All(x => x > 0)); + } + + [Fact] + public void HashFieldGetTimeToLiveNoExpireTime() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var fieldsResult = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.Equal([-1, -1,], fieldsResult); + } + + [Fact] + public void HashFieldGetTimeToLiveNoKey() + { + var db = Create(require: RedisFeatures.v7_4_0_rc2).GetDatabase(); + var hashKey = Me(); + + var fieldsResult = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.Equal([-2, -2,], fieldsResult); + } + + [Fact] + public void HashFieldGetTimeToLiveNoField() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, oneYearInMs); + + var fieldsResult = db.HashFieldGetTimeToLive(hashKey, ["notExistingField1", "notExistingField2"]); + Assert.Equal([-2, -2,], fieldsResult); + } + + [Fact] + public void HashFieldPersist() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, oneYearInMs); + long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds(); + + var result = db.HashFieldPersist(hashKey, ["f1"]); + Assert.Equal([PersistResult.Success], result); + + db.HashFieldExpire(hashKey, fields, oneYearInMs); + + var fieldsResult = db.HashFieldPersist(hashKey, fields); + Assert.Equal([PersistResult.Success, PersistResult.Success], fieldsResult); + } + + [Fact] + public void HashFieldPersistNoExpireTime() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var fieldsResult = db.HashFieldPersist(hashKey, fields); + Assert.Equal([PersistResult.ConditionNotMet, PersistResult.ConditionNotMet], fieldsResult); + } + + [Fact] + public void HashFieldPersistNoKey() + { + var db = Create(require: RedisFeatures.v7_4_0_rc2).GetDatabase(); + var hashKey = Me(); + + var fieldsResult = db.HashFieldPersist(hashKey, fields); + Assert.Equal([PersistResult.NoSuchField, PersistResult.NoSuchField], fieldsResult); + } + + [Fact] + public void HashFieldPersistNoField() + { + var db = Create(require: RedisFeatures.v7_4_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, oneYearInMs); + + var fieldsResult = db.HashFieldPersist(hashKey, ["notExistingField1", "notExistingField2"]); + Assert.Equal([PersistResult.NoSuchField, PersistResult.NoSuchField], fieldsResult); + } + + [Fact] + public void HashFieldGetAndSetExpiry() + { + using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var hashKey = Me(); + + // testing with timespan + db.HashSet(hashKey, entries); + var fieldResult = db.HashFieldGetAndSetExpiry(hashKey, "f1", TimeSpan.FromHours(1)); + Assert.Equal(1, fieldResult); + var fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing with datetime + db.HashSet(hashKey, entries); + fieldResult = db.HashFieldGetAndSetExpiry(hashKey, "f1", DateTime.Now.AddMinutes(120)); + Assert.Equal(1, fieldResult); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing persist + fieldResult = db.HashFieldGetAndSetExpiry(hashKey, "f1", persist: true); + Assert.Equal(1, fieldResult); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.Equal(-1, fieldTtl); + + // testing multiple fields with timespan + db.HashSet(hashKey, entries); + var fieldResults = db.HashFieldGetAndSetExpiry(hashKey, fields, TimeSpan.FromHours(1)); + Assert.Equal(values, fieldResults); + var fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing multiple fields with datetime + db.HashSet(hashKey, entries); + fieldResults = db.HashFieldGetAndSetExpiry(hashKey, fields, DateTime.Now.AddMinutes(120)); + Assert.Equal(values, fieldResults); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing multiple fields with persist + fieldResults = db.HashFieldGetAndSetExpiry(hashKey, fields, persist: true); + Assert.Equal(values, fieldResults); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.Equal(new long[] { -1, -1 }, fieldTtls); + } + + [Fact] + public async Task HashFieldGetAndSetExpiryAsync() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var hashKey = Me(); + + // testing with timespan + db.HashSet(hashKey, entries); + var fieldResult = await db.HashFieldGetAndSetExpiryAsync(hashKey, "f1", TimeSpan.FromHours(1)); + Assert.Equal(1, fieldResult); + var fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing with datetime + db.HashSet(hashKey, entries); + fieldResult = await db.HashFieldGetAndSetExpiryAsync(hashKey, "f1", DateTime.Now.AddMinutes(120)); + Assert.Equal(1, fieldResult); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing persist + fieldResult = await db.HashFieldGetAndSetExpiryAsync(hashKey, "f1", persist: true); + Assert.Equal(1, fieldResult); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.Equal(-1, fieldTtl); + + // testing multiple fields with timespan + db.HashSet(hashKey, entries); + var fieldResults = await db.HashFieldGetAndSetExpiryAsync(hashKey, fields, TimeSpan.FromHours(1)); + Assert.Equal(values, fieldResults); + var fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing multiple fields with datetime + db.HashSet(hashKey, entries); + fieldResults = await db.HashFieldGetAndSetExpiryAsync(hashKey, fields, DateTime.Now.AddMinutes(120)); + Assert.Equal(values, fieldResults); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing multiple fields with persist + fieldResults = await db.HashFieldGetAndSetExpiryAsync(hashKey, fields, persist: true); + Assert.Equal(values, fieldResults); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.Equal(new long[] { -1, -1 }, fieldTtls); + } + + [Fact] + public void HashFieldSetAndSetExpiry() + { + using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var hashKey = Me(); + + // testing with timespan + var result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, TimeSpan.FromHours(1)); + Assert.Equal(1, result); + var fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing with datetime + result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, DateTime.Now.AddMinutes(120)); + Assert.Equal(1, result); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing with keepttl + result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, keepTtl: true); + Assert.Equal(1, result); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing multiple fields with timespan + result = db.HashFieldSetAndSetExpiry(hashKey, entries, TimeSpan.FromHours(1)); + Assert.Equal(1, result); + var fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing multiple fields with datetime + result = db.HashFieldSetAndSetExpiry(hashKey, entries, DateTime.Now.AddMinutes(120)); + Assert.Equal(1, result); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing multiple fields with keepttl + result = db.HashFieldSetAndSetExpiry(hashKey, entries, keepTtl: true); + Assert.Equal(1, result); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing with ExpireWhen.Exists + db.KeyDelete(hashKey); + result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, TimeSpan.FromHours(1), when: When.Exists); + Assert.Equal(0, result); // should not set because it doesnt exist + + // testing with ExpireWhen.NotExists + result = db.HashFieldSetAndSetExpiry(hashKey, "f1", 1, TimeSpan.FromHours(1), when: When.NotExists); + Assert.Equal(1, result); // should set because it doesnt exist + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing with ExpireWhen.GreaterThanCurrentExpiry + result = db.HashFieldSetAndSetExpiry(hashKey, "f1", -1, keepTtl: true, when: When.Exists); + Assert.Equal(1, result); // should set because it exists + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + } + + [Fact] + public async Task HashFieldSetAndSetExpiryAsync() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var hashKey = Me(); + + // testing with timespan + var result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, TimeSpan.FromHours(1)); + Assert.Equal(1, result); + var fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing with datetime + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, DateTime.Now.AddMinutes(120)); + Assert.Equal(1, result); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing with keepttl + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, keepTtl: true); + Assert.Equal(1, result); + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing multiple fields with timespan + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, entries, TimeSpan.FromHours(1)); + Assert.Equal(1, result); + var fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing multiple fields with datetime + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, entries, DateTime.Now.AddMinutes(120)); + Assert.Equal(1, result); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing multiple fields with keepttl + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, entries, keepTtl: true); + Assert.Equal(1, result); + fieldTtls = db.HashFieldGetTimeToLive(hashKey, fields); + Assert.InRange(fieldTtls[0], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + Assert.InRange(fieldTtls[1], TimeSpan.FromMinutes(119).TotalMilliseconds, TimeSpan.FromHours(2).TotalMilliseconds); + + // testing with ExpireWhen.Exists + db.KeyDelete(hashKey); + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, TimeSpan.FromHours(1), when: When.Exists); + Assert.Equal(0, result); // should not set because it doesnt exist + + // testing with ExpireWhen.NotExists + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", 1, TimeSpan.FromHours(1), when: When.NotExists); + Assert.Equal(1, result); // should set because it doesnt exist + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + + // testing with ExpireWhen.GreaterThanCurrentExpiry + result = await db.HashFieldSetAndSetExpiryAsync(hashKey, "f1", -1, keepTtl: true, when: When.Exists); + Assert.Equal(1, result); // should set because it exists + fieldTtl = db.HashFieldGetTimeToLive(hashKey, new RedisValue[] { "f1" })[0]; + Assert.InRange(fieldTtl, TimeSpan.FromMinutes(59).TotalMilliseconds, TimeSpan.FromHours(1).TotalMilliseconds); + } + [Fact] + public void HashFieldGetAndDelete() + { + using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var hashKey = Me(); + + // single field + db.HashSet(hashKey, entries); + var fieldResult = db.HashFieldGetAndDelete(hashKey, "f1"); + Assert.Equal(1, fieldResult); + Assert.False(db.HashExists(hashKey, "f1")); + + // multiple fields + db.HashSet(hashKey, entries); + var fieldResults = db.HashFieldGetAndDelete(hashKey, fields); + Assert.Equal(values, fieldResults); + Assert.False(db.HashExists(hashKey, "f1")); + Assert.False(db.HashExists(hashKey, "f2")); + } + + [Fact] + public async Task HashFieldGetAndDeleteAsync() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var hashKey = Me(); + + // single field + db.HashSet(hashKey, entries); + var fieldResult = await db.HashFieldGetAndDeleteAsync(hashKey, "f1"); + Assert.Equal(1, fieldResult); + Assert.False(db.HashExists(hashKey, "f1")); + + // multiple fields + db.HashSet(hashKey, entries); + var fieldResults = await db.HashFieldGetAndDeleteAsync(hashKey, fields); + Assert.Equal(values, fieldResults); + Assert.False(db.HashExists(hashKey, "f1")); + Assert.False(db.HashExists(hashKey, "f2")); + } +} diff --git a/tests/StackExchange.Redis.Tests/HashTests.cs b/tests/StackExchange.Redis.Tests/HashTests.cs new file mode 100644 index 000000000..af2fa11c8 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/HashTests.cs @@ -0,0 +1,769 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +/// +/// Tests for . +/// +[RunPerProtocol] +public class HashTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task TestIncrBy() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + _ = db.KeyDeleteAsync(key).ForAwait(); + + const int iterations = 100; + var aTasks = new Task[iterations]; + var bTasks = new Task[iterations]; + for (int i = 1; i < iterations + 1; i++) + { + aTasks[i - 1] = db.HashIncrementAsync(key, "a", 1); + bTasks[i - 1] = db.HashIncrementAsync(key, "b", -1); + } + await Task.WhenAll(bTasks).ForAwait(); + for (int i = 1; i < iterations + 1; i++) + { + Assert.Equal(i, aTasks[i - 1].Result); + Assert.Equal(-i, bTasks[i - 1].Result); + } + } + + [Fact] + public async Task ScanAsync() + { + await using var conn = Create(require: RedisFeatures.v2_8_0); + + var db = conn.GetDatabase(); + var key = Me(); + await db.KeyDeleteAsync(key); + for (int i = 0; i < 200; i++) + { + await db.HashSetAsync(key, "key" + i, "value " + i); + } + + int count = 0; + // works for async + await foreach (var _ in db.HashScanAsync(key, pageSize: 20)) + { + count++; + } + Assert.Equal(200, count); + + // and sync=>async (via cast) + count = 0; + await foreach (var _ in (IAsyncEnumerable)db.HashScan(key, pageSize: 20)) + { + count++; + } + Assert.Equal(200, count); + + // and sync (native) + count = 0; + foreach (var _ in db.HashScan(key, pageSize: 20)) + { + count++; + } + Assert.Equal(200, count); + + // and async=>sync (via cast) + count = 0; + foreach (var _ in (IEnumerable)db.HashScanAsync(key, pageSize: 20)) + { + count++; + } + Assert.Equal(200, count); + } + + [Fact] + public async Task Scan() + { + await using var conn = Create(require: RedisFeatures.v2_8_0); + + var db = conn.GetDatabase(); + + var key = Me(); + _ = db.KeyDeleteAsync(key); + _ = db.HashSetAsync(key, "abc", "def"); + _ = db.HashSetAsync(key, "ghi", "jkl"); + _ = db.HashSetAsync(key, "mno", "pqr"); + + var t1 = db.HashScan(key); + var t2 = db.HashScan(key, "*h*"); + var t3 = db.HashScan(key); + var t4 = db.HashScan(key, "*h*"); + + var v1 = t1.ToArray(); + var v2 = t2.ToArray(); + var v3 = t3.ToArray(); + var v4 = t4.ToArray(); + + Assert.Equal(3, v1.Length); + Assert.Single(v2); + Assert.Equal(3, v3.Length); + Assert.Single(v4); + Array.Sort(v1, (x, y) => string.Compare(x.Name, y.Name)); + Array.Sort(v2, (x, y) => string.Compare(x.Name, y.Name)); + Array.Sort(v3, (x, y) => string.Compare(x.Name, y.Name)); + Array.Sort(v4, (x, y) => string.Compare(x.Name, y.Name)); + + Assert.Equal("abc=def,ghi=jkl,mno=pqr", string.Join(",", v1.Select(pair => pair.Name + "=" + pair.Value))); + Assert.Equal("ghi=jkl", string.Join(",", v2.Select(pair => pair.Name + "=" + pair.Value))); + Assert.Equal("abc=def,ghi=jkl,mno=pqr", string.Join(",", v3.Select(pair => pair.Name + "=" + pair.Value))); + Assert.Equal("ghi=jkl", string.Join(",", v4.Select(pair => pair.Name + "=" + pair.Value))); + } + + [Fact] + public async Task ScanNoValuesAsync() + { + await using var conn = Create(require: RedisFeatures.v7_4_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + await db.KeyDeleteAsync(key); + for (int i = 0; i < 200; i++) + { + await db.HashSetAsync(key, "key" + i, "value " + i); + } + + int count = 0; + // works for async + await foreach (var _ in db.HashScanNoValuesAsync(key, pageSize: 20)) + { + count++; + } + Assert.Equal(200, count); + + // and sync=>async (via cast) + count = 0; + await foreach (var _ in (IAsyncEnumerable)db.HashScanNoValues(key, pageSize: 20)) + { + count++; + } + Assert.Equal(200, count); + + // and sync (native) + count = 0; + foreach (var _ in db.HashScanNoValues(key, pageSize: 20)) + { + count++; + } + Assert.Equal(200, count); + + // and async=>sync (via cast) + count = 0; + foreach (var _ in (IEnumerable)db.HashScanNoValuesAsync(key, pageSize: 20)) + { + count++; + } + Assert.Equal(200, count); + } + + [Fact] + public async Task ScanNoValues() + { + await using var conn = Create(require: RedisFeatures.v7_4_0_rc1); + + var db = conn.GetDatabase(); + + var key = Me(); + _ = db.KeyDeleteAsync(key); + _ = db.HashSetAsync(key, "abc", "def"); + _ = db.HashSetAsync(key, "ghi", "jkl"); + _ = db.HashSetAsync(key, "mno", "pqr"); + + var t1 = db.HashScanNoValues(key); + var t2 = db.HashScanNoValues(key, "*h*"); + var t3 = db.HashScanNoValues(key); + var t4 = db.HashScanNoValues(key, "*h*"); + + var v1 = t1.ToArray(); + var v2 = t2.ToArray(); + var v3 = t3.ToArray(); + var v4 = t4.ToArray(); + + Assert.Equal(3, v1.Length); + Assert.Single(v2); + Assert.Equal(3, v3.Length); + Assert.Single(v4); + + Array.Sort(v1); + Array.Sort(v2); + Array.Sort(v3); + Array.Sort(v4); + + Assert.Equal(new RedisValue[] { "abc", "ghi", "mno" }, v1); + Assert.Equal(new RedisValue[] { "ghi" }, v2); + Assert.Equal(new RedisValue[] { "abc", "ghi", "mno" }, v3); + Assert.Equal(new RedisValue[] { "ghi" }, v4); + } + + [Fact] + public async Task TestIncrementOnHashThatDoesntExist() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + _ = db.KeyDeleteAsync("keynotexist"); + var result1 = db.Wait(db.HashIncrementAsync("keynotexist", "fieldnotexist", 1)); + var result2 = db.Wait(db.HashIncrementAsync("keynotexist", "anotherfieldnotexist", 1)); + Assert.Equal(1, result1); + Assert.Equal(1, result2); + } + + [Fact] + public async Task TestIncrByFloat() + { + await using var conn = Create(require: RedisFeatures.v2_6_0); + + var db = conn.GetDatabase(); + var key = Me(); + _ = db.KeyDeleteAsync(key).ForAwait(); + var aTasks = new Task[1000]; + var bTasks = new Task[1000]; + for (int i = 1; i < 1001; i++) + { + aTasks[i - 1] = db.HashIncrementAsync(key, "a", 1.0); + bTasks[i - 1] = db.HashIncrementAsync(key, "b", -1.0); + } + await Task.WhenAll(bTasks).ForAwait(); + for (int i = 1; i < 1001; i++) + { + Assert.Equal(i, aTasks[i - 1].Result); + Assert.Equal(-i, bTasks[i - 1].Result); + } + } + + [Fact] + public async Task TestGetAll() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + await db.KeyDeleteAsync(key).ForAwait(); + var shouldMatch = new Dictionary(); + var random = new Random(); + + for (int i = 0; i < 1000; i++) + { + var guid = Guid.NewGuid(); + var value = random.Next(int.MaxValue); + + shouldMatch[guid] = value; + + _ = db.HashIncrementAsync(key, guid.ToString(), value); + } + + var inRedis = (await db.HashGetAllAsync(key).ForAwait()).ToDictionary( + x => Guid.Parse(x.Name!), x => int.Parse(x.Value!)); + + Assert.Equal(shouldMatch.Count, inRedis.Count); + + foreach (var k in shouldMatch.Keys) + { + Assert.Equal(shouldMatch[k], inRedis[k]); + } + } + + [Fact] + public async Task TestGet() + { + await using var conn = Create(); + + var key = Me(); + var db = conn.GetDatabase(); + var shouldMatch = new Dictionary(); + var random = new Random(); + + for (int i = 1; i < 1000; i++) + { + var guid = Guid.NewGuid(); + var value = random.Next(int.MaxValue); + + shouldMatch[guid] = value; + + _ = db.HashIncrementAsync(key, guid.ToString(), value); + } + + foreach (var k in shouldMatch.Keys) + { + var inRedis = await db.HashGetAsync(key, k.ToString()).ForAwait(); + var num = int.Parse(inRedis!); + + Assert.Equal(shouldMatch[k], num); + } + } + + /// + /// Tests for . + /// + [Fact] + public async Task TestSet() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var hashkey = Me(); + var del = db.KeyDeleteAsync(hashkey).ForAwait(); + + var val0 = db.HashGetAsync(hashkey, "field").ForAwait(); + var set0 = db.HashSetAsync(hashkey, "field", "value1").ForAwait(); + var val1 = db.HashGetAsync(hashkey, "field").ForAwait(); + var set1 = db.HashSetAsync(hashkey, "field", "value2").ForAwait(); + var val2 = db.HashGetAsync(hashkey, "field").ForAwait(); + + var set2 = db.HashSetAsync(hashkey, "field-blob", Encoding.UTF8.GetBytes("value3")).ForAwait(); + var val3 = db.HashGetAsync(hashkey, "field-blob").ForAwait(); + + var set3 = db.HashSetAsync(hashkey, "empty_type1", "").ForAwait(); + var val4 = db.HashGetAsync(hashkey, "empty_type1").ForAwait(); + var set4 = db.HashSetAsync(hashkey, "empty_type2", RedisValue.EmptyString).ForAwait(); + var val5 = db.HashGetAsync(hashkey, "empty_type2").ForAwait(); + + await del; + Assert.Null((string?)(await val0)); + Assert.True(await set0); + Assert.Equal("value1", await val1); + Assert.False(await set1); + Assert.Equal("value2", await val2); + + Assert.True(await set2); + Assert.Equal("value3", await val3); + + Assert.True(await set3); + Assert.Equal("", await val4); + Assert.True(await set4); + Assert.Equal("", await val5); + } + + /// + /// Tests for . + /// + [Fact] + public async Task TestSetNotExists() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var hashkey = Me(); + var del = db.KeyDeleteAsync(hashkey).ForAwait(); + + var val0 = db.HashGetAsync(hashkey, "field").ForAwait(); + var set0 = db.HashSetAsync(hashkey, "field", "value1", When.NotExists).ForAwait(); + var val1 = db.HashGetAsync(hashkey, "field").ForAwait(); + var set1 = db.HashSetAsync(hashkey, "field", "value2", When.NotExists).ForAwait(); + var val2 = db.HashGetAsync(hashkey, "field").ForAwait(); + + var set2 = db.HashSetAsync(hashkey, "field-blob", Encoding.UTF8.GetBytes("value3"), When.NotExists).ForAwait(); + var val3 = db.HashGetAsync(hashkey, "field-blob").ForAwait(); + var set3 = db.HashSetAsync(hashkey, "field-blob", Encoding.UTF8.GetBytes("value3"), When.NotExists).ForAwait(); + + await del; + Assert.Null((string?)(await val0)); + Assert.True(await set0); + Assert.Equal("value1", await val1); + Assert.False(await set1); + Assert.Equal("value1", await val2); + + Assert.True(await set2); + Assert.Equal("value3", await val3); + Assert.False(await set3); + } + + /// + /// Tests for . + /// + [Fact] + public async Task TestDelSingle() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var hashkey = Me(); + await db.KeyDeleteAsync(hashkey).ForAwait(); + var del0 = db.HashDeleteAsync(hashkey, "field").ForAwait(); + + await db.HashSetAsync(hashkey, "field", "value").ForAwait(); + + var del1 = db.HashDeleteAsync(hashkey, "field").ForAwait(); + var del2 = db.HashDeleteAsync(hashkey, "field").ForAwait(); + + Assert.False(await del0); + Assert.True(await del1); + Assert.False(await del2); + } + + /// + /// Tests for . + /// + [Fact] + public async Task TestDelMulti() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var hashkey = Me(); + db.HashSet(hashkey, "key1", "val1", flags: CommandFlags.FireAndForget); + db.HashSet(hashkey, "key2", "val2", flags: CommandFlags.FireAndForget); + db.HashSet(hashkey, "key3", "val3", flags: CommandFlags.FireAndForget); + + var s1 = db.HashExistsAsync(hashkey, "key1"); + var s2 = db.HashExistsAsync(hashkey, "key2"); + var s3 = db.HashExistsAsync(hashkey, "key3"); + + var removed = db.HashDeleteAsync(hashkey, ["key1", "key3"]); + + var d1 = db.HashExistsAsync(hashkey, "key1"); + var d2 = db.HashExistsAsync(hashkey, "key2"); + var d3 = db.HashExistsAsync(hashkey, "key3"); + + Assert.True(await s1); + Assert.True(await s2); + Assert.True(await s3); + + Assert.Equal(2, await removed); + + Assert.False(await d1); + Assert.True(await d2); + Assert.False(await d3); + + var removeFinal = db.HashDeleteAsync(hashkey, ["key2"]); + + Assert.Equal(0, await db.HashLengthAsync(hashkey).ForAwait()); + Assert.Equal(1, await removeFinal); + } + + /// + /// Tests for . + /// + [Fact] + public async Task TestDelMultiInsideTransaction() + { + await using var conn = Create(); + + var tran = conn.GetDatabase().CreateTransaction(); + { + var hashkey = Me(); + _ = tran.HashSetAsync(hashkey, "key1", "val1"); + _ = tran.HashSetAsync(hashkey, "key2", "val2"); + _ = tran.HashSetAsync(hashkey, "key3", "val3"); + + var s1 = tran.HashExistsAsync(hashkey, "key1"); + var s2 = tran.HashExistsAsync(hashkey, "key2"); + var s3 = tran.HashExistsAsync(hashkey, "key3"); + + var removed = tran.HashDeleteAsync(hashkey, ["key1", "key3"]); + + var d1 = tran.HashExistsAsync(hashkey, "key1"); + var d2 = tran.HashExistsAsync(hashkey, "key2"); + var d3 = tran.HashExistsAsync(hashkey, "key3"); + + tran.Execute(); + + Assert.True(await s1); + Assert.True(await s2); + Assert.True(await s3); + + Assert.Equal(2, await removed); + + Assert.False(await d1); + Assert.True(await d2); + Assert.False(await d3); + } + } + + /// + /// Tests for . + /// + [Fact] + public async Task TestExists() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var hashkey = Me(); + _ = db.KeyDeleteAsync(hashkey).ForAwait(); + var ex0 = db.HashExistsAsync(hashkey, "field").ForAwait(); + _ = db.HashSetAsync(hashkey, "field", "value").ForAwait(); + var ex1 = db.HashExistsAsync(hashkey, "field").ForAwait(); + _ = db.HashDeleteAsync(hashkey, "field").ForAwait(); + _ = db.HashExistsAsync(hashkey, "field").ForAwait(); + + Assert.False(await ex0); + Assert.True(await ex1); + Assert.False(await ex0); + } + + /// + /// Tests for . + /// + [Fact] + public async Task TestHashKeys() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var hashKey = Me(); + await db.KeyDeleteAsync(hashKey).ForAwait(); + + var keys0 = await db.HashKeysAsync(hashKey).ForAwait(); + Assert.Empty(keys0); + + await db.HashSetAsync(hashKey, "foo", "abc").ForAwait(); + await db.HashSetAsync(hashKey, "bar", "def").ForAwait(); + + var keys1 = db.HashKeysAsync(hashKey); + + var arr = await keys1; + Assert.Equal(2, arr.Length); + Assert.Equal("foo", arr[0]); + Assert.Equal("bar", arr[1]); + } + + /// + /// Tests for . + /// + [Fact] + public async Task TestHashValues() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var hashkey = Me(); + await db.KeyDeleteAsync(hashkey).ForAwait(); + + var keys0 = await db.HashValuesAsync(hashkey).ForAwait(); + + await db.HashSetAsync(hashkey, "foo", "abc").ForAwait(); + await db.HashSetAsync(hashkey, "bar", "def").ForAwait(); + + var keys1 = db.HashValuesAsync(hashkey).ForAwait(); + + Assert.Empty(keys0); + + var arr = await keys1; + Assert.Equal(2, arr.Length); + Assert.Equal("abc", Encoding.UTF8.GetString(arr[0]!)); + Assert.Equal("def", Encoding.UTF8.GetString(arr[1]!)); + } + + /// + /// Tests for . + /// + [Fact] + public async Task TestHashLength() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var hashkey = Me(); + db.KeyDelete(hashkey, CommandFlags.FireAndForget); + + var len0 = db.HashLengthAsync(hashkey); + + db.HashSet(hashkey, "foo", "abc", flags: CommandFlags.FireAndForget); + db.HashSet(hashkey, "bar", "def", flags: CommandFlags.FireAndForget); + + var len1 = db.HashLengthAsync(hashkey); + + Assert.Equal(0, await len0); + Assert.Equal(2, await len1); + } + + /// + /// Tests for . + /// + [Fact] + public async Task TestGetMulti() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var hashkey = Me(); + db.KeyDelete(hashkey, CommandFlags.FireAndForget); + + RedisValue[] fields = ["foo", "bar", "blop"]; + var arr0 = await db.HashGetAsync(hashkey, fields).ForAwait(); + + db.HashSet(hashkey, "foo", "abc", flags: CommandFlags.FireAndForget); + db.HashSet(hashkey, "bar", "def", flags: CommandFlags.FireAndForget); + + var arr1 = await db.HashGetAsync(hashkey, fields).ForAwait(); + var arr2 = await db.HashGetAsync(hashkey, fields).ForAwait(); + + Assert.Equal(3, arr0.Length); + Assert.Null((string?)arr0[0]); + Assert.Null((string?)arr0[1]); + Assert.Null((string?)arr0[2]); + + Assert.Equal(3, arr1.Length); + Assert.Equal("abc", arr1[0]); + Assert.Equal("def", arr1[1]); + Assert.Null((string?)arr1[2]); + + Assert.Equal(3, arr2.Length); + Assert.Equal("abc", arr2[0]); + Assert.Equal("def", arr2[1]); + Assert.Null((string?)arr2[2]); + } + + /// + /// Tests for . + /// + [Fact] + public async Task TestGetPairs() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var hashkey = Me(); + _ = db.KeyDeleteAsync(hashkey); + + var result0 = db.HashGetAllAsync(hashkey); + + _ = db.HashSetAsync(hashkey, "foo", "abc"); + _ = db.HashSetAsync(hashkey, "bar", "def"); + + var result1 = db.HashGetAllAsync(hashkey); + + Assert.Empty(conn.Wait(result0)); + var result = conn.Wait(result1).ToStringDictionary(); + Assert.Equal(2, result.Count); + Assert.Equal("abc", result["foo"]); + Assert.Equal("def", result["bar"]); + } + + /// + /// Tests for . + /// + [Fact] + public async Task TestSetPairs() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var hashkey = Me(); + _ = db.KeyDeleteAsync(hashkey).ForAwait(); + + var result0 = db.HashGetAllAsync(hashkey); + + var data = new[] + { + new HashEntry("foo", Encoding.UTF8.GetBytes("abc")), + new HashEntry("bar", Encoding.UTF8.GetBytes("def")), + }; + _ = db.HashSetAsync(hashkey, data).ForAwait(); + + var result1 = db.Wait(db.HashGetAllAsync(hashkey)); + + Assert.Empty(result0.Result); + var result = result1.ToStringDictionary(); + Assert.Equal(2, result.Count); + Assert.Equal("abc", result["foo"]); + Assert.Equal("def", result["bar"]); + } + + [Fact] + public async Task TestWhenAlwaysAsync() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var hashkey = Me(); + db.KeyDelete(hashkey, CommandFlags.FireAndForget); + + var result1 = await db.HashSetAsync(hashkey, "foo", "bar", When.Always, CommandFlags.None); + var result2 = await db.HashSetAsync(hashkey, "foo2", "bar", When.Always, CommandFlags.None); + var result3 = await db.HashSetAsync(hashkey, "foo", "bar", When.Always, CommandFlags.None); + var result4 = await db.HashSetAsync(hashkey, "foo", "bar2", When.Always, CommandFlags.None); + + Assert.True(result1, "Initial set key 1"); + Assert.True(result2, "Initial set key 2"); + // Fields modified *but not added* should be a zero/false. That's the behavior of HSET + Assert.False(result3, "Duplicate set key 1"); + Assert.False(result4, "Duplicate se key 1 variant"); + } + + [Fact] + public async Task HashRandomFieldAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var hashKey = Me(); + var items = new HashEntry[] { new("new york", "yankees"), new("baltimore", "orioles"), new("boston", "red sox"), new("Tampa Bay", "rays"), new("Toronto", "blue jays") }; + await db.HashSetAsync(hashKey, items); + + var singleField = await db.HashRandomFieldAsync(hashKey); + var multiFields = await db.HashRandomFieldsAsync(hashKey, 3); + var withValues = await db.HashRandomFieldsWithValuesAsync(hashKey, 3); + Assert.Equal(3, multiFields.Length); + Assert.Equal(3, withValues.Length); + Assert.Contains(items, x => x.Name == singleField); + + foreach (var field in multiFields) + { + Assert.Contains(items, x => x.Name == field); + } + + foreach (var field in withValues) + { + Assert.Contains(items, x => x.Name == field.Name); + } + } + + [Fact] + public async Task HashRandomField() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var hashKey = Me(); + var items = new HashEntry[] { new("new york", "yankees"), new("baltimore", "orioles"), new("boston", "red sox"), new("Tampa Bay", "rays"), new("Toronto", "blue jays") }; + db.HashSet(hashKey, items); + + var singleField = db.HashRandomField(hashKey); + var multiFields = db.HashRandomFields(hashKey, 3); + var withValues = db.HashRandomFieldsWithValues(hashKey, 3); + Assert.Equal(3, multiFields.Length); + Assert.Equal(3, withValues.Length); + Assert.Contains(items, x => x.Name == singleField); + + foreach (var field in multiFields) + { + Assert.Contains(items, x => x.Name == field); + } + + foreach (var field in withValues) + { + Assert.Contains(items, x => x.Name == field.Name); + } + } + + [Fact] + public async Task HashRandomFieldEmptyHash() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var hashKey = Me(); + + var singleField = db.HashRandomField(hashKey); + var multiFields = db.HashRandomFields(hashKey, 3); + var withValues = db.HashRandomFieldsWithValues(hashKey, 3); + + Assert.Equal(RedisValue.Null, singleField); + Assert.Empty(multiFields); + Assert.Empty(withValues); + } +} diff --git a/tests/StackExchange.Redis.Tests/HeartbeatTests.cs b/tests/StackExchange.Redis.Tests/HeartbeatTests.cs new file mode 100644 index 000000000..4de271f9a --- /dev/null +++ b/tests/StackExchange.Redis.Tests/HeartbeatTests.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class HeartbeatTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task TestAutomaticHeartbeat() + { + RedisValue oldTimeout = RedisValue.Null; + await using var configConn = Create(allowAdmin: true); + + try + { + configConn.GetDatabase(); + var srv = GetAnyPrimary(configConn); + oldTimeout = srv.ConfigGet("timeout")[0].Value; + Log("Old Timeout: " + oldTimeout); + srv.ConfigSet("timeout", 3); + + await using var innerConn = Create(); + var innerDb = innerConn.GetDatabase(); + await innerDb.PingAsync(); // need to wait to pick up configuration etc + + var before = innerConn.OperationCount; + + Log("sleeping to test heartbeat..."); + await Task.Delay(TimeSpan.FromSeconds(5)).ForAwait(); + + var after = innerConn.OperationCount; + Assert.True(after >= before + 1, $"after: {after}, before: {before}"); + } + finally + { + if (!oldTimeout.IsNull) + { + Log("Resetting old timeout: " + oldTimeout); + var srv = GetAnyPrimary(configConn); + srv.ConfigSet("timeout", oldTimeout); + } + } + } +} diff --git a/tests/StackExchange.Redis.Tests/Helpers/Attributes.cs b/tests/StackExchange.Redis.Tests/Helpers/Attributes.cs new file mode 100644 index 000000000..a3386e80c --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Helpers/Attributes.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Internal; +using Xunit.Sdk; +using Xunit.v3; + +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1502 // Element should not be on a single line +#pragma warning disable SA1649 // File name should match first type name +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace StackExchange.Redis.Tests; + +/// +/// Override for that truncates our DisplayName down. +/// +/// Attribute that is applied to a method to indicate that it is a fact that should +/// be run by the test runner. It can also be extended to support a customized definition +/// of a test method. +/// +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] +[XunitTestCaseDiscoverer(typeof(FactDiscoverer))] +public class FactAttribute([CallerFilePath] string? sourceFilePath = null, [CallerLineNumber] int sourceLineNumber = -1) : Xunit.FactAttribute(sourceFilePath, sourceLineNumber) { } + +/// +/// Override for that truncates our DisplayName down. +/// +/// Marks a test method as being a data theory. Data theories are tests which are +/// fed various bits of data from a data source, mapping to parameters on the test +/// method. If the data source contains multiple rows, then the test method is executed +/// multiple times (once with each data row). Data is provided by attributes which +/// derive from Xunit.Sdk.DataAttribute (notably, Xunit.InlineDataAttribute and Xunit.MemberDataAttribute). +/// +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] +[XunitTestCaseDiscoverer(typeof(TheoryDiscoverer))] +public class TheoryAttribute([CallerFilePath] string? sourceFilePath = null, [CallerLineNumber] int sourceLineNumber = -1) : Xunit.TheoryAttribute(sourceFilePath, sourceLineNumber) { } + +public class FactDiscoverer : Xunit.v3.FactDiscoverer +{ + public override ValueTask> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, IXunitTestMethod testMethod, IFactAttribute factAttribute) + => base.Discover(discoveryOptions, testMethod, factAttribute).ExpandAsync(); +} + +public class TheoryDiscoverer : Xunit.v3.TheoryDiscoverer +{ + protected override ValueTask> CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, IXunitTestMethod testMethod, ITheoryAttribute theoryAttribute, ITheoryDataRow dataRow, object?[] testMethodArguments) + => base.CreateTestCasesForDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow, testMethodArguments).ExpandAsync(); + + protected override ValueTask> CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, IXunitTestMethod testMethod, ITheoryAttribute theoryAttribute) + => base.CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute).ExpandAsync(); +} + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] +public class RunPerProtocol() : Attribute { } + +public interface IProtocolTestCase +{ + RedisProtocol Protocol { get; } +} + +public class ProtocolTestCase : XunitTestCase, IProtocolTestCase +{ + public RedisProtocol Protocol { get; private set; } + + [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] + public ProtocolTestCase() { } + + public ProtocolTestCase(XunitTestCase testCase, RedisProtocol protocol) : base( + testMethod: testCase.TestMethod, + testCaseDisplayName: $"{testCase.TestCaseDisplayName.Replace("StackExchange.Redis.Tests.", "")} ({protocol.GetString()})", + uniqueID: testCase.UniqueID + protocol.GetString(), + @explicit: testCase.Explicit, + skipExceptions: testCase.SkipExceptions, + skipReason: testCase.SkipReason, + skipType: testCase.SkipType, + skipUnless: testCase.SkipUnless, + skipWhen: testCase.SkipWhen, + traits: testCase.TestMethod.Traits.ToReadWrite(StringComparer.OrdinalIgnoreCase), + testMethodArguments: testCase.TestMethodArguments, + sourceFilePath: testCase.SourceFilePath, + sourceLineNumber: testCase.SourceLineNumber, + timeout: testCase.Timeout) + => Protocol = protocol; + + protected override void Serialize(IXunitSerializationInfo data) + { + base.Serialize(data); + data.AddValue("resp", (int)Protocol); + } + + protected override void Deserialize(IXunitSerializationInfo data) + { + base.Deserialize(data); + Protocol = (RedisProtocol)data.GetValue("resp"); + } +} + +public class ProtocolDelayEnumeratedTestCase : XunitDelayEnumeratedTheoryTestCase, IProtocolTestCase +{ + public RedisProtocol Protocol { get; private set; } + + [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] + public ProtocolDelayEnumeratedTestCase() { } + + public ProtocolDelayEnumeratedTestCase(XunitDelayEnumeratedTheoryTestCase testCase, RedisProtocol protocol) : base( + testMethod: testCase.TestMethod, + testCaseDisplayName: $"{testCase.TestCaseDisplayName.Replace("StackExchange.Redis.Tests.", "")} ({protocol.GetString()})", + uniqueID: testCase.UniqueID + protocol.GetString(), + @explicit: testCase.Explicit, + skipTestWithoutData: testCase.SkipTestWithoutData, + skipExceptions: testCase.SkipExceptions, + skipReason: testCase.SkipReason, + skipType: testCase.SkipType, + skipUnless: testCase.SkipUnless, + skipWhen: testCase.SkipWhen, + traits: testCase.TestMethod.Traits.ToReadWrite(StringComparer.OrdinalIgnoreCase), + sourceFilePath: testCase.SourceFilePath, + sourceLineNumber: testCase.SourceLineNumber, + timeout: testCase.Timeout) + => Protocol = protocol; + + protected override void Serialize(IXunitSerializationInfo data) + { + base.Serialize(data); + data.AddValue("resp", (int)Protocol); + } + + protected override void Deserialize(IXunitSerializationInfo data) + { + base.Deserialize(data); + Protocol = (RedisProtocol)data.GetValue("resp"); + } +} + +internal static class XUnitExtensions +{ + public static async ValueTask> ExpandAsync(this ValueTask> discovery) + { + static IXunitTestCase CreateTestCase(XunitTestCase tc, RedisProtocol protocol) => tc switch + { + XunitDelayEnumeratedTheoryTestCase delayed => new ProtocolDelayEnumeratedTestCase(delayed, protocol), + _ => new ProtocolTestCase(tc, protocol), + }; + var testCases = await discovery; + List result = []; + foreach (var testCase in testCases.OfType()) + { + var testMethod = testCase.TestMethod; + + if ((testMethod.Method.GetCustomAttributes(typeof(RunPerProtocol)).FirstOrDefault() + ?? testMethod.TestClass.Class.GetCustomAttributes(typeof(RunPerProtocol)).FirstOrDefault()) is RunPerProtocol) + { + result.Add(CreateTestCase(testCase, RedisProtocol.Resp2)); + result.Add(CreateTestCase(testCase, RedisProtocol.Resp3)); + } + else + { + // Default to RESP2 everywhere else + result.Add(CreateTestCase(testCase, RedisProtocol.Resp2)); + } + } + return result; + } +} + +/// +/// Supports changing culture for the duration of a single test. +/// and with another culture. +/// +/// +/// Based on: https://bartwullems.blogspot.com/2022/03/xunit-change-culture-during-your-test.html. +/// Replaces the culture and UI culture of the current thread with . +/// +/// The name of the culture. +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] +public class TestCultureAttribute(string culture) : BeforeAfterTestAttribute +{ + private readonly CultureInfo culture = new CultureInfo(culture, false); + private CultureInfo? originalCulture; + + /// + /// Stores the current and + /// and replaces them with the new cultures defined in the constructor. + /// + /// The method under test. + /// The current . + public override void Before(MethodInfo methodUnderTest, IXunitTest test) + { + originalCulture = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = culture; + CultureInfo.CurrentCulture.ClearCachedData(); + } + + /// + /// Restores the original to . + /// + /// The method under test. + /// The current . + public override void After(MethodInfo methodUnderTest, IXunitTest test) + { + if (originalCulture is not null) + { + Thread.CurrentThread.CurrentCulture = originalCulture; + CultureInfo.CurrentCulture.ClearCachedData(); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/Helpers/Extensions.cs b/tests/StackExchange.Redis.Tests/Helpers/Extensions.cs new file mode 100644 index 000000000..6f776d268 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Helpers/Extensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Xunit; + +namespace StackExchange.Redis.Tests.Helpers; + +public static class Extensions +{ + private static string VersionInfo { get; } + + static Extensions() + { + VersionInfo = $"Running under {RuntimeInformation.FrameworkDescription} ({Environment.Version})"; + } + + public static void WriteFrameworkVersion(this ITestOutputHelper output) => output.WriteLine(VersionInfo); + + public static ConfigurationOptions WithoutSubscriptions(this ConfigurationOptions options) + { + options.CommandMap = CommandMap.Create(new HashSet() { nameof(RedisCommand.SUBSCRIBE) }, available: false); + return options; + } +} diff --git a/tests/StackExchange.Redis.Tests/Helpers/NonParallelCollection.cs b/tests/StackExchange.Redis.Tests/Helpers/NonParallelCollection.cs new file mode 100644 index 000000000..ef623c337 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Helpers/NonParallelCollection.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace StackExchange.Redis.Tests; + +[CollectionDefinition(Name, DisableParallelization = true)] +public static class NonParallelCollection +{ + public const string Name = "NonParallel"; +} diff --git a/tests/StackExchange.Redis.Tests/Helpers/SharedConnectionFixture.cs b/tests/StackExchange.Redis.Tests/Helpers/SharedConnectionFixture.cs new file mode 100644 index 000000000..9656ee45b --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Helpers/SharedConnectionFixture.cs @@ -0,0 +1,290 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using StackExchange.Redis.Maintenance; +using StackExchange.Redis.Profiling; +using Xunit; + +[assembly: AssemblyFixture(typeof(StackExchange.Redis.Tests.SharedConnectionFixture))] + +namespace StackExchange.Redis.Tests; + +public class SharedConnectionFixture : IDisposable +{ + public bool IsEnabled { get; } + + private readonly ConnectionMultiplexer _actualConnection; + public string Configuration { get; } + + public SharedConnectionFixture() + { + IsEnabled = TestConfig.Current.UseSharedConnection; + Configuration = TestBase.GetDefaultConfiguration(); + _actualConnection = TestBase.CreateDefault( + output: null, + clientName: nameof(SharedConnectionFixture), + configuration: Configuration, + allowAdmin: true); + _actualConnection.InternalError += OnInternalError; + _actualConnection.ConnectionFailed += OnConnectionFailed; + } + + private NonDisposingConnection? resp2, resp3; + internal IInternalConnectionMultiplexer GetConnection(TestBase obj, RedisProtocol protocol, [CallerMemberName] string caller = "") + { + Version? require = protocol == RedisProtocol.Resp3 ? RedisFeatures.v6_0_0 : null; + lock (this) + { + ref NonDisposingConnection? field = ref protocol == RedisProtocol.Resp3 ? ref resp3 : ref resp2; + if (field is { IsConnected: false }) + { + // abandon memoized connection if disconnected + var muxer = field.UnderlyingMultiplexer; + field = null; + muxer.Dispose(); + } + return field ??= VerifyAndWrap(obj.Create(protocol: protocol, require: require, caller: caller, shared: false, allowAdmin: true), protocol); + } + + static NonDisposingConnection VerifyAndWrap(IInternalConnectionMultiplexer muxer, RedisProtocol protocol) + { + var ep = muxer.GetEndPoints().FirstOrDefault(); + Assert.NotNull(ep); + var server = muxer.GetServer(ep); + server.Ping(); + var sep = muxer.GetServerEndPoint(ep); + if (sep.Protocol is null) + { + throw new InvalidOperationException("No RESP protocol; this means no connection?"); + } + Assert.Equal(protocol, sep.Protocol); + Assert.Equal(protocol, server.Protocol); + return new NonDisposingConnection(muxer); + } + } + + internal sealed class NonDisposingConnection(IInternalConnectionMultiplexer inner) : IInternalConnectionMultiplexer + { + public IInternalConnectionMultiplexer UnderlyingConnection => _inner; + + public bool AllowConnect + { + get => _inner.AllowConnect; + set => _inner.AllowConnect = value; + } + + public bool IgnoreConnect + { + get => _inner.IgnoreConnect; + set => _inner.IgnoreConnect = value; + } + + public ServerSelectionStrategy ServerSelectionStrategy => _inner.ServerSelectionStrategy; + + public ServerEndPoint GetServerEndPoint(EndPoint endpoint) => _inner.GetServerEndPoint(endpoint); + + public ReadOnlySpan GetServerSnapshot() => _inner.GetServerSnapshot(); + + public ConnectionMultiplexer UnderlyingMultiplexer => _inner.UnderlyingMultiplexer; + + private readonly IInternalConnectionMultiplexer _inner = inner; + + public int GetSubscriptionsCount() => _inner.GetSubscriptionsCount(); + public ConcurrentDictionary GetSubscriptions() => _inner.GetSubscriptions(); + + public void AddLibraryNameSuffix(string suffix) => _inner.AddLibraryNameSuffix(suffix); + + public string ClientName => _inner.ClientName; + + public string Configuration => _inner.Configuration; + + public int TimeoutMilliseconds => _inner.TimeoutMilliseconds; + + public long OperationCount => _inner.OperationCount; + +#pragma warning disable CS0618 // Type or member is obsolete + public bool PreserveAsyncOrder { get => false; set { } } +#pragma warning restore CS0618 + + public bool IsConnected => _inner.IsConnected; + + public bool IsConnecting => _inner.IsConnecting; + + public ConfigurationOptions RawConfig => _inner.RawConfig; + + public bool IncludeDetailInExceptions { get => _inner.RawConfig.IncludeDetailInExceptions; set => _inner.RawConfig.IncludeDetailInExceptions = value; } + + public int StormLogThreshold { get => _inner.StormLogThreshold; set => _inner.StormLogThreshold = value; } + + public event EventHandler ErrorMessage + { + add => _inner.ErrorMessage += value; + remove => _inner.ErrorMessage -= value; + } + + public event EventHandler ConnectionFailed + { + add => _inner.ConnectionFailed += value; + remove => _inner.ConnectionFailed -= value; + } + + public event EventHandler InternalError + { + add => _inner.InternalError += value; + remove => _inner.InternalError -= value; + } + + public event EventHandler ConnectionRestored + { + add => _inner.ConnectionRestored += value; + remove => _inner.ConnectionRestored -= value; + } + + public event EventHandler ConfigurationChanged + { + add => _inner.ConfigurationChanged += value; + remove => _inner.ConfigurationChanged -= value; + } + + public event EventHandler ConfigurationChangedBroadcast + { + add => _inner.ConfigurationChangedBroadcast += value; + remove => _inner.ConfigurationChangedBroadcast -= value; + } + + public event EventHandler HashSlotMoved + { + add => _inner.HashSlotMoved += value; + remove => _inner.HashSlotMoved -= value; + } + + public event EventHandler ServerMaintenanceEvent + { + add => _inner.ServerMaintenanceEvent += value; + remove => _inner.ServerMaintenanceEvent -= value; + } + + public void Close(bool allowCommandsToComplete = true) => _inner.Close(allowCommandsToComplete); + + public Task CloseAsync(bool allowCommandsToComplete = true) => _inner.CloseAsync(allowCommandsToComplete); + + public bool Configure(TextWriter? log = null) => _inner.Configure(log); + + public Task ConfigureAsync(TextWriter? log = null) => _inner.ConfigureAsync(log); + + public void Dispose() { } // DO NOT call _inner.Dispose(); + + public ValueTask DisposeAsync() => default; // DO NOT call _inner.DisposeAsync(); + + public ServerCounters GetCounters() => _inner.GetCounters(); + + public IDatabase GetDatabase(int db = -1, object? asyncState = null) => _inner.GetDatabase(db, asyncState); + + public EndPoint[] GetEndPoints(bool configuredOnly = false) => _inner.GetEndPoints(configuredOnly); + + public int GetHashSlot(RedisKey key) => _inner.GetHashSlot(key); + + public IServer GetServer(string host, int port, object? asyncState = null) => _inner.GetServer(host, port, asyncState); + + public IServer GetServer(string hostAndPort, object? asyncState = null) => _inner.GetServer(hostAndPort, asyncState); + + public IServer GetServer(IPAddress host, int port) => _inner.GetServer(host, port); + + public IServer GetServer(EndPoint endpoint, object? asyncState = null) => _inner.GetServer(endpoint, asyncState); + public IServer GetServer(RedisKey key, object? asyncState = null, CommandFlags flags = CommandFlags.None) => _inner.GetServer(key, asyncState, flags); + public IServer[] GetServers() => _inner.GetServers(); + + public string GetStatus() => _inner.GetStatus(); + + public void GetStatus(TextWriter log) => _inner.GetStatus(log); + + public string? GetStormLog() => _inner.GetStormLog(); + + public ISubscriber GetSubscriber(object? asyncState = null) => _inner.GetSubscriber(asyncState); + + public int HashSlot(RedisKey key) => _inner.HashSlot(key); + + public long PublishReconfigure(CommandFlags flags = CommandFlags.None) => _inner.PublishReconfigure(flags); + + public Task PublishReconfigureAsync(CommandFlags flags = CommandFlags.None) => _inner.PublishReconfigureAsync(flags); + + public void RegisterProfiler(Func profilingSessionProvider) => _inner.RegisterProfiler(profilingSessionProvider); + + public void ResetStormLog() => _inner.ResetStormLog(); + + public void Wait(Task task) => _inner.Wait(task); + + public T Wait(Task task) => _inner.Wait(task); + + public void WaitAll(params Task[] tasks) => _inner.WaitAll(tasks); + + public void ExportConfiguration(Stream destination, ExportOptions options = ExportOptions.All) + => _inner.ExportConfiguration(destination, options); + + public override string ToString() => _inner.ToString(); + long? IInternalConnectionMultiplexer.GetConnectionId(EndPoint endPoint, ConnectionType type) + => _inner.GetConnectionId(endPoint, type); + } + + public void Dispose() + { + resp2?.UnderlyingConnection?.Dispose(); + resp3?.UnderlyingConnection?.Dispose(); + GC.SuppressFinalize(this); + } + + protected void OnInternalError(object? sender, InternalErrorEventArgs e) + { + Interlocked.Increment(ref privateFailCount); + lock (privateExceptions) + { + privateExceptions.Add(TestBase.Time() + ": Internal error: " + e.Origin + ", " + EndPointCollection.ToString(e.EndPoint) + "/" + e.ConnectionType); + } + } + protected void OnConnectionFailed(object? sender, ConnectionFailedEventArgs e) + { + Interlocked.Increment(ref privateFailCount); + lock (privateExceptions) + { + privateExceptions.Add($"{TestBase.Time()}: Connection failed ({e.FailureType}): {EndPointCollection.ToString(e.EndPoint)}/{e.ConnectionType}: {e.Exception}"); + } + } + private readonly List privateExceptions = []; + private int privateFailCount; + + public void Teardown(TextWriter output) + { + var innerPrivateFailCount = Interlocked.Exchange(ref privateFailCount, 0); + if (innerPrivateFailCount != 0) + { + lock (privateExceptions) + { + foreach (var item in privateExceptions.Take(5)) + { + TestBase.Log(output, item); + } + privateExceptions.Clear(); + } + // Assert.True(false, $"There were {privateFailCount} private ambient exceptions."); + } + + if (_actualConnection != null) + { + TestBase.Log(output, "Connection Counts: " + _actualConnection.GetCounters().ToString()); + foreach (var ep in _actualConnection.GetServerSnapshot()) + { + var interactive = ep.GetBridge(ConnectionType.Interactive); + TestBase.Log(output, $" {Format.ToString(interactive)}: {interactive?.GetStatus()}"); + + var subscription = ep.GetBridge(ConnectionType.Subscription); + TestBase.Log(output, $" {Format.ToString(subscription)}: {subscription?.GetStatus()}"); + } + } + } +} diff --git a/tests/StackExchange.Redis.Tests/Helpers/Skip.cs b/tests/StackExchange.Redis.Tests/Helpers/Skip.cs new file mode 100644 index 000000000..72d62a3dc --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Helpers/Skip.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public static class Skip +{ + public static void UnlessLongRunning() + { + Assert.SkipUnless(TestConfig.Current.RunLongRunning, "Skipping long-running test"); + } + + public static void IfNoConfig(string prop, [NotNull] string? value) + { + Assert.SkipWhen(value.IsNullOrEmpty(), $"Config.{prop} is not set, skipping test."); + } + + internal static void IfMissingDatabase(IConnectionMultiplexer conn, int dbId) + { + var dbCount = conn.GetServer(conn.GetEndPoints()[0]).DatabaseCount; + Assert.SkipWhen(dbId >= dbCount, $"Database '{dbId}' is not supported on this server."); + } +} + +public class SkipTestException(string reason) : Exception(reason) +{ + public string? MissingFeatures { get; set; } +} diff --git a/tests/StackExchange.Redis.Tests/Helpers/TestConfig.cs b/tests/StackExchange.Redis.Tests/Helpers/TestConfig.cs new file mode 100644 index 000000000..c0194d5a6 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Helpers/TestConfig.cs @@ -0,0 +1,134 @@ +using System; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Threading; +using Newtonsoft.Json; + +namespace StackExchange.Redis.Tests; + +public static class TestConfig +{ + private const string FileName = "RedisTestConfig.json"; + + public static Config Current { get; } + +#if NET + private static int _db = 17; +#else + private static int _db = 77; +#endif + public static int GetDedicatedDB(IConnectionMultiplexer? conn = null) + { + int db = Interlocked.Increment(ref _db); + if (conn != null) Skip.IfMissingDatabase(conn, db); + return db; + } + + static TestConfig() + { + Current = new Config(); + try + { + using (var stream = typeof(TestConfig).Assembly.GetManifestResourceStream("StackExchange.Redis.Tests." + FileName)) + { + if (stream != null) + { + using (var reader = new StreamReader(stream)) + { + Current = JsonConvert.DeserializeObject(reader.ReadToEnd()) ?? new Config(); + } + } + } + } + catch (Exception ex) + { + Console.WriteLine("Error Deserializing TestConfig.json: " + ex); + } + } + + public static bool IsServerRunning(string? host, int port) + { + if (host.IsNullOrEmpty()) + { + return false; + } + + try + { + using var client = new TcpClient(host, port); + return true; + } + catch (SocketException) + { + return false; + } + } + + public class Config + { + public bool UseSharedConnection { get; set; } = true; + public bool RunLongRunning { get; set; } + + public string PrimaryServer { get; set; } = "127.0.0.1"; + public int PrimaryPort { get; set; } = 6379; + public string PrimaryServerAndPort => PrimaryServer + ":" + PrimaryPort.ToString(); + + public string ReplicaServer { get; set; } = "127.0.0.1"; + public int ReplicaPort { get; set; } = 6380; + public string ReplicaServerAndPort => ReplicaServer + ":" + ReplicaPort.ToString(); + + public string SecureServer { get; set; } = "127.0.0.1"; + public int SecurePort { get; set; } = 6381; + public string SecurePassword { get; set; } = "changeme"; + public string SecureServerAndPort => SecureServer + ":" + SecurePort.ToString(); + + // Separate servers for failover tests, so they don't wreak havoc on all others + public string FailoverPrimaryServer { get; set; } = "127.0.0.1"; + public int FailoverPrimaryPort { get; set; } = 6382; + public string FailoverPrimaryServerAndPort => FailoverPrimaryServer + ":" + FailoverPrimaryPort.ToString(); + + public string FailoverReplicaServer { get; set; } = "127.0.0.1"; + public int FailoverReplicaPort { get; set; } = 6383; + public string FailoverReplicaServerAndPort => FailoverReplicaServer + ":" + FailoverReplicaPort.ToString(); + + public string IPv4Server { get; set; } = "127.0.0.1"; + public int IPv4Port { get; set; } = 6379; + public string IPv6Server { get; set; } = "::1"; + public int IPv6Port { get; set; } = 6379; + + public string RemoteServer { get; set; } = "127.0.0.1"; + public int RemotePort { get; set; } = 6379; + public string RemoteServerAndPort => RemoteServer + ":" + RemotePort.ToString(); + + public string SentinelServer { get; set; } = "127.0.0.1"; + public int SentinelPortA { get; set; } = 26379; + public int SentinelPortB { get; set; } = 26380; + public int SentinelPortC { get; set; } = 26381; + public string SentinelSeviceName { get; set; } = "myprimary"; + + public string ClusterServer { get; set; } = "127.0.0.1"; + public int ClusterStartPort { get; set; } = 7000; + public int ClusterServerCount { get; set; } = 6; + public string ClusterServersAndPorts => string.Join(",", Enumerable.Range(ClusterStartPort, ClusterServerCount).Select(port => ClusterServer + ":" + port)); + + public string? SslServer { get; set; } = "127.0.0.1"; + public int SslPort { get; set; } = 6384; + public string SslServerAndPort => SslServer + ":" + SslPort.ToString(); + + public string? RedisLabsSslServer { get; set; } + public int RedisLabsSslPort { get; set; } = 6379; + public string? RedisLabsPfxPath { get; set; } + + public string? AzureCacheServer { get; set; } + public string? AzureCachePassword { get; set; } + + public string? SSDBServer { get; set; } + public int SSDBPort { get; set; } = 8888; + + public string ProxyServer { get; set; } = "127.0.0.1"; + public int ProxyPort { get; set; } = 7015; + + public string ProxyServerAndPort => ProxyServer + ":" + ProxyPort.ToString(); + } +} diff --git a/tests/StackExchange.Redis.Tests/Helpers/TestExtensions.cs b/tests/StackExchange.Redis.Tests/Helpers/TestExtensions.cs new file mode 100644 index 000000000..aab965f98 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Helpers/TestExtensions.cs @@ -0,0 +1,35 @@ +using StackExchange.Redis.Profiling; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public static class TestExtensions +{ + public static ProfilingSession AddProfiler(this IConnectionMultiplexer mutex) + { + var session = new ProfilingSession(); + mutex.RegisterProfiler(() => session); + return session; + } + + public static RedisProtocol GetProtocol(this ITestContext context) => + context.Test?.TestCase is IProtocolTestCase protocolTestCase + ? protocolTestCase.Protocol : RedisProtocol.Resp2; + + public static bool IsResp2(this ITestContext context) => GetProtocol(context) == RedisProtocol.Resp2; + public static bool IsResp3(this ITestContext context) => GetProtocol(context) == RedisProtocol.Resp3; + + public static string KeySuffix(this ITestContext context) => GetProtocol(context) switch + { + RedisProtocol.Resp2 => "R2", + RedisProtocol.Resp3 => "R3", + _ => "", + }; + + public static string GetString(this RedisProtocol protocol) => protocol switch + { + RedisProtocol.Resp2 => "RESP2", + RedisProtocol.Resp3 => "RESP3", + _ => "UnknownProtocolFixMeeeeee", + }; +} diff --git a/tests/StackExchange.Redis.Tests/Helpers/TextWriterOutputHelper.cs b/tests/StackExchange.Redis.Tests/Helpers/TextWriterOutputHelper.cs new file mode 100644 index 000000000..e41a46670 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Helpers/TextWriterOutputHelper.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.Text; +using Xunit; + +namespace StackExchange.Redis.Tests.Helpers; + +public class TextWriterOutputHelper(ITestOutputHelper outputHelper) : TextWriter +{ + private StringBuilder Buffer { get; } = new StringBuilder(2048); + private StringBuilder? Echo { get; set; } + public override Encoding Encoding => Encoding.UTF8; + private readonly ITestOutputHelper Output = outputHelper; + + public void EchoTo(StringBuilder sb) => Echo = sb; + + public void WriteLineNoTime(string? value) + { + try + { + base.WriteLine(value); + } + catch (Exception ex) + { + Console.Write("Attempted to write: "); + Console.WriteLine(value); + Console.WriteLine(ex); + } + } + + public override void WriteLine(string? value) + { + if (value is null) + { + return; + } + + try + { + base.WriteLine(value); + } + catch (Exception ex) + { + Console.Write("Attempted to write: "); + Console.WriteLine(value); + Console.WriteLine(ex); + } + } + + public override void Write(char value) + { + if (value == '\n' || value == '\r') + { + // Ignore empty lines + if (Buffer.Length > 0) + { + FlushBuffer(); + } + } + else + { + Buffer.Append(value); + } + } + + protected override void Dispose(bool disposing) + { + if (Buffer.Length > 0) + { + FlushBuffer(); + } + base.Dispose(disposing); + } + + private void FlushBuffer() + { + var text = Buffer.ToString(); + try + { + Output.WriteLine(text); + } + catch (InvalidOperationException) + { + // Thrown when writing from a handler after a test has ended - just bail in this case + } + Echo?.AppendLine(text); + Buffer.Clear(); + } +} diff --git a/tests/StackExchange.Redis.Tests/Helpers/redis-sharp.cs b/tests/StackExchange.Redis.Tests/Helpers/redis-sharp.cs new file mode 100644 index 000000000..557562b71 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Helpers/redis-sharp.cs @@ -0,0 +1,838 @@ +// preamble: original source: https://github.com/migueldeicaza/redis-sharp/blob/master/redis-sharp.cs +// included here for performance test purposes only; this is a separate and parallel implementation + +// +// redis-sharp.cs: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010 Novell, Inc. +// +// Licensed under the same terms of Redis: new BSD license. +// +#nullable disable + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Text; + +namespace RedisSharp +{ + public class Redis(string host, int port) : IDisposable + { + private Socket socket; + private BufferedStream bstream; + + public enum KeyType + { + None, + String, + List, + Set, + } + + public class ResponseException(string code) : Exception("Response error") + { + public string Code { get; } = code; + } + + public Redis(string host) : this(host, 6379) { } + public Redis() : this("localhost", 6379) { } + + public string Host { get; } = host ?? throw new ArgumentNullException(nameof(host)); + public int Port { get; } = port; + public int RetryTimeout { get; set; } + public int RetryCount { get; set; } + public int SendTimeout { get; set; } = -1; + public string Password { get; set; } + + private int db; + public int Db + { + get => db; + set + { + db = value; + SendExpectSuccess("SELECT {0}\r\n", db); + } + } + + public string this[string key] + { + get => GetString(key); + set => Set(key, value); + } + + public void Set(string key, string value) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (value == null) + throw new ArgumentNullException(nameof(value)); + + Set(key, Encoding.UTF8.GetBytes(value)); + } + + public void Set(string key, byte[] value) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (value.Length > 1073741824) + throw new ArgumentException("value exceeds 1G", nameof(value)); + + if (!SendDataCommand(value, "SET {0} {1}\r\n", key, value.Length)) + throw new Exception("Unable to connect"); + ExpectSuccess(); + } + + public bool SetNX(string key, string value) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (value == null) + throw new ArgumentNullException(nameof(value)); + + return SetNX(key, Encoding.UTF8.GetBytes(value)); + } + + public bool SetNX(string key, byte[] value) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (value.Length > 1073741824) + throw new ArgumentException("value exceeds 1G", nameof(value)); + + return SendDataExpectInt(value, "SETNX {0} {1}\r\n", key, value.Length) > 0; + } + + public void Set(IDictionary dict) + { + Set(dict.ToDictionary(k => k.Key, v => Encoding.UTF8.GetBytes(v.Value))); + } + + public void Set(IDictionary dict) + { + if (dict == null) + throw new ArgumentNullException(nameof(dict)); + + var nl = Encoding.UTF8.GetBytes("\r\n"); + + var ms = new MemoryStream(); + foreach (var key in dict.Keys) + { + var val = dict[key]; + + var kLength = Encoding.UTF8.GetBytes("$" + key.Length + "\r\n"); + var k = Encoding.UTF8.GetBytes(key + "\r\n"); + var vLength = Encoding.UTF8.GetBytes("$" + val.Length + "\r\n"); + ms.Write(kLength, 0, kLength.Length); + ms.Write(k, 0, k.Length); + ms.Write(vLength, 0, vLength.Length); + ms.Write(val, 0, val.Length); + ms.Write(nl, 0, nl.Length); + } + + SendDataCommand(ms.ToArray(), "*" + ((dict.Count * 2) + 1) + "\r\n$4\r\nMSET\r\n"); + ExpectSuccess(); + } + + public byte[] Get(string key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return SendExpectData(null, "GET " + key + "\r\n"); + } + + public string GetString(string key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return Encoding.UTF8.GetString(Get(key)); + } + + public byte[][] Sort(SortOptions options) + { + return SendDataCommandExpectMultiBulkReply(null, options.ToCommand() + "\r\n"); + } + + public byte[] GetSet(string key, byte[] value) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (value.Length > 1073741824) + throw new ArgumentException("value exceeds 1G", nameof(value)); + + if (!SendDataCommand(value, "GETSET {0} {1}\r\n", key, value.Length)) + throw new Exception("Unable to connect"); + + return ReadData(); + } + + public string GetSet(string key, string value) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (value == null) + throw new ArgumentNullException(nameof(value)); + return Encoding.UTF8.GetString(GetSet(key, Encoding.UTF8.GetBytes(value))); + } + + private string ReadLine() + { + var sb = new StringBuilder(); + int c; + + while ((c = bstream.ReadByte()) != -1) + { + if (c == '\r') + continue; + if (c == '\n') + break; + sb.Append((char)c); + } + return sb.ToString(); + } + + private void Connect() + { + socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) + { + NoDelay = true, + SendTimeout = SendTimeout, + }; + socket.Connect(Host, Port); + if (!socket.Connected) + { + socket.Dispose(); + socket = null; + return; + } + bstream = new BufferedStream(new NetworkStream(socket), 16 * 1024); + + if (Password != null) + SendExpectSuccess("AUTH {0}\r\n", Password); + } + + private readonly byte[] endData = [(byte)'\r', (byte)'\n']; + + private bool SendDataCommand(byte[] data, string cmd, params object[] args) + { + if (socket == null) + Connect(); + if (socket == null) + return false; + + var s = args.Length > 0 ? string.Format(cmd, args) : cmd; + byte[] r = Encoding.UTF8.GetBytes(s); + try + { + Log("S: " + string.Format(cmd, args)); + socket.Send(r); + if (data != null) + { + socket.Send(data); + socket.Send(endData); + } + } + catch (SocketException) + { + // timeout; + socket.Dispose(); + socket = null; + + return false; + } + return true; + } + + private bool SendCommand(string cmd, params object[] args) + { + if (socket == null) + Connect(); + if (socket == null) + return false; + + var s = args?.Length > 0 ? string.Format(cmd, args) : cmd; + byte[] r = Encoding.UTF8.GetBytes(s); + try + { + Log("S: " + string.Format(cmd, args)); + socket.Send(r); + } + catch (SocketException) + { + // timeout; + socket.Dispose(); + socket = null; + + return false; + } + return true; + } + + [Conditional("DEBUG")] + private static void Log(string fmt, params object[] args) + { + Console.WriteLine("{0}", string.Format(fmt, args).Trim()); + } + + private void ExpectSuccess() + { + int c = bstream.ReadByte(); + if (c == -1) + throw new ResponseException("No more data"); + + var s = ReadLine(); + Log((char)c + s); + if (c == '-') + throw new ResponseException(s.StartsWith("ERR") ? s.Substring(4) : s); + } + + private void SendExpectSuccess(string cmd, params object[] args) + { + if (!SendCommand(cmd, args)) + throw new Exception("Unable to connect"); + + ExpectSuccess(); + } + + private int SendDataExpectInt(byte[] data, string cmd, params object[] args) + { + if (!SendDataCommand(data, cmd, args)) + throw new Exception("Unable to connect"); + + int c = bstream.ReadByte(); + if (c == -1) + throw new ResponseException("No more data"); + + var s = ReadLine(); + Log("R: " + s); + if (c == '-') + throw new ResponseException(s.StartsWith("ERR") ? s.Substring(4) : s); + if (c == ':') + { + if (int.TryParse(s, out int i)) + { + return i; + } + } + throw new ResponseException("Unknown reply on integer request: " + c + s); + } + + private int SendExpectInt(string cmd, params object[] args) + { + if (!SendCommand(cmd, args)) + throw new Exception("Unable to connect"); + + int c = bstream.ReadByte(); + if (c == -1) + throw new ResponseException("No more data"); + + var s = ReadLine(); + Log("R: " + s); + if (c == '-') + throw new ResponseException(s.StartsWith("ERR") ? s.Substring(4) : s); + if (c == ':') + { + if (int.TryParse(s, out int i)) + { + return i; + } + } + throw new ResponseException("Unknown reply on integer request: " + c + s); + } + + private string SendExpectString(string cmd, params object[] args) + { + if (!SendCommand(cmd, args)) + throw new Exception("Unable to connect"); + + int c = bstream.ReadByte(); + if (c == -1) + throw new ResponseException("No more data"); + + var s = ReadLine(); + Log("R: " + s); + if (c == '-') + throw new ResponseException(s.StartsWith("ERR") ? s.Substring(4) : s); + if (c == '+') + return s; + + throw new ResponseException("Unknown reply on integer request: " + c + s); + } + + // This one does not throw errors + private string SendGetString(string cmd, params object[] args) + { + if (!SendCommand(cmd, args)) + throw new Exception("Unable to connect"); + + return ReadLine(); + } + + private byte[] SendExpectData(byte[] data, string cmd, params object[] args) + { + if (!SendDataCommand(data, cmd, args)) + throw new Exception("Unable to connect"); + + return ReadData(); + } + + private byte[] ReadData() + { + string r = ReadLine(); + Log("R: {0}", r); + if (r.Length == 0) + throw new ResponseException("Zero length response"); + + char c = r[0]; + if (c == '-') + throw new ResponseException(r.StartsWith("-ERR") ? r.Substring(5) : r.Substring(1)); + + if (c == '$') + { + if (r == "$-1") + return null; + + if (int.TryParse(r.Substring(1), out int n)) + { + var retbuf = new byte[n]; + + int bytesRead = 0; + do + { + int read = bstream.Read(retbuf, bytesRead, n - bytesRead); + if (read < 1) + throw new ResponseException("Invalid termination mid stream"); + bytesRead += read; + } + while (bytesRead < n); + if (bstream.ReadByte() != '\r' || bstream.ReadByte() != '\n') + throw new ResponseException("Invalid termination"); + return retbuf; + } + throw new ResponseException("Invalid length"); + } + + // returns the number of matches + if (c == '*') + { + if (int.TryParse(r.Substring(1), out int n)) + return n <= 0 ? Array.Empty() : ReadData(); + + throw new ResponseException("Unexpected length parameter" + r); + } + + throw new ResponseException("Unexpected reply: " + r); + } + + public bool ContainsKey(string key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return SendExpectInt("EXISTS " + key + "\r\n") == 1; + } + + public bool Remove(string key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return SendExpectInt("DEL " + key + "\r\n", key) == 1; + } + + public int Remove(params string[] args) + { + if (args == null) + throw new ArgumentNullException(nameof(args)); + return SendExpectInt("DEL " + string.Join(" ", args) + "\r\n"); + } + + public int Increment(string key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return SendExpectInt("INCR " + key + "\r\n"); + } + + public int Increment(string key, int count) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return SendExpectInt("INCRBY {0} {1}\r\n", key, count); + } + + public int Decrement(string key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return SendExpectInt("DECR " + key + "\r\n"); + } + + public int Decrement(string key, int count) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return SendExpectInt("DECRBY {0} {1}\r\n", key, count); + } + + public KeyType TypeOf(string key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return SendExpectString("TYPE {0}\r\n", key) switch + { + "none" => KeyType.None, + "string" => KeyType.String, + "set" => KeyType.Set, + "list" => KeyType.List, + _ => throw new ResponseException("Invalid value"), + }; + } + + public string RandomKey() + { + return SendExpectString("RANDOMKEY\r\n"); + } + + public bool Rename(string oldKeyname, string newKeyname) + { + if (oldKeyname == null) + throw new ArgumentNullException(nameof(oldKeyname)); + if (newKeyname == null) + throw new ArgumentNullException(nameof(newKeyname)); + return SendGetString("RENAME {0} {1}\r\n", oldKeyname, newKeyname)[0] == '+'; + } + + public bool Expire(string key, int seconds) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return SendExpectInt("EXPIRE {0} {1}\r\n", key, seconds) == 1; + } + + public bool ExpireAt(string key, int time) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return SendExpectInt("EXPIREAT {0} {1}\r\n", key, time) == 1; + } + + public int TimeToLive(string key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return SendExpectInt("TTL {0}\r\n", key); + } + + public int DbSize => SendExpectInt("DBSIZE\r\n"); + + public string Save() + { + return SendGetString("SAVE\r\n"); + } + + public void BackgroundSave() + { + SendGetString("BGSAVE\r\n"); + } + + public void Shutdown() + { + SendGetString("SHUTDOWN\r\n"); + } + + public void FlushAll() + { + SendGetString("FLUSHALL\r\n"); + } + + public void FlushDb() + { + SendGetString("FLUSHDB\r\n"); + } + + private const long UnixEpoch = 621355968000000000L; + + public DateTime LastSave + { + get + { + int t = SendExpectInt("LASTSAVE\r\n"); + + return new DateTime(UnixEpoch) + TimeSpan.FromSeconds(t); + } + } + + public Dictionary GetInfo() + { + byte[] r = SendExpectData(null, "INFO\r\n"); + var dict = new Dictionary(); + + foreach (var line in Encoding.UTF8.GetString(r).Split('\n')) + { + int p = line.IndexOf(':'); + if (p == -1) + continue; + dict.Add(line.Substring(0, p), line.Substring(p + 1)); + } + return dict; + } + + public string[] Keys + { + get + { + string commandResponse = Encoding.UTF8.GetString(SendExpectData(null, "KEYS *\r\n")); + if (commandResponse.Length < 1) + return Array.Empty(); + else + return commandResponse.Split(' '); + } + } + + public string[] GetKeys(string pattern) + { + if (pattern == null) + throw new ArgumentNullException(nameof(pattern)); + var keys = SendExpectData(null, "KEYS {0}\r\n", pattern); + if (keys.Length == 0) + return Array.Empty(); + return Encoding.UTF8.GetString(keys).Split(' '); + } + + public byte[][] GetKeys(params string[] keys) + { + if (keys == null) + throw new ArgumentNullException(nameof(keys)); + if (keys.Length == 0) + throw new ArgumentOutOfRangeException(nameof(keys)); + + return SendDataCommandExpectMultiBulkReply(null, "MGET {0}\r\n", string.Join(" ", keys)); + } + + public byte[][] SendDataCommandExpectMultiBulkReply(byte[] data, string command, params object[] args) + { + if (!SendDataCommand(data, command, args)) + throw new Exception("Unable to connect"); + int c = bstream.ReadByte(); + if (c == -1) + throw new ResponseException("No more data"); + + var s = ReadLine(); + Log("R: " + s); + if (c == '-') + throw new ResponseException(s.StartsWith("ERR") ? s.Substring(4) : s); + if (c == '*') + { + if (int.TryParse(s, out int count)) + { + var result = new byte[count][]; + + for (int i = 0; i < count; i++) + result[i] = ReadData(); + + return result; + } + } + throw new ResponseException("Unknown reply on multi-request: " + c + s); + } + + public byte[][] ListRange(string key, int start, int end) + { + return SendDataCommandExpectMultiBulkReply(null, "LRANGE {0} {1} {2}\r\n", key, start, end); + } + + public void RightPush(string key, string value) + { + SendExpectSuccess("RPUSH {0} {1}\r\n{2}\r\n", key, value.Length, value); + } + + public int ListLength(string key) + { + return SendExpectInt("LLEN {0}\r\n", key); + } + + public byte[] ListIndex(string key, int index) + { + SendCommand("LINDEX {0} {1}\r\n", key, index); + return ReadData(); + } + + public byte[] LeftPop(string key) + { + SendCommand("LPOP {0}\r\n", key); + return ReadData(); + } + + public bool AddToSet(string key, byte[] member) + { + return SendDataExpectInt(member, "SADD {0} {1}\r\n", key, member.Length) > 0; + } + + public bool AddToSet(string key, string member) + { + return AddToSet(key, Encoding.UTF8.GetBytes(member)); + } + + public int CardinalityOfSet(string key) + { + return SendDataExpectInt(null, "SCARD {0}\r\n", key); + } + + public bool IsMemberOfSet(string key, byte[] member) + { + return SendDataExpectInt(member, "SISMEMBER {0} {1}\r\n", key, member.Length) > 0; + } + + public bool IsMemberOfSet(string key, string member) + { + return IsMemberOfSet(key, Encoding.UTF8.GetBytes(member)); + } + + public byte[][] GetMembersOfSet(string key) + { + return SendDataCommandExpectMultiBulkReply(null, "SMEMBERS {0}\r\n", key); + } + + public byte[] GetRandomMemberOfSet(string key) + { + return SendExpectData(null, "SRANDMEMBER {0}\r\n", key); + } + + public byte[] PopRandomMemberOfSet(string key) + { + return SendExpectData(null, "SPOP {0}\r\n", key); + } + + public bool RemoveFromSet(string key, byte[] member) + { + return SendDataExpectInt(member, "SREM {0} {1}\r\n", key, member.Length) > 0; + } + + public bool RemoveFromSet(string key, string member) + { + return RemoveFromSet(key, Encoding.UTF8.GetBytes(member)); + } + + public byte[][] GetUnionOfSets(params string[] keys) + { + if (keys == null) + throw new ArgumentNullException(nameof(keys)); + + return SendDataCommandExpectMultiBulkReply(null, "SUNION " + string.Join(" ", keys) + "\r\n"); + } + + private void StoreSetCommands(string cmd, string destKey, params string[] keys) + { + if (string.IsNullOrEmpty(cmd)) + throw new ArgumentNullException(nameof(cmd)); + + if (string.IsNullOrEmpty(destKey)) + throw new ArgumentNullException(nameof(destKey)); + + if (keys == null) + throw new ArgumentNullException(nameof(keys)); + + SendExpectSuccess("{0} {1} {2}\r\n", cmd, destKey, string.Join(" ", keys)); + } + + public void StoreUnionOfSets(string destKey, params string[] keys) + { + StoreSetCommands("SUNIONSTORE", destKey, keys); + } + + public byte[][] GetIntersectionOfSets(params string[] keys) + { + if (keys == null) + throw new ArgumentNullException(nameof(keys)); + + return SendDataCommandExpectMultiBulkReply(null, "SINTER " + string.Join(" ", keys) + "\r\n"); + } + + public void StoreIntersectionOfSets(string destKey, params string[] keys) + { + StoreSetCommands("SINTERSTORE", destKey, keys); + } + + public byte[][] GetDifferenceOfSets(params string[] keys) + { + if (keys == null) + throw new ArgumentNullException(nameof(keys)); + + return SendDataCommandExpectMultiBulkReply(null, "SDIFF " + string.Join(" ", keys) + "\r\n"); + } + + public void StoreDifferenceOfSets(string destKey, params string[] keys) + { + StoreSetCommands("SDIFFSTORE", destKey, keys); + } + + public bool MoveMemberToSet(string srcKey, string destKey, byte[] member) + { + return SendDataExpectInt(member, "SMOVE {0} {1} {2}\r\n", srcKey, destKey, member.Length) > 0; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~Redis() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + SendCommand("QUIT\r\n"); + socket.Dispose(); + socket = null; + } + } + } + + public class SortOptions + { + public string Key { get; set; } + public bool Descending { get; set; } + public bool Lexographically { get; set; } + public int LowerLimit { get; set; } + public int UpperLimit { get; set; } + public string By { get; set; } + public string StoreInKey { get; set; } + public string Get { get; set; } + + public string ToCommand() + { + var command = "SORT " + Key; + if (LowerLimit != 0 || UpperLimit != 0) + command += " LIMIT " + LowerLimit + " " + UpperLimit; + if (Lexographically) + command += " ALPHA"; + if (!string.IsNullOrEmpty(By)) + command += " BY " + By; + if (!string.IsNullOrEmpty(Get)) + command += " GET " + Get; + if (!string.IsNullOrEmpty(StoreInKey)) + command += " STORE " + StoreInKey; + return command; + } + } +} diff --git a/tests/StackExchange.Redis.Tests/HighIntegrityBasicOpsTests.cs b/tests/StackExchange.Redis.Tests/HighIntegrityBasicOpsTests.cs new file mode 100644 index 000000000..d7b85cd62 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/HighIntegrityBasicOpsTests.cs @@ -0,0 +1,8 @@ +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class HighIntegrityBasicOpsTests(ITestOutputHelper output, SharedConnectionFixture fixture) : BasicOpsTests(output, fixture) +{ + internal override bool HighIntegrity => true; +} diff --git a/tests/StackExchange.Redis.Tests/HttpTunnelConnectTests.cs b/tests/StackExchange.Redis.Tests/HttpTunnelConnectTests.cs new file mode 100644 index 000000000..4099c7b94 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/HttpTunnelConnectTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests +{ + public class HttpTunnelConnectTests(ITestOutputHelper log) + { + private ITestOutputHelper Log { get; } = log; + + [Theory] + [InlineData("")] + [InlineData(",tunnel=http:127.0.0.1:8080")] + public async Task Connect(string suffix) + { + var cs = Environment.GetEnvironmentVariable("HACK_TUNNEL_ENDPOINT"); + if (string.IsNullOrWhiteSpace(cs)) + { + Assert.Skip("Need HACK_TUNNEL_ENDPOINT environment variable"); + } + var config = ConfigurationOptions.Parse(cs + suffix); + if (!string.IsNullOrWhiteSpace(suffix)) + { + Assert.NotNull(config.Tunnel); + } + await using var conn = await ConnectionMultiplexer.ConnectAsync(config); + var db = conn.GetDatabase(); + await db.PingAsync(); + RedisKey key = "HttpTunnel"; + await db.KeyDeleteAsync(key); + + // latency test + var watch = Stopwatch.StartNew(); + const int LATENCY_LOOP = 25, BANDWIDTH_LOOP = 10; + for (int i = 0; i < LATENCY_LOOP; i++) + { + await db.StringIncrementAsync(key); + } + watch.Stop(); + int count = (int)await db.StringGetAsync(key); + Log.WriteLine($"{LATENCY_LOOP}xINCR: {watch.ElapsedMilliseconds}ms"); + Assert.Equal(LATENCY_LOOP, count); + + // bandwidth test + var chunk = new byte[4096]; + var rand = new Random(1234); + for (int i = 0; i < BANDWIDTH_LOOP; i++) + { + rand.NextBytes(chunk); + watch = Stopwatch.StartNew(); + await db.StringSetAsync(key, chunk); + using var fetch = await db.StringGetLeaseAsync(key); + watch.Stop(); + Assert.NotNull(fetch); + Log.WriteLine($"SET+GET {chunk.Length} bytes: {watch.ElapsedMilliseconds}ms"); + Assert.True(fetch.Span.SequenceEqual(chunk)); + } + } + } +} diff --git a/tests/StackExchange.Redis.Tests/HyperLogLogTests.cs b/tests/StackExchange.Redis.Tests/HyperLogLogTests.cs new file mode 100644 index 000000000..f4c259854 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/HyperLogLogTests.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class HyperLogLogTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task SingleKeyLength() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = "hll1"; + + db.HyperLogLogAdd(key, "a"); + db.HyperLogLogAdd(key, "b"); + db.HyperLogLogAdd(key, "c"); + + Assert.True(db.HyperLogLogLength(key) > 0); + } + + [Fact] + public async Task MultiKeyLength() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey[] keys = ["hll1", "hll2", "hll3"]; + + db.HyperLogLogAdd(keys[0], "a"); + db.HyperLogLogAdd(keys[1], "b"); + db.HyperLogLogAdd(keys[2], "c"); + + Assert.True(db.HyperLogLogLength(keys) > 0); + } +} diff --git a/tests/StackExchange.Redis.Tests/InfoReplicationCheckTests.cs b/tests/StackExchange.Redis.Tests/InfoReplicationCheckTests.cs new file mode 100644 index 000000000..03b9f5b7f --- /dev/null +++ b/tests/StackExchange.Redis.Tests/InfoReplicationCheckTests.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class InfoReplicationCheckTests(ITestOutputHelper output) : TestBase(output) +{ + protected override string GetConfiguration() => base.GetConfiguration() + ",configCheckSeconds=2"; + + [Fact] + public async Task Exec() + { + Assert.Skip("need to think about CompletedSynchronously"); + + await using var conn = Create(); + + var parsed = ConfigurationOptions.Parse(conn.Configuration); + Assert.Equal(2, parsed.ConfigCheckSeconds); + var before = conn.GetCounters(); + await Task.Delay(7000).ForAwait(); + var after = conn.GetCounters(); + int done = (int)(after.Interactive.CompletedSynchronously - before.Interactive.CompletedSynchronously); + Assert.True(done >= 2, $"expected >=2, got {done}"); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/BgSaveResponseTests.cs b/tests/StackExchange.Redis.Tests/Issues/BgSaveResponseTests.cs new file mode 100644 index 000000000..15e4c6ef3 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/BgSaveResponseTests.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class BgSaveResponseTests(ITestOutputHelper output) : TestBase(output) +{ + [Theory(Skip = "We don't need to test this, and it really screws local testing hard.")] + [InlineData(SaveType.BackgroundSave)] + [InlineData(SaveType.BackgroundRewriteAppendOnlyFile)] + public async Task ShouldntThrowException(SaveType saveType) + { + await using var conn = Create(allowAdmin: true); + + var server = GetServer(conn); + server.Save(saveType); + await Task.Delay(1000); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/DefaultDatabaseTests.cs b/tests/StackExchange.Redis.Tests/Issues/DefaultDatabaseTests.cs new file mode 100644 index 000000000..9666c91a2 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/DefaultDatabaseTests.cs @@ -0,0 +1,54 @@ +using System.IO; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class DefaultDatabaseTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public void UnspecifiedDbId_ReturnsNull() + { + var config = ConfigurationOptions.Parse("localhost"); + Assert.Null(config.DefaultDatabase); + } + + [Fact] + public void SpecifiedDbId_ReturnsExpected() + { + var config = ConfigurationOptions.Parse("localhost,defaultDatabase=3"); + Assert.Equal(3, config.DefaultDatabase); + } + + [Fact] + public async Task ConfigurationOptions_UnspecifiedDefaultDb() + { + var log = new StringWriter(); + try + { + await using var conn = await ConnectionMultiplexer.ConnectAsync(TestConfig.Current.PrimaryServerAndPort, log); + var db = conn.GetDatabase(); + Assert.Equal(0, db.Database); + } + finally + { + Log(log.ToString()); + } + } + + [Fact] + public async Task ConfigurationOptions_SpecifiedDefaultDb() + { + var log = new StringWriter(); + try + { + await using var conn = await ConnectionMultiplexer.ConnectAsync($"{TestConfig.Current.PrimaryServerAndPort},defaultDatabase=3", log); + var db = conn.GetDatabase(); + Assert.Equal(3, db.Database); + } + finally + { + Log(log.ToString()); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue10Tests.cs b/tests/StackExchange.Redis.Tests/Issues/Issue10Tests.cs new file mode 100644 index 000000000..0a2f3fa8f --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue10Tests.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class Issue10Tests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task Execute() + { + await using var conn = Create(); + + var key = Me(); + var db = conn.GetDatabase(); + _ = db.KeyDeleteAsync(key); // contents: nil + _ = db.ListLeftPushAsync(key, "abc"); // "abc" + _ = db.ListLeftPushAsync(key, "def"); // "def", "abc" + _ = db.ListLeftPushAsync(key, "ghi"); // "ghi", "def", "abc", + _ = db.ListSetByIndexAsync(key, 1, "jkl"); // "ghi", "jkl", "abc" + + var contents = await db.ListRangeAsync(key, 0, -1); + Assert.Equal(3, contents.Length); + Assert.Equal("ghi", contents[0]); + Assert.Equal("jkl", contents[1]); + Assert.Equal("abc", contents[2]); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue1101Tests.cs b/tests/StackExchange.Redis.Tests/Issues/Issue1101Tests.cs new file mode 100644 index 000000000..b0d9b9027 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue1101Tests.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class Issue1101Tests(ITestOutputHelper output) : TestBase(output) +{ + private static void AssertCounts(ISubscriber pubsub, in RedisChannel channel, bool has, int handlers, int queues) + { + if (pubsub.Multiplexer is ConnectionMultiplexer muxer) + { + var aHas = muxer.GetSubscriberCounts(channel, out var ah, out var aq); + Assert.Equal(has, aHas); + Assert.Equal(handlers, ah); + Assert.Equal(queues, aq); + } + } + + [Fact] + public async Task ExecuteWithUnsubscribeViaChannel() + { + await using var conn = Create(log: Writer); + + RedisChannel name = RedisChannel.Literal(Me()); + var pubsub = conn.GetSubscriber(); + AssertCounts(pubsub, name, false, 0, 0); + + // subscribe and check we get data + var first = await pubsub.SubscribeAsync(name); + var second = await pubsub.SubscribeAsync(name); + AssertCounts(pubsub, name, true, 0, 2); + var values = new List(); + int i = 0; + first.OnMessage(x => + { + lock (values) { values.Add(x.Message); } + return Task.CompletedTask; + }); + second.OnMessage(_ => Interlocked.Increment(ref i)); + await Task.Delay(200); + await pubsub.PublishAsync(name, "abc"); + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => values.Count == 1); + lock (values) + { + Assert.Equal("abc", Assert.Single(values)); + } + var subs = conn.GetServer(conn.GetEndPoints().Single()).SubscriptionSubscriberCount(name); + Assert.Equal(1, subs); + Assert.False(first.Completion.IsCompleted, "completed"); + Assert.False(second.Completion.IsCompleted, "completed"); + + await first.UnsubscribeAsync(); + await Task.Delay(200); + await pubsub.PublishAsync(name, "def"); + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => values.Count == 1 && Volatile.Read(ref i) == 2); + lock (values) + { + Assert.Equal("abc", Assert.Single(values)); + } + Assert.Equal(2, Volatile.Read(ref i)); + Assert.True(first.Completion.IsCompleted, "completed"); + Assert.False(second.Completion.IsCompleted, "completed"); + AssertCounts(pubsub, name, true, 0, 1); + + await second.UnsubscribeAsync(); + await Task.Delay(200); + await pubsub.PublishAsync(name, "ghi"); + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => values.Count == 1); + lock (values) + { + Assert.Equal("abc", Assert.Single(values)); + } + Assert.Equal(2, Volatile.Read(ref i)); + Assert.True(first.Completion.IsCompleted, "completed"); + Assert.True(second.Completion.IsCompleted, "completed"); + AssertCounts(pubsub, name, false, 0, 0); + + subs = conn.GetServer(conn.GetEndPoints().Single()).SubscriptionSubscriberCount(name); + Assert.Equal(0, subs); + Assert.True(first.Completion.IsCompleted, "completed"); + Assert.True(second.Completion.IsCompleted, "completed"); + } + + [Fact] + public async Task ExecuteWithUnsubscribeViaSubscriber() + { + await using var conn = Create(shared: false, log: Writer); + + RedisChannel name = RedisChannel.Literal(Me()); + var pubsub = conn.GetSubscriber(); + AssertCounts(pubsub, name, false, 0, 0); + + // subscribe and check we get data + var first = await pubsub.SubscribeAsync(name); + var second = await pubsub.SubscribeAsync(name); + AssertCounts(pubsub, name, true, 0, 2); + var values = new List(); + int i = 0; + first.OnMessage(x => + { + lock (values) { values.Add(x.Message); } + return Task.CompletedTask; + }); + second.OnMessage(_ => Interlocked.Increment(ref i)); + + await Task.Delay(100); + await pubsub.PublishAsync(name, "abc"); + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => values.Count == 1); + lock (values) + { + Assert.Equal("abc", Assert.Single(values)); + } + var subs = conn.GetServer(conn.GetEndPoints().Single()).SubscriptionSubscriberCount(name); + Assert.Equal(1, subs); + Assert.False(first.Completion.IsCompleted, "completed"); + Assert.False(second.Completion.IsCompleted, "completed"); + + await pubsub.UnsubscribeAsync(name); + await Task.Delay(100); + await pubsub.PublishAsync(name, "def"); + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => values.Count == 1); + lock (values) + { + Assert.Equal("abc", Assert.Single(values)); + } + Assert.Equal(1, Volatile.Read(ref i)); + + subs = conn.GetServer(conn.GetEndPoints().Single()).SubscriptionSubscriberCount(name); + Assert.Equal(0, subs); + Assert.True(first.Completion.IsCompleted, "completed"); + Assert.True(second.Completion.IsCompleted, "completed"); + AssertCounts(pubsub, name, false, 0, 0); + } + + [Fact] + public async Task ExecuteWithUnsubscribeViaClearAll() + { + await using var conn = Create(log: Writer); + + RedisChannel name = RedisChannel.Literal(Me()); + var pubsub = conn.GetSubscriber(); + AssertCounts(pubsub, name, false, 0, 0); + + // subscribe and check we get data + var first = await pubsub.SubscribeAsync(name); + var second = await pubsub.SubscribeAsync(name); + AssertCounts(pubsub, name, true, 0, 2); + var values = new List(); + int i = 0; + first.OnMessage(x => + { + lock (values) { values.Add(x.Message); } + return Task.CompletedTask; + }); + second.OnMessage(_ => Interlocked.Increment(ref i)); + await Task.Delay(100); + await pubsub.PublishAsync(name, "abc"); + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => values.Count == 1); + lock (values) + { + Assert.Equal("abc", Assert.Single(values)); + } + var subs = conn.GetServer(conn.GetEndPoints().Single()).SubscriptionSubscriberCount(name); + Assert.Equal(1, subs); + Assert.False(first.Completion.IsCompleted, "completed"); + Assert.False(second.Completion.IsCompleted, "completed"); + + await pubsub.UnsubscribeAllAsync(); + await Task.Delay(100); + await pubsub.PublishAsync(name, "def"); + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => values.Count == 1); + lock (values) + { + Assert.Equal("abc", Assert.Single(values)); + } + Assert.Equal(1, Volatile.Read(ref i)); + + subs = conn.GetServer(conn.GetEndPoints().Single()).SubscriptionSubscriberCount(name); + Assert.Equal(0, subs); + Assert.True(first.Completion.IsCompleted, "completed"); + Assert.True(second.Completion.IsCompleted, "completed"); + AssertCounts(pubsub, name, false, 0, 0); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue1103Tests.cs b/tests/StackExchange.Redis.Tests/Issues/Issue1103Tests.cs new file mode 100644 index 000000000..ab4042042 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue1103Tests.cs @@ -0,0 +1,65 @@ +using System.Globalization; +using System.Threading.Tasks; +using Xunit; +using static StackExchange.Redis.RedisValue; + +namespace StackExchange.Redis.Tests.Issues; + +public class Issue1103Tests(ITestOutputHelper output) : TestBase(output) +{ + [Theory] + [InlineData(142205255210238005UL, (int)StorageType.Int64)] + [InlineData(ulong.MaxValue, (int)StorageType.UInt64)] + [InlineData(ulong.MinValue, (int)StorageType.Int64)] + [InlineData(0x8000000000000000UL, (int)StorageType.UInt64)] + [InlineData(0x8000000000000001UL, (int)StorageType.UInt64)] + [InlineData(0x7FFFFFFFFFFFFFFFUL, (int)StorageType.Int64)] + public async Task LargeUInt64StoredCorrectly(ulong value, int storageType) + { + await using var conn = Create(); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + RedisValue typed = value; + + // only need UInt64 for 64-bits + Assert.Equal((StorageType)storageType, typed.Type); + db.StringSet(key, typed); + var fromRedis = db.StringGet(key); + + Log($"{fromRedis.Type}: {fromRedis}"); + Assert.Equal(StorageType.Raw, fromRedis.Type); + Assert.Equal(value, (ulong)fromRedis); + Assert.Equal(value.ToString(CultureInfo.InvariantCulture), fromRedis.ToString()); + + var simplified = fromRedis.Simplify(); + Log($"{simplified.Type}: {simplified}"); + Assert.Equal((StorageType)storageType, typed.Type); + Assert.Equal(value, (ulong)simplified); + Assert.Equal(value.ToString(CultureInfo.InvariantCulture), fromRedis.ToString()); + } + + [Fact] + public void UnusualRedisValueOddities() // things we found while doing this + { + RedisValue x = 0, y = "0"; + Assert.Equal(x, y); + Assert.Equal(y, x); + + y = "-0"; + Assert.Equal(x, y); + Assert.Equal(y, x); + + y = "-"; // this is the oddness; this used to return true + Assert.NotEqual(x, y); + Assert.NotEqual(y, x); + + y = "+"; + Assert.NotEqual(x, y); + Assert.NotEqual(y, x); + + y = "."; + Assert.NotEqual(x, y); + Assert.NotEqual(y, x); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue182Tests.cs b/tests/StackExchange.Redis.Tests/Issues/Issue182Tests.cs new file mode 100644 index 000000000..e60332603 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue182Tests.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class Issue182Tests(ITestOutputHelper output) : TestBase(output) +{ + protected override string GetConfiguration() => $"{TestConfig.Current.PrimaryServerAndPort},responseTimeout=10000"; + + [Fact] + public async Task SetMembers() + { + Skip.UnlessLongRunning(); + await using var conn = Create(syncTimeout: 20000); + + conn.ConnectionFailed += (s, a) => + { + Log(a.FailureType.ToString()); + Log(a.Exception?.Message); + Log(a.Exception?.StackTrace); + }; + var db = conn.GetDatabase(); + + var key = Me(); + const int count = (int)5e6; + var len = await db.SetLengthAsync(key).ForAwait(); + + if (len != count) + { + await db.KeyDeleteAsync(key).ForAwait(); + foreach (var _ in Enumerable.Range(0, count)) + db.SetAdd(key, Guid.NewGuid().ToByteArray(), CommandFlags.FireAndForget); + + Assert.Equal(count, await db.SetLengthAsync(key).ForAwait()); // SCARD for set + } + var result = await db.SetMembersAsync(key).ForAwait(); + Assert.Equal(count, result.Length); // SMEMBERS result length + } + + [Fact] + public async Task SetUnion() + { + Skip.UnlessLongRunning(); + await using var conn = Create(syncTimeout: 10000); + + var db = conn.GetDatabase(); + + var key1 = Me() + ":1"; + var key2 = Me() + ":2"; + var dstkey = Me() + ":dst"; + + const int count = (int)5e6; + + var len1 = await db.SetLengthAsync(key1).ForAwait(); + var len2 = await db.SetLengthAsync(key2).ForAwait(); + await db.KeyDeleteAsync(dstkey).ForAwait(); + + if (len1 != count || len2 != count) + { + await db.KeyDeleteAsync(key1).ForAwait(); + await db.KeyDeleteAsync(key2).ForAwait(); + + foreach (var _ in Enumerable.Range(0, count)) + { + db.SetAdd(key1, Guid.NewGuid().ToByteArray(), CommandFlags.FireAndForget); + db.SetAdd(key2, Guid.NewGuid().ToByteArray(), CommandFlags.FireAndForget); + } + Assert.Equal(count, await db.SetLengthAsync(key1).ForAwait()); // SCARD for set 1 + Assert.Equal(count, await db.SetLengthAsync(key2).ForAwait()); // SCARD for set 2 + } + await db.SetCombineAndStoreAsync(SetOperation.Union, dstkey, key1, key2).ForAwait(); + var dstLen = db.SetLength(dstkey); + Assert.Equal(count * 2, dstLen); // SCARD for destination set + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue2164Tests.cs b/tests/StackExchange.Redis.Tests/Issues/Issue2164Tests.cs new file mode 100644 index 000000000..b52e9f627 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue2164Tests.cs @@ -0,0 +1,55 @@ +namespace StackExchange.Redis.Tests.Issues +{ + public class Issue2164Tests + { + [Fact] + public void LoadSimpleScript() + { + LuaScript.Prepare("return 42"); + } + [Fact] + public void LoadComplexScript() + { + LuaScript.Prepare(@" +------------------------------------------------------------------------------- +-- API definitions +------------------------------------------------------------------------------- +local MessageStoreAPI = {} + +MessageStoreAPI.confirmPendingDelivery = function(smscMessageId, smscDeliveredAt, smscMessageState) + local messageId = redis.call('hget', ""smId:"" .. smscMessageId, 'mId') + if not messageId then + return nil + end + -- delete pending delivery + redis.call('del', ""smId:"" .. smscMessageId) + + local mIdK = 'm:'..messageId + + local result = redis.call('hsetnx', mIdK, 'sState', smscMessageState) + if result == 1 then + redis.call('hset', mIdK, 'sDlvAt', smscDeliveredAt) + redis.call('zrem', ""msg.validUntil"", messageId) + return redis.call('hget', mIdK, 'payload') + else + return nil + end +end + + +------------------------------------------------------------------------------- +-- Function lookup +------------------------------------------------------------------------------- + +-- None of the function calls accept keys +if #KEYS > 0 then error('No Keys should be provided') end + +-- The first argument must be the function that we intend to call, and it must +-- exist +local command_name = assert(table.remove(ARGV, 1), 'Must provide a command as first argument') +local command = assert(MessageStoreAPI[command_name], 'Unknown command ' .. command_name) + +return command(unpack(ARGV))"); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue2176Tests.cs b/tests/StackExchange.Redis.Tests/Issues/Issue2176Tests.cs new file mode 100644 index 000000000..39edd91d1 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue2176Tests.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues +{ + public class Issue2176Tests(ITestOutputHelper output) : TestBase(output) + { + [Fact] + public async Task Execute_Batch() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + + var me = Me(); + var key = me + ":1"; + var key2 = me + ":2"; + var keyIntersect = me + ":result"; + + db.KeyDelete(key); + db.KeyDelete(key2); + db.KeyDelete(keyIntersect); + db.SortedSetAdd(key, "a", 1345); + + var tasks = new List(); + var batch = db.CreateBatch(); + tasks.Add(batch.SortedSetAddAsync(key2, "a", 4567)); + tasks.Add(batch.SortedSetCombineAndStoreAsync(SetOperation.Intersect, keyIntersect, [key, key2])); + var rangeByRankTask = batch.SortedSetRangeByRankAsync(keyIntersect); + tasks.Add(rangeByRankTask); + batch.Execute(); + + await Task.WhenAll(tasks.ToArray()); + + var rangeByRankSortedSetValues = rangeByRankTask.Result; + + int size = rangeByRankSortedSetValues.Length; + Assert.Equal(1, size); + string firstRedisValue = rangeByRankSortedSetValues.FirstOrDefault().ToString(); + Assert.Equal("a", firstRedisValue); + } + + [Fact] + public async Task Execute_Transaction() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + + var me = Me(); + var key = me + ":1"; + var key2 = me + ":2"; + var keyIntersect = me + ":result"; + + db.KeyDelete(key); + db.KeyDelete(key2); + db.KeyDelete(keyIntersect); + db.SortedSetAdd(key, "a", 1345); + + var tasks = new List(); + var batch = db.CreateTransaction(); + tasks.Add(batch.SortedSetAddAsync(key2, "a", 4567)); + tasks.Add(batch.SortedSetCombineAndStoreAsync(SetOperation.Intersect, keyIntersect, [key, key2])); + var rangeByRankTask = batch.SortedSetRangeByRankAsync(keyIntersect); + tasks.Add(rangeByRankTask); + batch.Execute(); + + await Task.WhenAll(tasks.ToArray()); + + var rangeByRankSortedSetValues = rangeByRankTask.Result; + + int size = rangeByRankSortedSetValues.Length; + Assert.Equal(1, size); + string firstRedisValue = rangeByRankSortedSetValues.FirstOrDefault().ToString(); + Assert.Equal("a", firstRedisValue); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue2392Tests.cs b/tests/StackExchange.Redis.Tests/Issues/Issue2392Tests.cs new file mode 100644 index 000000000..39df94021 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue2392Tests.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues +{ + public class Issue2392Tests(ITestOutputHelper output) : TestBase(output) + { + [Fact] + public async Task Execute() + { + var options = new ConfigurationOptions() + { + BacklogPolicy = new() + { + QueueWhileDisconnected = true, + AbortPendingOnConnectionFailure = false, + }, + AbortOnConnectFail = false, + ConnectTimeout = 1, + ConnectRetry = 0, + AsyncTimeout = 1, + SyncTimeout = 1, + AllowAdmin = true, + }; + options.EndPoints.Add("127.0.0.1:1234"); + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer); + var key = Me(); + var db = conn.GetDatabase(); + var server = conn.GetServerSnapshot()[0]; + + // Fail the connection + conn.AllowConnect = false; + server.SimulateConnectionFailure(SimulatedFailureType.All); + Assert.False(conn.IsConnected); + + await db.StringGetAsync(key, flags: CommandFlags.FireAndForget); + var ex = await Assert.ThrowsAnyAsync(() => db.StringGetAsync(key).WithTimeout(5000)); + Assert.True(ex is RedisTimeoutException or RedisConnectionException); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue2418.cs b/tests/StackExchange.Redis.Tests/Issues/Issue2418.cs new file mode 100644 index 000000000..db38b1325 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue2418.cs @@ -0,0 +1,39 @@ +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class Issue2418(ITestOutputHelper output, SharedConnectionFixture? fixture = null) : TestBase(output, fixture) +{ + [Fact] + public async Task Execute() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + + RedisKey key = Me(); + RedisValue someInt = 12; + Assert.False(someInt.IsNullOrEmpty, nameof(someInt.IsNullOrEmpty) + " before"); + Assert.True(someInt.IsInteger, nameof(someInt.IsInteger) + " before"); + await db.HashSetAsync(key, [new HashEntry("some_int", someInt)]); + + // check we can fetch it + var entry = await db.HashGetAllAsync(key); + Assert.NotEmpty(entry); + Assert.Single(entry); + foreach (var pair in entry) + { + Log($"'{pair.Name}'='{pair.Value}'"); + } + + // filter with LINQ + Assert.True(entry.Any(x => x.Name == "some_int"), "Any"); + someInt = entry.FirstOrDefault(x => x.Name == "some_int").Value; + Log($"found via Any: '{someInt}'"); + Assert.False(someInt.IsNullOrEmpty, nameof(someInt.IsNullOrEmpty) + " after"); + Assert.True(someInt.TryParse(out int i)); + Assert.Equal(12, i); + Assert.Equal(12, (int)someInt); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue2507.cs b/tests/StackExchange.Redis.Tests/Issues/Issue2507.cs new file mode 100644 index 000000000..b548d7031 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue2507.cs @@ -0,0 +1,40 @@ +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +[Collection(NonParallelCollection.Name)] +public class Issue2507(ITestOutputHelper output, SharedConnectionFixture? fixture = null) : TestBase(output, fixture) +{ + [Fact(Explicit = true)] + public async Task Execute() + { + await using var conn = Create(shared: false); + var db = conn.GetDatabase(); + var pubsub = conn.GetSubscriber(); + var queue = await pubsub.SubscribeAsync(RedisChannel.Literal("__redis__:invalidate")); + await Task.Delay(100); + var connectionId = conn.GetConnectionId(conn.GetEndPoints().Single(), ConnectionType.Subscription); + if (connectionId is null) Assert.Skip("Connection id not available"); + + string baseKey = Me(); + RedisKey key1 = baseKey + "abc", + key2 = baseKey + "ghi", + key3 = baseKey + "mno"; + + await db.StringSetAsync([new(key1, "def"), new(key2, "jkl"), new(key3, "pqr")]); + // this is not supported, but: we want it to at least not fail + await db.ExecuteAsync("CLIENT", "TRACKING", "on", "REDIRECT", connectionId!.Value, "BCAST"); + await db.KeyDeleteAsync([key1, key2, key3]); + await Task.Delay(100); + queue.Unsubscribe(); + Assert.True(queue.TryRead(out var message), "Queue 1 Read failed"); + Assert.Equal(key1, message.Message); + Assert.True(queue.TryRead(out message), "Queue 2 Read failed"); + Assert.Equal(key2, message.Message); + Assert.True(queue.TryRead(out message), "Queue 3 Read failed"); + Assert.Equal(key3, message.Message); + Assert.False(queue.TryRead(out message), "Queue 4 Read succeeded"); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue25Tests.cs b/tests/StackExchange.Redis.Tests/Issues/Issue25Tests.cs new file mode 100644 index 000000000..05dc4d57c --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue25Tests.cs @@ -0,0 +1,41 @@ +using System; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class Issue25Tests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public void CaseInsensitive() + { + var options = ConfigurationOptions.Parse("ssl=true"); + Assert.True(options.Ssl); + Assert.Equal("ssl=True", options.ToString()); + + options = ConfigurationOptions.Parse("SSL=TRUE"); + Assert.True(options.Ssl); + Assert.Equal("ssl=True", options.ToString()); + } + + [Fact] + public void UnkonwnKeywordHandling_Ignore() + { + ConfigurationOptions.Parse("ssl2=true", true); + } + + [Fact] + public void UnkonwnKeywordHandling_ExplicitFail() + { + var ex = Assert.Throws(() => ConfigurationOptions.Parse("ssl2=true", false)); + Assert.StartsWith("Keyword 'ssl2' is not supported", ex.Message); + Assert.Equal("ssl2", ex.ParamName); + } + + [Fact] + public void UnkonwnKeywordHandling_ImplicitFail() + { + var ex = Assert.Throws(() => ConfigurationOptions.Parse("ssl2=true")); + Assert.StartsWith("Keyword 'ssl2' is not supported", ex.Message); + Assert.Equal("ssl2", ex.ParamName); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue2653.cs b/tests/StackExchange.Redis.Tests/Issues/Issue2653.cs new file mode 100644 index 000000000..d304ff44a --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue2653.cs @@ -0,0 +1,16 @@ +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class Issue2653 +{ + [Theory] + [InlineData(null, "")] + [InlineData("", "")] + [InlineData("abcdef", "abcdef")] + [InlineData("abc.def", "abc.def")] + [InlineData("abc d \t ef", "abc-d-ef")] + [InlineData(" abc\r\ndef\n", "abc-def")] + public void CheckLibraySanitization(string? input, string expected) + => Assert.Equal(expected, ServerEndPoint.ClientInfoSanitize(input)); +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue2763Tests.cs b/tests/StackExchange.Redis.Tests/Issues/Issue2763Tests.cs new file mode 100644 index 000000000..699076118 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue2763Tests.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues +{ + public class Issue2763Tests(ITestOutputHelper output) : TestBase(output) + { + [Fact] + public async Task Execute() + { + await using var conn = Create(); + var subscriber = conn.GetSubscriber(); + + static void Handler(RedisChannel c, RedisValue v) { } + + const int COUNT = 1000; + RedisChannel channel = RedisChannel.Literal("CHANNEL:TEST"); + + List subscribes = new List(COUNT); + for (int i = 0; i < COUNT; i++) + subscribes.Add(() => subscriber.Subscribe(channel, Handler)); + Parallel.ForEach(subscribes, action => action()); + + Assert.Equal(COUNT, CountSubscriptionsForChannel(subscriber, channel)); + + List unsubscribes = new List(COUNT); + for (int i = 0; i < COUNT; i++) + unsubscribes.Add(() => subscriber.Unsubscribe(channel, Handler)); + Parallel.ForEach(unsubscribes, action => action()); + + Assert.Equal(0, CountSubscriptionsForChannel(subscriber, channel)); + } + + private static int CountSubscriptionsForChannel(ISubscriber subscriber, RedisChannel channel) + { + ConnectionMultiplexer connMultiplexer = (ConnectionMultiplexer)subscriber.Multiplexer; + connMultiplexer.GetSubscriberCounts(channel, out int handlers, out int _); + return handlers; + } + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue6Tests.cs b/tests/StackExchange.Redis.Tests/Issues/Issue6Tests.cs new file mode 100644 index 000000000..c7c6385c0 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/Issue6Tests.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class Issue6Tests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task ShouldWorkWithoutEchoOrPing() + { + await using var conn = Create(proxy: Proxy.Twemproxy); + + Log("config: " + conn.Configuration); + var db = conn.GetDatabase(); + var time = await db.PingAsync(); + Log("ping time: " + time); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/MassiveDeleteTests.cs b/tests/StackExchange.Redis.Tests/Issues/MassiveDeleteTests.cs new file mode 100644 index 000000000..94590a186 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/MassiveDeleteTests.cs @@ -0,0 +1,74 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class MassiveDeleteTests(ITestOutputHelper output) : TestBase(output) +{ + private async Task Prep(int dbId, string key) + { + await using var conn = Create(allowAdmin: true); + + var prefix = Me(); + Skip.IfMissingDatabase(conn, dbId); + GetServer(conn).FlushDatabase(dbId); + Task? last = null; + var db = conn.GetDatabase(dbId); + for (int i = 0; i < 10000; i++) + { + string iKey = prefix + i; + _ = db.StringSetAsync(iKey, iKey); + last = db.SetAddAsync(key, iKey); + } + await last!; + } + + [Fact] + public async Task ExecuteMassiveDelete() + { + Skip.UnlessLongRunning(); + var dbId = TestConfig.GetDedicatedDB(); + var key = Me(); + await Prep(dbId, key); + var watch = Stopwatch.StartNew(); + await using var conn = Create(); + using var throttle = new SemaphoreSlim(1); + var db = conn.GetDatabase(dbId); + var originally = await db.SetLengthAsync(key).ForAwait(); + int keepChecking = 1; + Task? last = null; + while (Volatile.Read(ref keepChecking) == 1) + { + throttle.Wait(); // acquire + var x = db.SetPopAsync(key).ContinueWith(task => + { + throttle.Release(); + if (task.IsCompleted) + { + if ((string?)task.Result == null) + { + Volatile.Write(ref keepChecking, 0); + } + else + { + last = db.KeyDeleteAsync((string?)task.Result); + } + } + }); + GC.KeepAlive(x); + } + if (last != null) + { + await last; + } + watch.Stop(); + long remaining = await db.SetLengthAsync(key).ForAwait(); + Log($"From {originally} to {remaining}; {watch.ElapsedMilliseconds}ms"); + + var counters = GetServer(conn).GetCounters(); + Log("Completions: {0} sync, {1} async", counters.Interactive.CompletedSynchronously, counters.Interactive.CompletedAsynchronously); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/SO10504853Tests.cs b/tests/StackExchange.Redis.Tests/Issues/SO10504853Tests.cs new file mode 100644 index 000000000..7d4276e9d --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/SO10504853Tests.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class SO10504853Tests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task LoopLotsOfTrivialStuff() + { + var key = Me(); + Trace.WriteLine("### init"); + await using (var conn = Create()) + { + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + } + const int COUNT = 2; + for (int i = 0; i < COUNT; i++) + { + Trace.WriteLine("### incr:" + i); + await using var conn = Create(); + var db = conn.GetDatabase(); + Assert.Equal(i + 1, db.StringIncrement(key)); + } + Trace.WriteLine("### close"); + await using (var conn = Create()) + { + var db = conn.GetDatabase(); + Assert.Equal(COUNT, (long)db.StringGet(key)); + } + } + + [Fact] + public async Task ExecuteWithEmptyStartingPoint() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + var task = new { priority = 3 }; + _ = db.KeyDeleteAsync(key); + _ = db.HashSetAsync(key, "something else", "abc"); + _ = db.HashSetAsync(key, "priority", task.priority.ToString()); + + var taskResult = db.HashGetAsync(key, "priority"); + + await taskResult; + + var priority = int.Parse(taskResult.Result!); + + Assert.Equal(3, priority); + } + + [Fact] + public async Task ExecuteWithNonHashStartingPoint() + { + var key = Me(); + await Assert.ThrowsAsync(async () => + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var task = new { priority = 3 }; + _ = db.KeyDeleteAsync(key); + _ = db.StringSetAsync(key, "not a hash"); + _ = db.HashSetAsync(key, "priority", task.priority.ToString()); + + var taskResult = db.HashGetAsync(key, "priority"); + + try + { + db.Wait(taskResult); + Assert.Fail("Should throw a WRONGTYPE"); + } + catch (AggregateException ex) + { + throw ex.InnerExceptions[0]; + } + }); // WRONGTYPE Operation against a key holding the wrong kind of value + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/SO10825542Tests.cs b/tests/StackExchange.Redis.Tests/Issues/SO10825542Tests.cs new file mode 100644 index 000000000..493f4ec1b --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/SO10825542Tests.cs @@ -0,0 +1,28 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class SO10825542Tests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task Execute() + { + await using var conn = Create(); + var key = Me(); + + var db = conn.GetDatabase(); + // set the field value and expiration + _ = db.HashSetAsync(key, "field1", Encoding.UTF8.GetBytes("hello world")); + _ = db.KeyExpireAsync(key, TimeSpan.FromSeconds(7200)); + _ = db.HashSetAsync(key, "field2", "fooobar"); + var result = await db.HashGetAllAsync(key).ForAwait(); + + Assert.Equal(2, result.Length); + var dict = result.ToStringDictionary(); + Assert.Equal("hello world", dict["field1"]); + Assert.Equal("fooobar", dict["field2"]); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/SO11766033Tests.cs b/tests/StackExchange.Redis.Tests/Issues/SO11766033Tests.cs new file mode 100644 index 000000000..65cef55a7 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/SO11766033Tests.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class SO11766033Tests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task TestNullString() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + const string? expectedTestValue = null; + var uid = Me(); + _ = db.StringSetAsync(uid, "abc"); + _ = db.StringSetAsync(uid, expectedTestValue); + string? testValue = db.StringGet(uid); + Assert.Null(testValue); + } + + [Fact] + public async Task TestEmptyString() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + const string expectedTestValue = ""; + var uid = Me(); + + _ = db.StringSetAsync(uid, expectedTestValue); + string? testValue = db.StringGet(uid); + + Assert.Equal(expectedTestValue, testValue); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/SO22786599Tests.cs b/tests/StackExchange.Redis.Tests/Issues/SO22786599Tests.cs new file mode 100644 index 000000000..0fc653991 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/SO22786599Tests.cs @@ -0,0 +1,33 @@ +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class SO22786599Tests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task Execute() + { + string currentIdsSetDbKey = Me() + ".x"; + string currentDetailsSetDbKey = Me() + ".y"; + + RedisValue[] stringIds = Enumerable.Range(1, 750).Select(i => (RedisValue)(i + " id")).ToArray(); + RedisValue[] stringDetails = Enumerable.Range(1, 750).Select(i => (RedisValue)(i + " detail")).ToArray(); + + await using var conn = Create(); + + var db = conn.GetDatabase(); + var tran = db.CreateTransaction(); + + _ = tran.SetAddAsync(currentIdsSetDbKey, stringIds); + _ = tran.SetAddAsync(currentDetailsSetDbKey, stringDetails); + + var watch = Stopwatch.StartNew(); + var isOperationSuccessful = tran.Execute(); + watch.Stop(); + Log("{0}ms", watch.ElapsedMilliseconds); + Assert.True(isOperationSuccessful); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/SO23949477Tests.cs b/tests/StackExchange.Redis.Tests/Issues/SO23949477Tests.cs new file mode 100644 index 000000000..92277289a --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/SO23949477Tests.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class SO23949477Tests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task Execute() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SortedSetAdd(key, "c", 3, When.Always, CommandFlags.FireAndForget); + db.SortedSetAdd( + key, + [ + new SortedSetEntry("a", 1), + new SortedSetEntry("b", 2), + new SortedSetEntry("d", 4), + new SortedSetEntry("e", 5), + ], + When.Always, + CommandFlags.FireAndForget); + var pairs = db.SortedSetRangeByScoreWithScores( + key, order: Order.Descending, take: 3); + Assert.Equal(3, pairs.Length); + Assert.Equal(5, pairs[0].Score); + Assert.Equal("e", pairs[0].Element); + Assert.Equal(4, pairs[1].Score); + Assert.Equal("d", pairs[1].Element); + Assert.Equal(3, pairs[2].Score); + Assert.Equal("c", pairs[2].Element); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/SO24807536Tests.cs b/tests/StackExchange.Redis.Tests/Issues/SO24807536Tests.cs new file mode 100644 index 000000000..ddff810c0 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/SO24807536Tests.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class SO24807536Tests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task Exec() + { + await using var conn = Create(); + + var key = Me(); + var db = conn.GetDatabase(); + + // setup some data + db.KeyDelete(key, CommandFlags.FireAndForget); + db.HashSet(key, "full", "some value", flags: CommandFlags.FireAndForget); + db.KeyExpire(key, TimeSpan.FromSeconds(2), CommandFlags.FireAndForget); + + // test while exists + var keyExists = db.KeyExists(key); + var ttl = db.KeyTimeToLive(key); + var fullWait = db.HashGetAsync(key, "full", flags: CommandFlags.None); + Assert.True(keyExists, "key exists"); + Assert.NotNull(ttl); + Assert.Equal("some value", await fullWait); + + // wait for expiry + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => !db.KeyExists(key)).ForAwait(); + + // test once expired + keyExists = db.KeyExists(key); + ttl = db.KeyTimeToLive(key); + fullWait = db.HashGetAsync(key, "full", flags: CommandFlags.None); + + Assert.False(keyExists); + Assert.Null(ttl); + var r = await fullWait; + Assert.True(r.IsNull); + Assert.Null((string?)r); + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/SO25113323Tests.cs b/tests/StackExchange.Redis.Tests/Issues/SO25113323Tests.cs new file mode 100644 index 000000000..00bc9836b --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/SO25113323Tests.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class SO25113323Tests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task SetExpirationToPassed() + { + await using var conn = Create(); + + // Given + var key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.HashSet(key, "full", "test", When.NotExists, CommandFlags.PreferMaster); + + await Task.Delay(2000).ForAwait(); + + // When + var serverTime = GetServer(conn).Time(); + var expiresOn = serverTime.AddSeconds(-2); + + var firstResult = db.KeyExpire(key, expiresOn, CommandFlags.PreferMaster); + var secondResult = db.KeyExpire(key, expiresOn, CommandFlags.PreferMaster); + var exists = db.KeyExists(key); + var ttl = db.KeyTimeToLive(key); + + // Then + Assert.True(firstResult); // could set the first time, but this nukes the key + Assert.False(secondResult); // can't set, since nuked + Assert.False(exists); // does not exist since nuked + Assert.Null(ttl); // no expiry since nuked + } +} diff --git a/tests/StackExchange.Redis.Tests/Issues/SO25567566Tests.cs b/tests/StackExchange.Redis.Tests/Issues/SO25567566Tests.cs new file mode 100644 index 000000000..6d00a705e --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Issues/SO25567566Tests.cs @@ -0,0 +1,70 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests.Issues; + +public class SO25567566Tests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task Execute() + { + Skip.UnlessLongRunning(); + await using var conn = await ConnectionMultiplexer.ConnectAsync(GetConfiguration()); + + for (int i = 0; i < 100; i++) + { + Assert.Equal("ok", await DoStuff(conn).ForAwait()); + } + } + + private async Task DoStuff(ConnectionMultiplexer conn) + { + var db = conn.GetDatabase(); + + var timeout = Task.Delay(5000); + var key = Me(); + var key2 = key + "2"; + var len = db.ListLengthAsync(key); + + if (await Task.WhenAny(timeout, len).ForAwait() != len) + { + return "Timeout getting length"; + } + + if ((await len.ForAwait()) == 0) + { + db.ListRightPush(key, "foo", flags: CommandFlags.FireAndForget); + } + var tran = db.CreateTransaction(); + var x = tran.ListRightPopLeftPushAsync(key, key2); + var y = tran.SetAddAsync(key + "set", "bar"); + var z = tran.KeyExpireAsync(key2, TimeSpan.FromSeconds(60)); + timeout = Task.Delay(5000); + + var exec = tran.ExecuteAsync(); + // SWAP THESE TWO + // bool ok = true; + bool ok = await Task.WhenAny(exec, timeout).ForAwait() == exec; + + if (ok) + { + if (await exec.ForAwait()) + { + await Task.WhenAll(x, y, z).ForAwait(); + + var db2 = conn.GetDatabase(); + db2.HashGet(key + "hash", "whatever"); + return "ok"; + } + else + { + return "Transaction aborted"; + } + } + else + { + return "Timeout during exec"; + } + } +} diff --git a/tests/StackExchange.Redis.Tests/KeyAndValueTests.cs b/tests/StackExchange.Redis.Tests/KeyAndValueTests.cs new file mode 100644 index 000000000..6d37fbe7a --- /dev/null +++ b/tests/StackExchange.Redis.Tests/KeyAndValueTests.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class KeyAndValueTests +{ + [Fact] + public void TestValues() + { + RedisValue @default = default(RedisValue); + CheckNull(@default); + + RedisValue nullString = (string?)null; + CheckNull(nullString); + + RedisValue nullBlob = (byte[]?)null; + CheckNull(nullBlob); + + RedisValue emptyString = ""; + CheckNotNull(emptyString); + + RedisValue emptyBlob = Array.Empty(); + CheckNotNull(emptyBlob); + + RedisValue a0 = new string('a', 1); + CheckNotNull(a0); + RedisValue a1 = new string('a', 1); + CheckNotNull(a1); + RedisValue b0 = new[] { (byte)'b' }; + CheckNotNull(b0); + RedisValue b1 = new[] { (byte)'b' }; + CheckNotNull(b1); + + RedisValue i4 = 1; + CheckNotNull(i4); + RedisValue i8 = 1L; + CheckNotNull(i8); + + RedisValue bool1 = true; + CheckNotNull(bool1); + RedisValue bool2 = false; + CheckNotNull(bool2); + RedisValue bool3 = true; + CheckNotNull(bool3); + + CheckSame(a0, a0); + CheckSame(a1, a1); + CheckSame(a0, a1); + + CheckSame(b0, b0); + CheckSame(b1, b1); + CheckSame(b0, b1); + + CheckSame(i4, i4); + CheckSame(i8, i8); + CheckSame(i4, i8); + + CheckSame(bool1, bool3); + CheckNotSame(bool1, bool2); + } + + internal static void CheckSame(RedisValue x, RedisValue y) + { + if (x.TryParse(out double value) && double.IsNaN(value)) + { + // NaN has atypical equality rules + Assert.True(y.TryParse(out value) && double.IsNaN(value)); + return; + } + Assert.True(Equals(x, y), "Equals(x, y)"); + Assert.True(Equals(y, x), "Equals(y, x)"); + Assert.True(EqualityComparer.Default.Equals(x, y), "EQ(x,y)"); + Assert.True(EqualityComparer.Default.Equals(y, x), "EQ(y,x)"); + Assert.True(x == y, "x==y"); + Assert.True(y == x, "y==x"); + Assert.False(x != y, "x!=y"); + Assert.False(y != x, "y!=x"); + Assert.True(x.Equals(y), "x.EQ(y)"); + Assert.True(y.Equals(x), "y.EQ(x)"); + Assert.True(x.GetHashCode() == y.GetHashCode(), "GetHashCode"); + } + + private static void CheckNotSame(RedisValue x, RedisValue y) + { + Assert.False(Equals(x, y)); + Assert.False(Equals(y, x)); + Assert.False(EqualityComparer.Default.Equals(x, y)); + Assert.False(EqualityComparer.Default.Equals(y, x)); + Assert.False(x == y); + Assert.False(y == x); + Assert.True(x != y); + Assert.True(y != x); + Assert.False(x.Equals(y)); + Assert.False(y.Equals(x)); + Assert.False(x.GetHashCode() == y.GetHashCode()); // well, very unlikely + } + + private static void CheckNotNull(RedisValue value) + { + Assert.False(value.IsNull); + Assert.NotNull((byte[]?)value); + Assert.NotNull((string?)value); + Assert.NotEqual(-1, value.GetHashCode()); + + Assert.NotNull((string?)value); + Assert.NotNull((byte[]?)value); + + CheckSame(value, value); + CheckNotSame(value, default(RedisValue)); + CheckNotSame(value, (string?)null); + CheckNotSame(value, (byte[]?)null); + } + + internal static void CheckNull(RedisValue value) + { + Assert.True(value.IsNull); + Assert.True(value.IsNullOrEmpty); + Assert.False(value.IsInteger); + Assert.Equal(-1, value.GetHashCode()); + + Assert.Null((string?)value); + Assert.Null((byte[]?)value); + + Assert.Equal(0, (int)value); + Assert.Equal(0L, (long)value); + + CheckSame(value, value); + // CheckSame(value, default(RedisValue)); + // CheckSame(value, (string)null); + // CheckSame(value, (byte[])null); + } + + [Fact] + public void ValuesAreConvertible() + { + RedisValue val = 123; + object o = val; + byte[] blob = (byte[])Convert.ChangeType(o, typeof(byte[])); + + Assert.Equal(3, blob.Length); + Assert.Equal((byte)'1', blob[0]); + Assert.Equal((byte)'2', blob[1]); + Assert.Equal((byte)'3', blob[2]); + + Assert.Equal(123, Convert.ToDouble(o)); + + IConvertible c = (IConvertible)o; + // ReSharper disable RedundantCast + Assert.Equal((short)123, c.ToInt16(CultureInfo.InvariantCulture)); + Assert.Equal((int)123, c.ToInt32(CultureInfo.InvariantCulture)); + Assert.Equal(123L, c.ToInt64(CultureInfo.InvariantCulture)); + Assert.Equal(123F, c.ToSingle(CultureInfo.InvariantCulture)); + Assert.Equal("123", c.ToString(CultureInfo.InvariantCulture)); + Assert.Equal(123D, c.ToDouble(CultureInfo.InvariantCulture)); + Assert.Equal(123M, c.ToDecimal(CultureInfo.InvariantCulture)); + Assert.Equal((ushort)123, c.ToUInt16(CultureInfo.InvariantCulture)); + Assert.Equal(123U, c.ToUInt32(CultureInfo.InvariantCulture)); + Assert.Equal(123UL, c.ToUInt64(CultureInfo.InvariantCulture)); + + blob = (byte[])c.ToType(typeof(byte[]), CultureInfo.InvariantCulture); + Assert.Equal(3, blob.Length); + Assert.Equal((byte)'1', blob[0]); + Assert.Equal((byte)'2', blob[1]); + Assert.Equal((byte)'3', blob[2]); + } + + [Fact] + public void CanBeDynamic() + { + RedisValue val = "abc"; + object o = val; + dynamic d = o; + byte[] blob = (byte[])d; // could be in a try/catch + Assert.Equal(3, blob.Length); + Assert.Equal((byte)'a', blob[0]); + Assert.Equal((byte)'b', blob[1]); + Assert.Equal((byte)'c', blob[2]); + } +} diff --git a/tests/StackExchange.Redis.Tests/KeyIdleAsyncTests.cs b/tests/StackExchange.Redis.Tests/KeyIdleAsyncTests.cs new file mode 100644 index 000000000..598e84d93 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/KeyIdleAsyncTests.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class KeyIdleAsyncTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task IdleTimeAsync() + { + await using var conn = Create(); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + await Task.Delay(2000).ForAwait(); + var idleTime = await db.KeyIdleTimeAsync(key).ForAwait(); + Assert.True(idleTime > TimeSpan.Zero, "First check"); + + db.StringSet(key, "new value2", flags: CommandFlags.FireAndForget); + var idleTime2 = await db.KeyIdleTimeAsync(key).ForAwait(); + Assert.True(idleTime2 < idleTime, "Second check"); + + db.KeyDelete(key); + var idleTime3 = await db.KeyIdleTimeAsync(key).ForAwait(); + Assert.Null(idleTime3); + } + + [Fact] + public async Task TouchIdleTimeAsync() + { + await using var conn = Create(require: RedisFeatures.v3_2_1); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + await Task.Delay(2000).ForAwait(); + var idleTime = await db.KeyIdleTimeAsync(key).ForAwait(); + Assert.True(idleTime > TimeSpan.Zero, "First check"); + + Assert.True(await db.KeyTouchAsync(key).ForAwait(), "Second check"); + var idleTime1 = await db.KeyIdleTimeAsync(key).ForAwait(); + Assert.True(idleTime1 < idleTime, "Third check"); + } +} diff --git a/tests/StackExchange.Redis.Tests/KeyIdleTests.cs b/tests/StackExchange.Redis.Tests/KeyIdleTests.cs new file mode 100644 index 000000000..deec1efb4 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/KeyIdleTests.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class KeyIdleTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task IdleTime() + { + await using var conn = Create(); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + await Task.Delay(2000).ForAwait(); + var idleTime = db.KeyIdleTime(key); + Assert.True(idleTime > TimeSpan.Zero); + + db.StringSet(key, "new value2", flags: CommandFlags.FireAndForget); + var idleTime2 = db.KeyIdleTime(key); + Assert.True(idleTime2 < idleTime); + + db.KeyDelete(key); + var idleTime3 = db.KeyIdleTime(key); + Assert.Null(idleTime3); + } + + [Fact] + public async Task TouchIdleTime() + { + await using var conn = Create(require: RedisFeatures.v3_2_1); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + await Task.Delay(2000).ForAwait(); + var idleTime = db.KeyIdleTime(key); + Assert.True(idleTime > TimeSpan.Zero, "First check"); + + Assert.True(db.KeyTouch(key), "Second check"); + var idleTime1 = db.KeyIdleTime(key); + Assert.True(idleTime1 < idleTime, "Third check"); + } +} diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedBatchTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedBatchTests.cs new file mode 100644 index 000000000..e92a7a227 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedBatchTests.cs @@ -0,0 +1,26 @@ +using System.Text; +using NSubstitute; +using StackExchange.Redis.KeyspaceIsolation; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(nameof(SubstituteDependentCollection))] +public sealed class KeyPrefixedBatchTests +{ + private readonly IBatch mock; + private readonly KeyPrefixedBatch prefixed; + + public KeyPrefixedBatchTests() + { + mock = Substitute.For(); + prefixed = new KeyPrefixedBatch(mock, Encoding.UTF8.GetBytes("prefix:")); + } + + [Fact] + public void Execute() + { + prefixed.Execute(); + mock.Received(1).Execute(); + } +} diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs new file mode 100644 index 000000000..0b781123c --- /dev/null +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs @@ -0,0 +1,1794 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Net; +using System.Text; +using NSubstitute; +using StackExchange.Redis.KeyspaceIsolation; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[CollectionDefinition(nameof(SubstituteDependentCollection), DisableParallelization = true)] +public class SubstituteDependentCollection { } + +[Collection(nameof(SubstituteDependentCollection))] +public sealed class KeyPrefixedDatabaseTests +{ + private readonly IDatabase mock; + private readonly IDatabase prefixed; + + internal static RedisKey[] IsKeys(params RedisKey[] expected) => IsRaw(expected); + internal static RedisValue[] IsValues(params RedisValue[] expected) => IsRaw(expected); + private static T[] IsRaw(T[] expected) + { + Expression> lambda = actual => actual.Length == expected.Length && expected.SequenceEqual(actual); + return Arg.Is(lambda); + } + + public KeyPrefixedDatabaseTests() + { + mock = Substitute.For(); + prefixed = new KeyPrefixedDatabase(mock, Encoding.UTF8.GetBytes("prefix:")); + } + + [Fact] + public void CreateBatch() + { + object asyncState = new(); + IBatch innerBatch = Substitute.For(); + mock.CreateBatch(asyncState).Returns(innerBatch); + IBatch wrappedBatch = prefixed.CreateBatch(asyncState); + mock.Received().CreateBatch(asyncState); + Assert.IsType(wrappedBatch); + Assert.Same(innerBatch, ((KeyPrefixedBatch)wrappedBatch).Inner); + } + + [Fact] + public void CreateTransaction() + { + object asyncState = new(); + ITransaction innerTransaction = Substitute.For(); + mock.CreateTransaction(asyncState).Returns(innerTransaction); + ITransaction wrappedTransaction = prefixed.CreateTransaction(asyncState); + mock.Received().CreateTransaction(asyncState); + Assert.IsType(wrappedTransaction); + Assert.Same(innerTransaction, ((KeyPrefixedTransaction)wrappedTransaction).Inner); + } + + [Fact] + public void DebugObject() + { + prefixed.DebugObject("key", CommandFlags.None); + mock.Received().DebugObject("prefix:key", CommandFlags.None); + } + + [Fact] + public void Get_Database() + { + mock.Database.Returns(123); + Assert.Equal(123, prefixed.Database); + } + + [Fact] + public void HashDecrement_1() + { + prefixed.HashDecrement("key", "hashField", 123, CommandFlags.None); + mock.Received().HashDecrement("prefix:key", "hashField", 123, CommandFlags.None); + } + + [Fact] + public void HashDecrement_2() + { + prefixed.HashDecrement("key", "hashField", 1.23, CommandFlags.None); + mock.Received().HashDecrement("prefix:key", "hashField", 1.23, CommandFlags.None); + } + + [Fact] + public void HashDelete_1() + { + prefixed.HashDelete("key", "hashField", CommandFlags.None); + mock.Received().HashDelete("prefix:key", "hashField", CommandFlags.None); + } + + [Fact] + public void HashDelete_2() + { + RedisValue[] hashFields = Array.Empty(); + prefixed.HashDelete("key", hashFields, CommandFlags.None); + mock.Received().HashDelete("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public void HashExists() + { + prefixed.HashExists("key", "hashField", CommandFlags.None); + mock.Received().HashExists("prefix:key", "hashField", CommandFlags.None); + } + + [Fact] + public void HashGet_1() + { + prefixed.HashGet("key", "hashField", CommandFlags.None); + mock.Received().HashGet("prefix:key", "hashField", CommandFlags.None); + } + + [Fact] + public void HashGet_2() + { + RedisValue[] hashFields = Array.Empty(); + prefixed.HashGet("key", hashFields, CommandFlags.None); + mock.Received().HashGet("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public void HashGetAll() + { + prefixed.HashGetAll("key", CommandFlags.None); + mock.Received().HashGetAll("prefix:key", CommandFlags.None); + } + + [Fact] + public void HashIncrement_1() + { + prefixed.HashIncrement("key", "hashField", 123, CommandFlags.None); + mock.Received().HashIncrement("prefix:key", "hashField", 123, CommandFlags.None); + } + + [Fact] + public void HashIncrement_2() + { + prefixed.HashIncrement("key", "hashField", 1.23, CommandFlags.None); + mock.Received().HashIncrement("prefix:key", "hashField", 1.23, CommandFlags.None); + } + + [Fact] + public void HashKeys() + { + prefixed.HashKeys("key", CommandFlags.None); + mock.Received().HashKeys("prefix:key", CommandFlags.None); + } + + [Fact] + public void HashLength() + { + prefixed.HashLength("key", CommandFlags.None); + mock.Received().HashLength("prefix:key", CommandFlags.None); + } + + [Fact] + public void HashScan() + { + prefixed.HashScan("key", "pattern", 123, flags: CommandFlags.None); + mock.Received().HashScan("prefix:key", "pattern", 123, CommandFlags.None); + } + + [Fact] + public void HashScan_Full() + { + prefixed.HashScan("key", "pattern", 123, 42, 64, flags: CommandFlags.None); + mock.Received().HashScan("prefix:key", "pattern", 123, 42, 64, CommandFlags.None); + } + + [Fact] + public void HashScanNoValues() + { + prefixed.HashScanNoValues("key", "pattern", 123, flags: CommandFlags.None); + mock.Received().HashScanNoValues("prefix:key", "pattern", 123, flags: CommandFlags.None); + } + + [Fact] + public void HashScanNoValues_Full() + { + prefixed.HashScanNoValues("key", "pattern", 123, 42, 64, flags: CommandFlags.None); + mock.Received().HashScanNoValues("prefix:key", "pattern", 123, 42, 64, CommandFlags.None); + } + + [Fact] + public void HashSet_1() + { + HashEntry[] hashFields = Array.Empty(); + prefixed.HashSet("key", hashFields, CommandFlags.None); + mock.Received().HashSet("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public void HashSet_2() + { + prefixed.HashSet("key", "hashField", "value", When.Exists, CommandFlags.None); + mock.Received().HashSet("prefix:key", "hashField", "value", When.Exists, CommandFlags.None); + } + + [Fact] + public void HashStringLength() + { + prefixed.HashStringLength("key", "field", CommandFlags.None); + mock.Received().HashStringLength("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public void HashValues() + { + prefixed.HashValues("key", CommandFlags.None); + mock.Received().HashValues("prefix:key", CommandFlags.None); + } + + [Fact] + public void HyperLogLogAdd_1() + { + prefixed.HyperLogLogAdd("key", "value", CommandFlags.None); + mock.Received().HyperLogLogAdd("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public void HyperLogLogAdd_2() + { + RedisValue[] values = Array.Empty(); + prefixed.HyperLogLogAdd("key", values, CommandFlags.None); + mock.Received().HyperLogLogAdd("prefix:key", values, CommandFlags.None); + } + + [Fact] + public void HyperLogLogLength() + { + prefixed.HyperLogLogLength("key", CommandFlags.None); + mock.Received().HyperLogLogLength("prefix:key", CommandFlags.None); + } + + [Fact] + public void HyperLogLogMerge_1() + { + prefixed.HyperLogLogMerge("destination", "first", "second", CommandFlags.None); + mock.Received().HyperLogLogMerge("prefix:destination", "prefix:first", "prefix:second", CommandFlags.None); + } + + [Fact] + public void HyperLogLogMerge_2() + { + prefixed.HyperLogLogMerge("destination", ["a", "b"], CommandFlags.None); + mock.Received().HyperLogLogMerge("prefix:destination", IsKeys(["prefix:a", "prefix:b"]), CommandFlags.None); + } + + [Fact] + public void IdentifyEndpoint() + { + prefixed.IdentifyEndpoint("key", CommandFlags.None); + mock.Received().IdentifyEndpoint("prefix:key", CommandFlags.None); + } + + [Fact] + public void KeyCopy() + { + prefixed.KeyCopy("key", "destination", flags: CommandFlags.None); + mock.Received().KeyCopy("prefix:key", "prefix:destination", -1, false, CommandFlags.None); + } + + [Fact] + public void KeyDelete_1() + { + prefixed.KeyDelete("key", CommandFlags.None); + mock.Received().KeyDelete("prefix:key", CommandFlags.None); + } + + [Fact] + public void KeyDelete_2() + { + prefixed.KeyDelete(["a", "b"], CommandFlags.None); + mock.Received().KeyDelete(IsKeys(["prefix:a", "prefix:b"]), CommandFlags.None); + } + + [Fact] + public void KeyDump() + { + prefixed.KeyDump("key", CommandFlags.None); + mock.Received().KeyDump("prefix:key", CommandFlags.None); + } + + [Fact] + public void KeyEncoding() + { + prefixed.KeyEncoding("key", CommandFlags.None); + mock.Received().KeyEncoding("prefix:key", CommandFlags.None); + } + + [Fact] + public void KeyExists() + { + prefixed.KeyExists("key", CommandFlags.None); + mock.Received().KeyExists("prefix:key", CommandFlags.None); + } + + [Fact] + public void KeyExpire_1() + { + TimeSpan expiry = TimeSpan.FromSeconds(123); + prefixed.KeyExpire("key", expiry, CommandFlags.None); + mock.Received().KeyExpire("prefix:key", expiry, CommandFlags.None); + } + + [Fact] + public void KeyExpire_2() + { + DateTime expiry = DateTime.Now; + prefixed.KeyExpire("key", expiry, CommandFlags.None); + mock.Received().KeyExpire("prefix:key", expiry, CommandFlags.None); + } + + [Fact] + public void KeyExpire_3() + { + TimeSpan expiry = TimeSpan.FromSeconds(123); + prefixed.KeyExpire("key", expiry, ExpireWhen.HasNoExpiry, CommandFlags.None); + mock.Received().KeyExpire("prefix:key", expiry, ExpireWhen.HasNoExpiry, CommandFlags.None); + } + + [Fact] + public void KeyExpire_4() + { + DateTime expiry = DateTime.Now; + prefixed.KeyExpire("key", expiry, ExpireWhen.HasNoExpiry, CommandFlags.None); + mock.Received().KeyExpire("prefix:key", expiry, ExpireWhen.HasNoExpiry, CommandFlags.None); + } + + [Fact] + public void KeyExpireTime() + { + prefixed.KeyExpireTime("key", CommandFlags.None); + mock.Received().KeyExpireTime("prefix:key", CommandFlags.None); + } + + [Fact] + public void KeyFrequency() + { + prefixed.KeyFrequency("key", CommandFlags.None); + mock.Received().KeyFrequency("prefix:key", CommandFlags.None); + } + + [Fact] + public void KeyMigrate() + { + EndPoint toServer = new IPEndPoint(IPAddress.Loopback, 123); + prefixed.KeyMigrate("key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.None); + mock.Received().KeyMigrate("prefix:key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.None); + } + + [Fact] + public void KeyMove() + { + prefixed.KeyMove("key", 123, CommandFlags.None); + mock.Received().KeyMove("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public void KeyPersist() + { + prefixed.KeyPersist("key", CommandFlags.None); + mock.Received().KeyPersist("prefix:key", CommandFlags.None); + } + + [Fact] + public void KeyRandom() + { + Assert.Throws(() => prefixed.KeyRandom()); + } + + [Fact] + public void KeyRefCount() + { + prefixed.KeyRefCount("key", CommandFlags.None); + mock.Received().KeyRefCount("prefix:key", CommandFlags.None); + } + + [Fact] + public void KeyRename() + { + prefixed.KeyRename("key", "newKey", When.Exists, CommandFlags.None); + mock.Received().KeyRename("prefix:key", "prefix:newKey", When.Exists, CommandFlags.None); + } + + [Fact] + public void KeyRestore() + { + byte[] value = Array.Empty(); + TimeSpan expiry = TimeSpan.FromSeconds(123); + prefixed.KeyRestore("key", value, expiry, CommandFlags.None); + mock.Received().KeyRestore("prefix:key", value, expiry, CommandFlags.None); + } + + [Fact] + public void KeyTimeToLive() + { + prefixed.KeyTimeToLive("key", CommandFlags.None); + mock.Received().KeyTimeToLive("prefix:key", CommandFlags.None); + } + + [Fact] + public void KeyType() + { + prefixed.KeyType("key", CommandFlags.None); + mock.Received().KeyType("prefix:key", CommandFlags.None); + } + + [Fact] + public void ListGetByIndex() + { + prefixed.ListGetByIndex("key", 123, CommandFlags.None); + mock.Received().ListGetByIndex("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public void ListInsertAfter() + { + prefixed.ListInsertAfter("key", "pivot", "value", CommandFlags.None); + mock.Received().ListInsertAfter("prefix:key", "pivot", "value", CommandFlags.None); + } + + [Fact] + public void ListInsertBefore() + { + prefixed.ListInsertBefore("key", "pivot", "value", CommandFlags.None); + mock.Received().ListInsertBefore("prefix:key", "pivot", "value", CommandFlags.None); + } + + [Fact] + public void ListLeftPop() + { + prefixed.ListLeftPop("key", CommandFlags.None); + mock.Received().ListLeftPop("prefix:key", CommandFlags.None); + } + + [Fact] + public void ListLeftPop_1() + { + prefixed.ListLeftPop("key", 123, CommandFlags.None); + mock.Received().ListLeftPop("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public void ListLeftPush_1() + { + prefixed.ListLeftPush("key", "value", When.Exists, CommandFlags.None); + mock.Received().ListLeftPush("prefix:key", "value", When.Exists, CommandFlags.None); + } + + [Fact] + public void ListLeftPush_2() + { + RedisValue[] values = Array.Empty(); + prefixed.ListLeftPush("key", values, CommandFlags.None); + mock.Received().ListLeftPush("prefix:key", values, CommandFlags.None); + } + + [Fact] + public void ListLeftPush_3() + { + RedisValue[] values = ["value1", "value2"]; + prefixed.ListLeftPush("key", values, When.Exists, CommandFlags.None); + mock.Received().ListLeftPush("prefix:key", values, When.Exists, CommandFlags.None); + } + + [Fact] + public void ListLength() + { + prefixed.ListLength("key", CommandFlags.None); + mock.Received().ListLength("prefix:key", CommandFlags.None); + } + + [Fact] + public void ListMove() + { + prefixed.ListMove("key", "destination", ListSide.Left, ListSide.Right, CommandFlags.None); + mock.Received().ListMove("prefix:key", "prefix:destination", ListSide.Left, ListSide.Right, CommandFlags.None); + } + + [Fact] + public void ListRange() + { + prefixed.ListRange("key", 123, 456, CommandFlags.None); + mock.Received().ListRange("prefix:key", 123, 456, CommandFlags.None); + } + + [Fact] + public void ListRemove() + { + prefixed.ListRemove("key", "value", 123, CommandFlags.None); + mock.Received().ListRemove("prefix:key", "value", 123, CommandFlags.None); + } + + [Fact] + public void ListRightPop() + { + prefixed.ListRightPop("key", CommandFlags.None); + mock.Received().ListRightPop("prefix:key", CommandFlags.None); + } + + [Fact] + public void ListRightPop_1() + { + prefixed.ListRightPop("key", 123, CommandFlags.None); + mock.Received().ListRightPop("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public void ListRightPopLeftPush() + { + prefixed.ListRightPopLeftPush("source", "destination", CommandFlags.None); + mock.Received().ListRightPopLeftPush("prefix:source", "prefix:destination", CommandFlags.None); + } + + [Fact] + public void ListRightPush_1() + { + prefixed.ListRightPush("key", "value", When.Exists, CommandFlags.None); + mock.Received().ListRightPush("prefix:key", "value", When.Exists, CommandFlags.None); + } + + [Fact] + public void ListRightPush_2() + { + RedisValue[] values = Array.Empty(); + prefixed.ListRightPush("key", values, CommandFlags.None); + mock.Received().ListRightPush("prefix:key", values, CommandFlags.None); + } + + [Fact] + public void ListRightPush_3() + { + RedisValue[] values = ["value1", "value2"]; + prefixed.ListRightPush("key", values, When.Exists, CommandFlags.None); + mock.Received().ListRightPush("prefix:key", values, When.Exists, CommandFlags.None); + } + + [Fact] + public void ListSetByIndex() + { + prefixed.ListSetByIndex("key", 123, "value", CommandFlags.None); + mock.Received().ListSetByIndex("prefix:key", 123, "value", CommandFlags.None); + } + + [Fact] + public void ListTrim() + { + prefixed.ListTrim("key", 123, 456, CommandFlags.None); + mock.Received().ListTrim("prefix:key", 123, 456, CommandFlags.None); + } + + [Fact] + public void LockExtend() + { + TimeSpan expiry = TimeSpan.FromSeconds(123); + prefixed.LockExtend("key", "value", expiry, CommandFlags.None); + mock.Received().LockExtend("prefix:key", "value", expiry, CommandFlags.None); + } + + [Fact] + public void LockQuery() + { + prefixed.LockQuery("key", CommandFlags.None); + mock.Received().LockQuery("prefix:key", CommandFlags.None); + } + + [Fact] + public void LockRelease() + { + prefixed.LockRelease("key", "value", CommandFlags.None); + mock.Received().LockRelease("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public void LockTake() + { + TimeSpan expiry = TimeSpan.FromSeconds(123); + prefixed.LockTake("key", "value", expiry, CommandFlags.None); + mock.Received().LockTake("prefix:key", "value", expiry, CommandFlags.None); + } + + [Fact] + public void Publish() + { +#pragma warning disable CS0618 + prefixed.Publish("channel", "message", CommandFlags.None); + mock.Received().Publish("prefix:channel", "message", CommandFlags.None); +#pragma warning restore CS0618 + } + + [Fact] + public void ScriptEvaluate_1() + { + byte[] hash = Array.Empty(); + RedisValue[] values = Array.Empty(); + RedisKey[] keys = ["a", "b"]; + prefixed.ScriptEvaluate(hash, keys, values, CommandFlags.None); + mock.Received().ScriptEvaluate(hash, IsKeys(["prefix:a", "prefix:b"]), values, CommandFlags.None); + } + + [Fact] + public void ScriptEvaluate_2() + { + RedisValue[] values = Array.Empty(); + RedisKey[] keys = ["a", "b"]; + prefixed.ScriptEvaluate(script: "script", keys: keys, values: values, flags: CommandFlags.None); + mock.Received().ScriptEvaluate(script: "script", keys: IsKeys(["prefix:a", "prefix:b"]), values: values, flags: CommandFlags.None); + } + + [Fact] + public void SetAdd_1() + { + prefixed.SetAdd("key", "value", CommandFlags.None); + mock.Received().SetAdd("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public void SetAdd_2() + { + RedisValue[] values = Array.Empty(); + prefixed.SetAdd("key", values, CommandFlags.None); + mock.Received().SetAdd("prefix:key", values, CommandFlags.None); + } + + [Fact] + public void SetCombine_1() + { + prefixed.SetCombine(SetOperation.Intersect, "first", "second", CommandFlags.None); + mock.Received().SetCombine(SetOperation.Intersect, "prefix:first", "prefix:second", CommandFlags.None); + } + + [Fact] + public void SetCombine_2() + { + RedisKey[] keys = ["a", "b"]; + prefixed.SetCombine(SetOperation.Intersect, keys, CommandFlags.None); + mock.Received().SetCombine(SetOperation.Intersect, IsKeys(["prefix:a", "prefix:b"]), CommandFlags.None); + } + + [Fact] + public void SetCombineAndStore_1() + { + prefixed.SetCombineAndStore(SetOperation.Intersect, "destination", "first", "second", CommandFlags.None); + mock.Received().SetCombineAndStore(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.None); + } + + [Fact] + public void SetCombineAndStore_2() + { + RedisKey[] keys = ["a", "b"]; + prefixed.SetCombineAndStore(SetOperation.Intersect, "destination", keys, CommandFlags.None); + mock.Received().SetCombineAndStore(SetOperation.Intersect, "prefix:destination", IsKeys(["prefix:a", "prefix:b"]), CommandFlags.None); + } + + [Fact] + public void SetContains() + { + prefixed.SetContains("key", "value", CommandFlags.None); + mock.Received().SetContains("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public void SetContains_2() + { + RedisValue[] values = ["value1", "value2"]; + prefixed.SetContains("key", values, CommandFlags.None); + mock.Received().SetContains("prefix:key", values, CommandFlags.None); + } + + [Fact] + public void SetIntersectionLength() + { + prefixed.SetIntersectionLength(["key1", "key2"]); + mock.Received().SetIntersectionLength(IsKeys(["prefix:key1", "prefix:key2"]), 0, CommandFlags.None); + } + + [Fact] + public void SetLength() + { + prefixed.SetLength("key", CommandFlags.None); + mock.Received().SetLength("prefix:key", CommandFlags.None); + } + + [Fact] + public void SetMembers() + { + prefixed.SetMembers("key", CommandFlags.None); + mock.Received().SetMembers("prefix:key", CommandFlags.None); + } + + [Fact] + public void SetMove() + { + prefixed.SetMove("source", "destination", "value", CommandFlags.None); + mock.Received().SetMove("prefix:source", "prefix:destination", "value", CommandFlags.None); + } + + [Fact] + public void SetPop_1() + { + prefixed.SetPop("key", CommandFlags.None); + mock.Received().SetPop("prefix:key", CommandFlags.None); + + prefixed.SetPop("key", 5, CommandFlags.None); + mock.Received().SetPop("prefix:key", 5, CommandFlags.None); + } + + [Fact] + public void SetPop_2() + { + prefixed.SetPop("key", 5, CommandFlags.None); + mock.Received().SetPop("prefix:key", 5, CommandFlags.None); + } + + [Fact] + public void SetRandomMember() + { + prefixed.SetRandomMember("key", CommandFlags.None); + mock.Received().SetRandomMember("prefix:key", CommandFlags.None); + } + + [Fact] + public void SetRandomMembers() + { + prefixed.SetRandomMembers("key", 123, CommandFlags.None); + mock.Received().SetRandomMembers("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public void SetRemove_1() + { + prefixed.SetRemove("key", "value", CommandFlags.None); + mock.Received().SetRemove("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public void SetRemove_2() + { + RedisValue[] values = Array.Empty(); + prefixed.SetRemove("key", values, CommandFlags.None); + mock.Received().SetRemove("prefix:key", values, CommandFlags.None); + } + + [Fact] + public void SetScan() + { + prefixed.SetScan("key", "pattern", 123, flags: CommandFlags.None); + mock.Received().SetScan("prefix:key", "pattern", 123, CommandFlags.None); + } + + [Fact] + public void SetScan_Full() + { + prefixed.SetScan("key", "pattern", 123, 42, 64, flags: CommandFlags.None); + mock.Received().SetScan("prefix:key", "pattern", 123, 42, 64, CommandFlags.None); + } + + [Fact] + public void Sort() + { + RedisValue[] get = ["a", "#"]; + + prefixed.Sort("key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.None); + prefixed.Sort("key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.None); + + mock.Received().Sort("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", IsValues(["prefix:a", "#"]), CommandFlags.None); + mock.Received().Sort("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", IsValues(["prefix:a", "#"]), CommandFlags.None); + } + + [Fact] + public void SortAndStore() + { + RedisValue[] get = ["a", "#"]; + + prefixed.SortAndStore("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.None); + prefixed.SortAndStore("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.None); + + mock.Received().SortAndStore("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", IsValues(["prefix:a", "#"]), CommandFlags.None); + mock.Received().SortAndStore("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", IsValues(["prefix:a", "#"]), CommandFlags.None); + } + + [Fact] + public void SortedSetAdd_1() + { + prefixed.SortedSetAdd("key", "member", 1.23, When.Exists, CommandFlags.None); + mock.Received().SortedSetAdd("prefix:key", "member", 1.23, When.Exists, CommandFlags.None); + } + + [Fact] + public void SortedSetAdd_2() + { + SortedSetEntry[] values = Array.Empty(); + prefixed.SortedSetAdd("key", values, When.Exists, CommandFlags.None); + mock.Received().SortedSetAdd("prefix:key", values, When.Exists, CommandFlags.None); + } + + [Fact] + public void SortedSetAdd_3() + { + SortedSetEntry[] values = Array.Empty(); + prefixed.SortedSetAdd("key", values, SortedSetWhen.GreaterThan, CommandFlags.None); + mock.Received().SortedSetAdd("prefix:key", values, SortedSetWhen.GreaterThan, CommandFlags.None); + } + + [Fact] + public void SortedSetCombine() + { + RedisKey[] keys = ["a", "b"]; + prefixed.SortedSetCombine(SetOperation.Intersect, ["a", "b"]); + mock.Received().SortedSetCombine(SetOperation.Intersect, IsKeys(["prefix:a", "prefix:b"]), null, Aggregate.Sum, CommandFlags.None); + } + + [Fact] + public void SortedSetCombineWithScores() + { + prefixed.SortedSetCombineWithScores(SetOperation.Intersect, ["a", "b"]); + mock.Received().SortedSetCombineWithScores(SetOperation.Intersect, IsKeys("prefix:a", "prefix:b"), null, Aggregate.Sum, CommandFlags.None); + } + + [Fact] + public void SortedSetCombineAndStore_1() + { + prefixed.SortedSetCombineAndStore(SetOperation.Intersect, "destination", "first", "second", Aggregate.Max, CommandFlags.None); + mock.Received().SortedSetCombineAndStore(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", Aggregate.Max, CommandFlags.None); + } + + [Fact] + public void SortedSetCombineAndStore_2() + { + RedisKey[] keys = ["a", "b"]; + prefixed.SetCombineAndStore(SetOperation.Intersect, "destination", keys, CommandFlags.None); + mock.Received().SetCombineAndStore(SetOperation.Intersect, "prefix:destination", IsKeys("prefix:a", "prefix:b"), CommandFlags.None); + } + + [Fact] + public void SortedSetDecrement() + { + prefixed.SortedSetDecrement("key", "member", 1.23, CommandFlags.None); + mock.Received().SortedSetDecrement("prefix:key", "member", 1.23, CommandFlags.None); + } + + [Fact] + public void SortedSetIncrement() + { + prefixed.SortedSetIncrement("key", "member", 1.23, CommandFlags.None); + mock.Received().SortedSetIncrement("prefix:key", "member", 1.23, CommandFlags.None); + } + + [Fact] + public void SortedSetIntersectionLength() + { + prefixed.SortedSetIntersectionLength(["a", "b"], 1, CommandFlags.None); + mock.Received().SortedSetIntersectionLength(IsKeys("prefix:a", "prefix:b"), 1, CommandFlags.None); + } + + [Fact] + public void SortedSetLength() + { + prefixed.SortedSetLength("key", 1.23, 1.23, Exclude.Start, CommandFlags.None); + mock.Received().SortedSetLength("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.None); + } + + [Fact] + public void SortedSetRandomMember() + { + prefixed.SortedSetRandomMember("key", CommandFlags.None); + mock.Received().SortedSetRandomMember("prefix:key", CommandFlags.None); + } + + [Fact] + public void SortedSetRandomMembers() + { + prefixed.SortedSetRandomMembers("key", 2, CommandFlags.None); + mock.Received().SortedSetRandomMembers("prefix:key", 2, CommandFlags.None); + } + + [Fact] + public void SortedSetRandomMembersWithScores() + { + prefixed.SortedSetRandomMembersWithScores("key", 2, CommandFlags.None); + mock.Received().SortedSetRandomMembersWithScores("prefix:key", 2, CommandFlags.None); + } + + [Fact] + public void SortedSetLengthByValue() + { + prefixed.SortedSetLengthByValue("key", "min", "max", Exclude.Start, CommandFlags.None); + mock.Received().SortedSetLengthByValue("prefix:key", "min", "max", Exclude.Start, CommandFlags.None); + } + + [Fact] + public void SortedSetRangeByRank() + { + prefixed.SortedSetRangeByRank("key", 123, 456, Order.Descending, CommandFlags.None); + mock.Received().SortedSetRangeByRank("prefix:key", 123, 456, Order.Descending, CommandFlags.None); + } + + [Fact] + public void SortedSetRangeByRankWithScores() + { + prefixed.SortedSetRangeByRankWithScores("key", 123, 456, Order.Descending, CommandFlags.None); + mock.Received().SortedSetRangeByRankWithScores("prefix:key", 123, 456, Order.Descending, CommandFlags.None); + } + + [Fact] + public void SortedSetRangeByScore() + { + prefixed.SortedSetRangeByScore("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.None); + mock.Received().SortedSetRangeByScore("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.None); + } + + [Fact] + public void SortedSetRangeByScoreWithScores() + { + prefixed.SortedSetRangeByScoreWithScores("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.None); + mock.Received().SortedSetRangeByScoreWithScores("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.None); + } + + [Fact] + public void SortedSetRangeByValue() + { + prefixed.SortedSetRangeByValue("key", "min", "max", Exclude.Start, 123, 456, CommandFlags.None); + mock.Received().SortedSetRangeByValue("prefix:key", "min", "max", Exclude.Start, Order.Ascending, 123, 456, CommandFlags.None); + } + + [Fact] + public void SortedSetRangeByValueDesc() + { + prefixed.SortedSetRangeByValue("key", "min", "max", Exclude.Start, Order.Descending, 123, 456, CommandFlags.None); + mock.Received().SortedSetRangeByValue("prefix:key", "min", "max", Exclude.Start, Order.Descending, 123, 456, CommandFlags.None); + } + + [Fact] + public void SortedSetRank() + { + prefixed.SortedSetRank("key", "member", Order.Descending, CommandFlags.None); + mock.Received().SortedSetRank("prefix:key", "member", Order.Descending, CommandFlags.None); + } + + [Fact] + public void SortedSetRemove_1() + { + prefixed.SortedSetRemove("key", "member", CommandFlags.None); + mock.Received().SortedSetRemove("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public void SortedSetRemove_2() + { + RedisValue[] members = Array.Empty(); + prefixed.SortedSetRemove("key", members, CommandFlags.None); + mock.Received().SortedSetRemove("prefix:key", members, CommandFlags.None); + } + + [Fact] + public void SortedSetRemoveRangeByRank() + { + prefixed.SortedSetRemoveRangeByRank("key", 123, 456, CommandFlags.None); + mock.Received().SortedSetRemoveRangeByRank("prefix:key", 123, 456, CommandFlags.None); + } + + [Fact] + public void SortedSetRemoveRangeByScore() + { + prefixed.SortedSetRemoveRangeByScore("key", 1.23, 1.23, Exclude.Start, CommandFlags.None); + mock.Received().SortedSetRemoveRangeByScore("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.None); + } + + [Fact] + public void SortedSetRemoveRangeByValue() + { + prefixed.SortedSetRemoveRangeByValue("key", "min", "max", Exclude.Start, CommandFlags.None); + mock.Received().SortedSetRemoveRangeByValue("prefix:key", "min", "max", Exclude.Start, CommandFlags.None); + } + + [Fact] + public void SortedSetScan() + { + prefixed.SortedSetScan("key", "pattern", 123, flags: CommandFlags.None); + mock.Received().SortedSetScan("prefix:key", "pattern", 123, CommandFlags.None); + } + + [Fact] + public void SortedSetScan_Full() + { + prefixed.SortedSetScan("key", "pattern", 123, 42, 64, flags: CommandFlags.None); + mock.Received().SortedSetScan("prefix:key", "pattern", 123, 42, 64, CommandFlags.None); + } + + [Fact] + public void SortedSetScore() + { + prefixed.SortedSetScore("key", "member", CommandFlags.None); + mock.Received().SortedSetScore("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public void SortedSetScore_Multiple() + { + var values = new RedisValue[] { "member1", "member2" }; + prefixed.SortedSetScores("key", values, CommandFlags.None); + mock.Received().SortedSetScores("prefix:key", values, CommandFlags.None); + } + + [Fact] + public void SortedSetUpdate() + { + SortedSetEntry[] values = Array.Empty(); + prefixed.SortedSetUpdate("key", values, SortedSetWhen.GreaterThan, CommandFlags.None); + mock.Received().SortedSetUpdate("prefix:key", values, SortedSetWhen.GreaterThan, CommandFlags.None); + } + + [Fact] + public void StreamAcknowledge_1() + { + prefixed.StreamAcknowledge("key", "group", "0-0", CommandFlags.None); + mock.Received().StreamAcknowledge("prefix:key", "group", "0-0", CommandFlags.None); + } + + [Fact] + public void StreamAcknowledge_2() + { + var messageIds = new RedisValue[] { "0-0", "0-1", "0-2" }; + prefixed.StreamAcknowledge("key", "group", messageIds, CommandFlags.None); + mock.Received().StreamAcknowledge("prefix:key", "group", messageIds, CommandFlags.None); + } + + [Fact] + public void StreamAdd_1() + { + prefixed.StreamAdd("key", "field1", "value1", "*", 1000, true, CommandFlags.None); + mock.Received().StreamAdd("prefix:key", "field1", "value1", "*", 1000, true, CommandFlags.None); + } + + [Fact] + public void StreamAdd_2() + { + var fields = Array.Empty(); + prefixed.StreamAdd("key", fields, "*", 1000, true, CommandFlags.None); + mock.Received().StreamAdd("prefix:key", fields, "*", 1000, true, CommandFlags.None); + } + + [Fact] + public void StreamAutoClaim() + { + prefixed.StreamAutoClaim("key", "group", "consumer", 0, "0-0", 100, CommandFlags.None); + mock.Received().StreamAutoClaim("prefix:key", "group", "consumer", 0, "0-0", 100, CommandFlags.None); + } + + [Fact] + public void StreamAutoClaimIdsOnly() + { + prefixed.StreamAutoClaimIdsOnly("key", "group", "consumer", 0, "0-0", 100, CommandFlags.None); + mock.Received().StreamAutoClaimIdsOnly("prefix:key", "group", "consumer", 0, "0-0", 100, CommandFlags.None); + } + + [Fact] + public void StreamClaimMessages() + { + var messageIds = Array.Empty(); + prefixed.StreamClaim("key", "group", "consumer", 1000, messageIds, CommandFlags.None); + mock.Received().StreamClaim("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.None); + } + + [Fact] + public void StreamClaimMessagesReturningIds() + { + var messageIds = Array.Empty(); + prefixed.StreamClaimIdsOnly("key", "group", "consumer", 1000, messageIds, CommandFlags.None); + mock.Received().StreamClaimIdsOnly("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.None); + } + + [Fact] + public void StreamConsumerGroupSetPosition() + { + prefixed.StreamConsumerGroupSetPosition("key", "group", StreamPosition.Beginning, CommandFlags.None); + mock.Received().StreamConsumerGroupSetPosition("prefix:key", "group", StreamPosition.Beginning, CommandFlags.None); + } + + [Fact] + public void StreamConsumerInfoGet() + { + prefixed.StreamConsumerInfo("key", "group", CommandFlags.None); + mock.Received().StreamConsumerInfo("prefix:key", "group", CommandFlags.None); + } + + [Fact] + public void StreamCreateConsumerGroup() + { + prefixed.StreamCreateConsumerGroup("key", "group", StreamPosition.Beginning, false, CommandFlags.None); + mock.Received().StreamCreateConsumerGroup("prefix:key", "group", StreamPosition.Beginning, false, CommandFlags.None); + } + + [Fact] + public void StreamGroupInfoGet() + { + prefixed.StreamGroupInfo("key", CommandFlags.None); + mock.Received().StreamGroupInfo("prefix:key", CommandFlags.None); + } + + [Fact] + public void StreamInfoGet() + { + prefixed.StreamInfo("key", CommandFlags.None); + mock.Received().StreamInfo("prefix:key", CommandFlags.None); + } + + [Fact] + public void StreamLength() + { + prefixed.StreamLength("key", CommandFlags.None); + mock.Received().StreamLength("prefix:key", CommandFlags.None); + } + + [Fact] + public void StreamMessagesDelete() + { + var messageIds = Array.Empty(); + prefixed.StreamDelete("key", messageIds, CommandFlags.None); + mock.Received().StreamDelete("prefix:key", messageIds, CommandFlags.None); + } + + [Fact] + public void StreamDeleteConsumer() + { + prefixed.StreamDeleteConsumer("key", "group", "consumer", CommandFlags.None); + mock.Received().StreamDeleteConsumer("prefix:key", "group", "consumer", CommandFlags.None); + } + + [Fact] + public void StreamDeleteConsumerGroup() + { + prefixed.StreamDeleteConsumerGroup("key", "group", CommandFlags.None); + mock.Received().StreamDeleteConsumerGroup("prefix:key", "group", CommandFlags.None); + } + + [Fact] + public void StreamPendingInfoGet() + { + prefixed.StreamPending("key", "group", CommandFlags.None); + mock.Received().StreamPending("prefix:key", "group", CommandFlags.None); + } + + [Fact] + public void StreamPendingMessageInfoGet() + { + prefixed.StreamPendingMessages("key", "group", 10, RedisValue.Null, "-", "+", 1000, CommandFlags.None); + mock.Received().StreamPendingMessages("prefix:key", "group", 10, RedisValue.Null, "-", "+", 1000, CommandFlags.None); + } + + [Fact] + public void StreamRange() + { + prefixed.StreamRange("key", "-", "+", null, Order.Ascending, CommandFlags.None); + mock.Received().StreamRange("prefix:key", "-", "+", null, Order.Ascending, CommandFlags.None); + } + + [Fact] + public void StreamRead_1() + { + var streamPositions = Array.Empty(); + prefixed.StreamRead(streamPositions, null, CommandFlags.None); + mock.Received().StreamRead(streamPositions, null, CommandFlags.None); + } + + [Fact] + public void StreamRead_2() + { + prefixed.StreamRead("key", "0-0", null, CommandFlags.None); + mock.Received().StreamRead("prefix:key", "0-0", null, CommandFlags.None); + } + + [Fact] + public void StreamStreamReadGroup_1() + { + prefixed.StreamReadGroup("key", "group", "consumer", "0-0", 10, false, CommandFlags.None); + mock.Received().StreamReadGroup("prefix:key", "group", "consumer", "0-0", 10, false, CommandFlags.None); + } + + [Fact] + public void StreamStreamReadGroup_2() + { + var streamPositions = Array.Empty(); + prefixed.StreamReadGroup(streamPositions, "group", "consumer", 10, false, CommandFlags.None); + mock.Received().StreamReadGroup(streamPositions, "group", "consumer", 10, false, CommandFlags.None); + } + + [Fact] + public void StreamTrim() + { + prefixed.StreamTrim("key", 1000, true, CommandFlags.None); + mock.Received().StreamTrim("prefix:key", 1000, true, CommandFlags.None); + } + + [Fact] + public void StreamTrimByMinId() + { + prefixed.StreamTrimByMinId("key", 1111111111); + mock.Received().StreamTrimByMinId("prefix:key", 1111111111); + } + + [Fact] + public void StreamTrimByMinIdWithApproximate() + { + prefixed.StreamTrimByMinId("key", 1111111111, useApproximateMaxLength: true); + mock.Received().StreamTrimByMinId("prefix:key", 1111111111, useApproximateMaxLength: true); + } + + [Fact] + public void StreamTrimByMinIdWithApproximateAndLimit() + { + prefixed.StreamTrimByMinId("key", 1111111111, useApproximateMaxLength: true, limit: 100); + mock.Received().StreamTrimByMinId("prefix:key", 1111111111, useApproximateMaxLength: true, limit: 100); + } + + [Fact] + public void StringAppend() + { + prefixed.StringAppend("key", "value", CommandFlags.None); + mock.Received().StringAppend("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public void StringBitCount() + { + prefixed.StringBitCount("key", 123, 456, CommandFlags.None); + mock.Received().StringBitCount("prefix:key", 123, 456, CommandFlags.None); + } + + [Fact] + public void StringBitCount_2() + { + prefixed.StringBitCount("key", 123, 456, StringIndexType.Byte, CommandFlags.None); + mock.Received().StringBitCount("prefix:key", 123, 456, StringIndexType.Byte, CommandFlags.None); + } + + [Fact] + public void StringBitOperation_1() + { + prefixed.StringBitOperation(Bitwise.Xor, "destination", "first", "second", CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.Xor, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.None); + } + + [Fact] + public void StringBitOperation_2() + { + RedisKey[] keys = ["a", "b"]; + prefixed.StringBitOperation(Bitwise.Xor, "destination", keys, CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.Xor, "prefix:destination", IsKeys("prefix:a", "prefix:b"), CommandFlags.None); + } + + [Fact] + public void StringBitOperation_Diff() + { + RedisKey[] keys = ["x", "y1", "y2"]; + prefixed.StringBitOperation(Bitwise.Diff, "destination", keys, CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.Diff, "prefix:destination", IsKeys("prefix:x", "prefix:y1", "prefix:y2"), CommandFlags.None); + } + + [Fact] + public void StringBitOperation_Diff1() + { + RedisKey[] keys = ["x", "y1", "y2"]; + prefixed.StringBitOperation(Bitwise.Diff1, "destination", keys, CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.Diff1, "prefix:destination", IsKeys("prefix:x", "prefix:y1", "prefix:y2"), CommandFlags.None); + } + + [Fact] + public void StringBitOperation_AndOr() + { + RedisKey[] keys = ["x", "y1", "y2"]; + prefixed.StringBitOperation(Bitwise.AndOr, "destination", keys, CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.AndOr, "prefix:destination", IsKeys("prefix:x", "prefix:y1", "prefix:y2"), CommandFlags.None); + } + + [Fact] + public void StringBitOperation_One() + { + RedisKey[] keys = ["a", "b", "c"]; + prefixed.StringBitOperation(Bitwise.One, "destination", keys, CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.One, "prefix:destination", IsKeys("prefix:a", "prefix:b", "prefix:c"), CommandFlags.None); + } + + [Fact] + public void StringBitPosition() + { + prefixed.StringBitPosition("key", true, 123, 456, CommandFlags.None); + mock.Received().StringBitPosition("prefix:key", true, 123, 456, CommandFlags.None); + } + + [Fact] + public void StringBitPosition_2() + { + prefixed.StringBitPosition("key", true, 123, 456, StringIndexType.Byte, CommandFlags.None); + mock.Received().StringBitPosition("prefix:key", true, 123, 456, StringIndexType.Byte, CommandFlags.None); + } + + [Fact] + public void StringDecrement_1() + { + prefixed.StringDecrement("key", 123, CommandFlags.None); + mock.Received().StringDecrement("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public void StringDecrement_2() + { + prefixed.StringDecrement("key", 1.23, CommandFlags.None); + mock.Received().StringDecrement("prefix:key", 1.23, CommandFlags.None); + } + + [Fact] + public void StringGet_1() + { + prefixed.StringGet("key", CommandFlags.None); + mock.Received().StringGet("prefix:key", CommandFlags.None); + } + + [Fact] + public void StringGet_2() + { + RedisKey[] keys = ["a", "b"]; + prefixed.StringGet(keys, CommandFlags.None); + mock.Received().StringGet(IsKeys("prefix:a", "prefix:b"), CommandFlags.None); + } + + [Fact] + public void StringGetBit() + { + prefixed.StringGetBit("key", 123, CommandFlags.None); + mock.Received().StringGetBit("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public void StringGetRange() + { + prefixed.StringGetRange("key", 123, 456, CommandFlags.None); + mock.Received().StringGetRange("prefix:key", 123, 456, CommandFlags.None); + } + + [Fact] + public void StringGetSet() + { + prefixed.StringGetSet("key", "value", CommandFlags.None); + mock.Received().StringGetSet("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public void StringGetDelete() + { + prefixed.StringGetDelete("key", CommandFlags.None); + mock.Received().StringGetDelete("prefix:key", CommandFlags.None); + } + + [Fact] + public void StringGetWithExpiry() + { + prefixed.StringGetWithExpiry("key", CommandFlags.None); + mock.Received().StringGetWithExpiry("prefix:key", CommandFlags.None); + } + + [Fact] + public void StringIncrement_1() + { + prefixed.StringIncrement("key", 123, CommandFlags.None); + mock.Received().StringIncrement("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public void StringIncrement_2() + { + prefixed.StringIncrement("key", 1.23, CommandFlags.None); + mock.Received().StringIncrement("prefix:key", 1.23, CommandFlags.None); + } + + [Fact] + public void StringLength() + { + prefixed.StringLength("key", CommandFlags.None); + mock.Received().StringLength("prefix:key", CommandFlags.None); + } + + [Fact] + public void StringSet_1() + { + TimeSpan expiry = TimeSpan.FromSeconds(123); + prefixed.StringSet("key", "value", expiry, When.Exists, CommandFlags.None); + mock.Received().StringSet("prefix:key", "value", expiry, When.Exists, CommandFlags.None); + } + + [Fact] + public void StringSet_2() + { + TimeSpan? expiry = null; + prefixed.StringSet("key", "value", expiry, true, When.Exists, CommandFlags.None); + mock.Received().StringSet("prefix:key", "value", expiry, true, When.Exists, CommandFlags.None); + } + + [Fact] + public void StringSet_3() + { + KeyValuePair[] values = [new KeyValuePair("a", "x"), new KeyValuePair("b", "y")]; + Expression[]>> valid = _ => _.Length == 2 && _[0].Key == "prefix:a" && _[0].Value == "x" && _[1].Key == "prefix:b" && _[1].Value == "y"; + prefixed.StringSet(values, When.Exists, CommandFlags.None); + mock.Received().StringSet(Arg.Is(valid), When.Exists, CommandFlags.None); + } + + [Fact] + public void StringSet_Compat() + { + TimeSpan? expiry = null; + prefixed.StringSet("key", "value", expiry, When.Exists); + mock.Received().StringSet("prefix:key", "value", expiry, When.Exists); + } + + [Fact] + public void StringSetBit() + { + prefixed.StringSetBit("key", 123, true, CommandFlags.None); + mock.Received().StringSetBit("prefix:key", 123, true, CommandFlags.None); + } + + [Fact] + public void StringSetRange() + { + prefixed.StringSetRange("key", 123, "value", CommandFlags.None); + mock.Received().StringSetRange("prefix:key", 123, "value", CommandFlags.None); + } + + [Fact] + public void Execute_1() + { + prefixed.Execute("CUSTOM", "arg1", (RedisKey)"arg2"); + mock.Received().Execute("CUSTOM", Arg.Is(args => args.Length == 2 && args[0].Equals("arg1") && args[1].Equals((RedisKey)"prefix:arg2")), CommandFlags.None); + } + + [Fact] + public void Execute_2() + { + var args = new List { "arg1", (RedisKey)"arg2" }; + prefixed.Execute("CUSTOM", args, CommandFlags.None); + mock.Received().Execute("CUSTOM", Arg.Is>(a => a.Count == 2 && a.ElementAt(0).Equals("arg1") && a.ElementAt(1).Equals((RedisKey)"prefix:arg2"))!, CommandFlags.None); + } + + [Fact] + public void GeoAdd_1() + { + prefixed.GeoAdd("key", 1.23, 4.56, "member", CommandFlags.None); + mock.Received().GeoAdd("prefix:key", 1.23, 4.56, "member", CommandFlags.None); + } + + [Fact] + public void GeoAdd_2() + { + var geoEntry = new GeoEntry(1.23, 4.56, "member"); + prefixed.GeoAdd("key", geoEntry, CommandFlags.None); + mock.Received().GeoAdd("prefix:key", geoEntry, CommandFlags.None); + } + + [Fact] + public void GeoAdd_3() + { + var geoEntries = new GeoEntry[] { new GeoEntry(1.23, 4.56, "member1") }; + prefixed.GeoAdd("key", geoEntries, CommandFlags.None); + mock.Received().GeoAdd("prefix:key", geoEntries, CommandFlags.None); + } + + [Fact] + public void GeoRemove() + { + prefixed.GeoRemove("key", "member", CommandFlags.None); + mock.Received().GeoRemove("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public void GeoDistance() + { + prefixed.GeoDistance("key", "member1", "member2", GeoUnit.Meters, CommandFlags.None); + mock.Received().GeoDistance("prefix:key", "member1", "member2", GeoUnit.Meters, CommandFlags.None); + } + + [Fact] + public void GeoHash_1() + { + prefixed.GeoHash("key", "member", CommandFlags.None); + mock.Received().GeoHash("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public void GeoHash_2() + { + var members = new RedisValue[] { "member1", "member2" }; + prefixed.GeoHash("key", members, CommandFlags.None); + mock.Received().GeoHash("prefix:key", members, CommandFlags.None); + } + + [Fact] + public void GeoPosition_1() + { + prefixed.GeoPosition("key", "member", CommandFlags.None); + mock.Received().GeoPosition("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public void GeoPosition_2() + { + var members = new RedisValue[] { "member1", "member2" }; + prefixed.GeoPosition("key", members, CommandFlags.None); + mock.Received().GeoPosition("prefix:key", members, CommandFlags.None); + } + + [Fact] + public void GeoRadius_1() + { + prefixed.GeoRadius("key", "member", 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + mock.Received().GeoRadius("prefix:key", "member", 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public void GeoRadius_2() + { + prefixed.GeoRadius("key", 1.23, 4.56, 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + mock.Received().GeoRadius("prefix:key", 1.23, 4.56, 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public void GeoSearch_1() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + prefixed.GeoSearch("key", "member", shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + mock.Received().GeoSearch("prefix:key", "member", shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public void GeoSearch_2() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + prefixed.GeoSearch("key", 1.23, 4.56, shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + mock.Received().GeoSearch("prefix:key", 1.23, 4.56, shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public void GeoSearchAndStore_1() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + prefixed.GeoSearchAndStore("source", "destination", "member", shape, 10, true, Order.Ascending, false, CommandFlags.None); + mock.Received().GeoSearchAndStore("prefix:source", "prefix:destination", "member", shape, 10, true, Order.Ascending, false, CommandFlags.None); + } + + [Fact] + public void GeoSearchAndStore_2() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + prefixed.GeoSearchAndStore("source", "destination", 1.23, 4.56, shape, 10, true, Order.Ascending, false, CommandFlags.None); + mock.Received().GeoSearchAndStore("prefix:source", "prefix:destination", 1.23, 4.56, shape, 10, true, Order.Ascending, false, CommandFlags.None); + } + + [Fact] + public void HashFieldExpire_1() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + var expiry = TimeSpan.FromSeconds(60); + prefixed.HashFieldExpire("key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + mock.Received().HashFieldExpire("prefix:key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + } + + [Fact] + public void HashFieldExpire_2() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + var expiry = DateTime.Now.AddMinutes(1); + prefixed.HashFieldExpire("key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + mock.Received().HashFieldExpire("prefix:key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + } + + [Fact] + public void HashFieldGetExpireDateTime() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + prefixed.HashFieldGetExpireDateTime("key", hashFields, CommandFlags.None); + mock.Received().HashFieldGetExpireDateTime("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public void HashFieldPersist() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + prefixed.HashFieldPersist("key", hashFields, CommandFlags.None); + mock.Received().HashFieldPersist("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public void HashFieldGetTimeToLive() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + prefixed.HashFieldGetTimeToLive("key", hashFields, CommandFlags.None); + mock.Received().HashFieldGetTimeToLive("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public void HashGetLease() + { + prefixed.HashGetLease("key", "field", CommandFlags.None); + mock.Received().HashGetLease("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public void HashFieldGetAndDelete_1() + { + prefixed.HashFieldGetAndDelete("key", "field", CommandFlags.None); + mock.Received().HashFieldGetAndDelete("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public void HashFieldGetAndDelete_2() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + prefixed.HashFieldGetAndDelete("key", hashFields, CommandFlags.None); + mock.Received().HashFieldGetAndDelete("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public void HashFieldGetLeaseAndDelete() + { + prefixed.HashFieldGetLeaseAndDelete("key", "field", CommandFlags.None); + mock.Received().HashFieldGetLeaseAndDelete("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public void HashFieldGetAndSetExpiry_1() + { + var expiry = TimeSpan.FromMinutes(5); + prefixed.HashFieldGetAndSetExpiry("key", "field", expiry, false, CommandFlags.None); + mock.Received().HashFieldGetAndSetExpiry("prefix:key", "field", expiry, false, CommandFlags.None); + } + + [Fact] + public void HashFieldGetAndSetExpiry_2() + { + var expiry = DateTime.Now.AddMinutes(5); + prefixed.HashFieldGetAndSetExpiry("key", "field", expiry, CommandFlags.None); + mock.Received().HashFieldGetAndSetExpiry("prefix:key", "field", expiry, CommandFlags.None); + } + + [Fact] + public void HashFieldGetLeaseAndSetExpiry_1() + { + var expiry = TimeSpan.FromMinutes(5); + prefixed.HashFieldGetLeaseAndSetExpiry("key", "field", expiry, false, CommandFlags.None); + mock.Received().HashFieldGetLeaseAndSetExpiry("prefix:key", "field", expiry, false, CommandFlags.None); + } + + [Fact] + public void HashFieldGetLeaseAndSetExpiry_2() + { + var expiry = DateTime.Now.AddMinutes(5); + prefixed.HashFieldGetLeaseAndSetExpiry("key", "field", expiry, CommandFlags.None); + mock.Received().HashFieldGetLeaseAndSetExpiry("prefix:key", "field", expiry, CommandFlags.None); + } + [Fact] + public void StringGetLease() + { + prefixed.StringGetLease("key", CommandFlags.None); + mock.Received().StringGetLease("prefix:key", CommandFlags.None); + } + + [Fact] + public void StringGetSetExpiry_1() + { + var expiry = TimeSpan.FromMinutes(5); + prefixed.StringGetSetExpiry("key", expiry, CommandFlags.None); + mock.Received().StringGetSetExpiry("prefix:key", expiry, CommandFlags.None); + } + + [Fact] + public void StringGetSetExpiry_2() + { + var expiry = DateTime.Now.AddMinutes(5); + prefixed.StringGetSetExpiry("key", expiry, CommandFlags.None); + mock.Received().StringGetSetExpiry("prefix:key", expiry, CommandFlags.None); + } + + [Fact] + public void StringSetAndGet_1() + { + var expiry = TimeSpan.FromMinutes(5); + prefixed.StringSetAndGet("key", "value", expiry, When.Always, CommandFlags.None); + mock.Received().StringSetAndGet("prefix:key", "value", expiry, When.Always, CommandFlags.None); + } + + [Fact] + public void StringSetAndGet_2() + { + var expiry = TimeSpan.FromMinutes(5); + prefixed.StringSetAndGet("key", "value", expiry, false, When.Always, CommandFlags.None); + mock.Received().StringSetAndGet("prefix:key", "value", expiry, false, When.Always, CommandFlags.None); + } + [Fact] + public void StringLongestCommonSubsequence() + { + prefixed.StringLongestCommonSubsequence("key1", "key2", CommandFlags.None); + mock.Received().StringLongestCommonSubsequence("prefix:key1", "prefix:key2", CommandFlags.None); + } + + [Fact] + public void StringLongestCommonSubsequenceLength() + { + prefixed.StringLongestCommonSubsequenceLength("key1", "key2", CommandFlags.None); + mock.Received().StringLongestCommonSubsequenceLength("prefix:key1", "prefix:key2", CommandFlags.None); + } + + [Fact] + public void StringLongestCommonSubsequenceWithMatches() + { + prefixed.StringLongestCommonSubsequenceWithMatches("key1", "key2", 5, CommandFlags.None); + mock.Received().StringLongestCommonSubsequenceWithMatches("prefix:key1", "prefix:key2", 5, CommandFlags.None); + } + [Fact] + public void IsConnected() + { + prefixed.IsConnected("key", CommandFlags.None); + mock.Received().IsConnected("prefix:key", CommandFlags.None); + } + [Fact] + public void StreamAdd_WithTrimMode_1() + { + prefixed.StreamAdd("key", "field", "value", "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + mock.Received().StreamAdd("prefix:key", "field", "value", "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public void StreamAdd_WithTrimMode_2() + { + var fields = new NameValueEntry[] { new NameValueEntry("field", "value") }; + prefixed.StreamAdd("key", fields, "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + mock.Received().StreamAdd("prefix:key", fields, "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public void StreamTrim_WithMode() + { + prefixed.StreamTrim("key", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + mock.Received().StreamTrim("prefix:key", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public void StreamTrimByMinId_WithMode() + { + prefixed.StreamTrimByMinId("key", "1111111111", false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + mock.Received().StreamTrimByMinId("prefix:key", "1111111111", false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public void StreamReadGroup_WithNoAck_1() + { + prefixed.StreamReadGroup("key", "group", "consumer", "0-0", 10, true, CommandFlags.None); + mock.Received().StreamReadGroup("prefix:key", "group", "consumer", "0-0", 10, true, CommandFlags.None); + } + + [Fact] + public void StreamReadGroup_WithNoAck_2() + { + var streamPositions = new StreamPosition[] { new StreamPosition("key", "0-0") }; + prefixed.StreamReadGroup(streamPositions, "group", "consumer", 10, true, CommandFlags.None); + mock.Received().StreamReadGroup(streamPositions, "group", "consumer", 10, true, CommandFlags.None); + } + + [Fact] + public void StreamTrim_Simple() + { + prefixed.StreamTrim("key", 1000, true, CommandFlags.None); + mock.Received().StreamTrim("prefix:key", 1000, true, CommandFlags.None); + } + + [Fact] + public void StreamReadGroup_Simple_1() + { + prefixed.StreamReadGroup("key", "group", "consumer", "0-0", 10, CommandFlags.None); + mock.Received().StreamReadGroup("prefix:key", "group", "consumer", "0-0", 10, CommandFlags.None); + } + + [Fact] + public void StreamReadGroup_Simple_2() + { + var streamPositions = new StreamPosition[] { new StreamPosition("key", "0-0") }; + prefixed.StreamReadGroup(streamPositions, "group", "consumer", 10, CommandFlags.None); + mock.Received().StreamReadGroup(streamPositions, "group", "consumer", 10, CommandFlags.None); + } +} diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs new file mode 100644 index 000000000..94b54e112 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs @@ -0,0 +1,1744 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using NSubstitute; +using StackExchange.Redis.KeyspaceIsolation; +using Xunit; +using static StackExchange.Redis.Tests.KeyPrefixedDatabaseTests; // for IsKeys etc + +namespace StackExchange.Redis.Tests +{ + [Collection(nameof(SubstituteDependentCollection))] + public sealed class KeyPrefixedTests + { + private readonly IDatabaseAsync mock; + private readonly KeyPrefixed prefixed; + + public KeyPrefixedTests() + { + mock = Substitute.For(); + prefixed = new KeyPrefixed(mock, Encoding.UTF8.GetBytes("prefix:")); + } + + [Fact] + public async Task DebugObjectAsync() + { + await prefixed.DebugObjectAsync("key", CommandFlags.None); + await mock.Received().DebugObjectAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task HashDecrementAsync_1() + { + await prefixed.HashDecrementAsync("key", "hashField", 123, CommandFlags.None); + await mock.Received().HashDecrementAsync("prefix:key", "hashField", 123, CommandFlags.None); + } + + [Fact] + public async Task HashDecrementAsync_2() + { + await prefixed.HashDecrementAsync("key", "hashField", 1.23, CommandFlags.None); + await mock.Received().HashDecrementAsync("prefix:key", "hashField", 1.23, CommandFlags.None); + } + + [Fact] + public async Task HashDeleteAsync_1() + { + await prefixed.HashDeleteAsync("key", "hashField", CommandFlags.None); + await mock.Received().HashDeleteAsync("prefix:key", "hashField", CommandFlags.None); + } + + [Fact] + public async Task HashDeleteAsync_2() + { + RedisValue[] hashFields = Array.Empty(); + await prefixed.HashDeleteAsync("key", hashFields, CommandFlags.None); + await mock.Received().HashDeleteAsync("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public async Task HashExistsAsync() + { + await prefixed.HashExistsAsync("key", "hashField", CommandFlags.None); + await mock.Received().HashExistsAsync("prefix:key", "hashField", CommandFlags.None); + } + + [Fact] + public async Task HashGetAllAsync() + { + await prefixed.HashGetAllAsync("key", CommandFlags.None); + await mock.Received().HashGetAllAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task HashGetAsync_1() + { + await prefixed.HashGetAsync("key", "hashField", CommandFlags.None); + await mock.Received().HashGetAsync("prefix:key", "hashField", CommandFlags.None); + } + + [Fact] + public async Task HashGetAsync_2() + { + RedisValue[] hashFields = Array.Empty(); + await prefixed.HashGetAsync("key", hashFields, CommandFlags.None); + await mock.Received().HashGetAsync("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public async Task HashIncrementAsync_1() + { + await prefixed.HashIncrementAsync("key", "hashField", 123, CommandFlags.None); + await mock.Received().HashIncrementAsync("prefix:key", "hashField", 123, CommandFlags.None); + } + + [Fact] + public async Task HashIncrementAsync_2() + { + await prefixed.HashIncrementAsync("key", "hashField", 1.23, CommandFlags.None); + await mock.Received().HashIncrementAsync("prefix:key", "hashField", 1.23, CommandFlags.None); + } + + [Fact] + public async Task HashKeysAsync() + { + await prefixed.HashKeysAsync("key", CommandFlags.None); + await mock.Received().HashKeysAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task HashLengthAsync() + { + await prefixed.HashLengthAsync("key", CommandFlags.None); + await mock.Received().HashLengthAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task HashSetAsync_1() + { + HashEntry[] hashFields = Array.Empty(); + await prefixed.HashSetAsync("key", hashFields, CommandFlags.None); + await mock.Received().HashSetAsync("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public async Task HashSetAsync_2() + { + await prefixed.HashSetAsync("key", "hashField", "value", When.Exists, CommandFlags.None); + await mock.Received().HashSetAsync("prefix:key", "hashField", "value", When.Exists, CommandFlags.None); + } + + [Fact] + public async Task HashStringLengthAsync() + { + await prefixed.HashStringLengthAsync("key", "field", CommandFlags.None); + await mock.Received().HashStringLengthAsync("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public async Task HashValuesAsync() + { + await prefixed.HashValuesAsync("key", CommandFlags.None); + await mock.Received().HashValuesAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task HyperLogLogAddAsync_1() + { + await prefixed.HyperLogLogAddAsync("key", "value", CommandFlags.None); + await mock.Received().HyperLogLogAddAsync("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public async Task HyperLogLogAddAsync_2() + { + var values = Array.Empty(); + await prefixed.HyperLogLogAddAsync("key", values, CommandFlags.None); + await mock.Received().HyperLogLogAddAsync("prefix:key", values, CommandFlags.None); + } + + [Fact] + public async Task HyperLogLogLengthAsync() + { + await prefixed.HyperLogLogLengthAsync("key", CommandFlags.None); + await mock.Received().HyperLogLogLengthAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task HyperLogLogMergeAsync_1() + { + await prefixed.HyperLogLogMergeAsync("destination", "first", "second", CommandFlags.None); + await mock.Received().HyperLogLogMergeAsync("prefix:destination", "prefix:first", "prefix:second", CommandFlags.None); + } + + [Fact] + public async Task HyperLogLogMergeAsync_2() + { + RedisKey[] keys = ["a", "b"]; + await prefixed.HyperLogLogMergeAsync("destination", keys, CommandFlags.None); + await mock.Received().HyperLogLogMergeAsync("prefix:destination", IsKeys("prefix:a", "prefix:b"), CommandFlags.None); + } + + [Fact] + public async Task IdentifyEndpointAsync() + { + await prefixed.IdentifyEndpointAsync("key", CommandFlags.None); + await mock.Received().IdentifyEndpointAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public void IsConnected() + { + prefixed.IsConnected("key", CommandFlags.None); + mock.Received().IsConnected("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task KeyCopyAsync() + { + await prefixed.KeyCopyAsync("key", "destination", flags: CommandFlags.None); + await mock.Received().KeyCopyAsync("prefix:key", "prefix:destination", -1, false, CommandFlags.None); + } + + [Fact] + public async Task KeyDeleteAsync_1() + { + await prefixed.KeyDeleteAsync("key", CommandFlags.None); + await mock.Received().KeyDeleteAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task KeyDeleteAsync_2() + { + RedisKey[] keys = ["a", "b"]; + await prefixed.KeyDeleteAsync(keys, CommandFlags.None); + await mock.Received().KeyDeleteAsync(IsKeys("prefix:a", "prefix:b"), CommandFlags.None); + } + + [Fact] + public async Task KeyDumpAsync() + { + await prefixed.KeyDumpAsync("key", CommandFlags.None); + await mock.Received().KeyDumpAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task KeyEncodingAsync() + { + await prefixed.KeyEncodingAsync("key", CommandFlags.None); + await mock.Received().KeyEncodingAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task KeyExistsAsync() + { + await prefixed.KeyExistsAsync("key", CommandFlags.None); + await mock.Received().KeyExistsAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task KeyExpireAsync_1() + { + TimeSpan expiry = TimeSpan.FromSeconds(123); + await prefixed.KeyExpireAsync("key", expiry, CommandFlags.None); + await mock.Received().KeyExpireAsync("prefix:key", expiry, CommandFlags.None); + } + + [Fact] + public async Task KeyExpireAsync_2() + { + DateTime expiry = DateTime.Now; + await prefixed.KeyExpireAsync("key", expiry, CommandFlags.None); + await mock.Received().KeyExpireAsync("prefix:key", expiry, CommandFlags.None); + } + + [Fact] + public async Task KeyExpireAsync_3() + { + TimeSpan expiry = TimeSpan.FromSeconds(123); + await prefixed.KeyExpireAsync("key", expiry, ExpireWhen.HasNoExpiry, CommandFlags.None); + await mock.Received().KeyExpireAsync("prefix:key", expiry, ExpireWhen.HasNoExpiry, CommandFlags.None); + } + + [Fact] + public async Task KeyExpireAsync_4() + { + DateTime expiry = DateTime.Now; + await prefixed.KeyExpireAsync("key", expiry, ExpireWhen.HasNoExpiry, CommandFlags.None); + await mock.Received().KeyExpireAsync("prefix:key", expiry, ExpireWhen.HasNoExpiry, CommandFlags.None); + } + + [Fact] + public async Task KeyExpireTimeAsync() + { + await prefixed.KeyExpireTimeAsync("key", CommandFlags.None); + await mock.Received().KeyExpireTimeAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task KeyFrequencyAsync() + { + await prefixed.KeyFrequencyAsync("key", CommandFlags.None); + await mock.Received().KeyFrequencyAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task KeyMigrateAsync() + { + EndPoint toServer = new IPEndPoint(IPAddress.Loopback, 123); + await prefixed.KeyMigrateAsync("key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.None); + await mock.Received().KeyMigrateAsync("prefix:key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.None); + } + + [Fact] + public async Task KeyMoveAsync() + { + await prefixed.KeyMoveAsync("key", 123, CommandFlags.None); + await mock.Received().KeyMoveAsync("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public async Task KeyPersistAsync() + { + await prefixed.KeyPersistAsync("key", CommandFlags.None); + await mock.Received().KeyPersistAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public Task KeyRandomAsync() + { + return Assert.ThrowsAsync(() => prefixed.KeyRandomAsync()); + } + + [Fact] + public async Task KeyRefCountAsync() + { + await prefixed.KeyRefCountAsync("key", CommandFlags.None); + await mock.Received().KeyRefCountAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task KeyRenameAsync() + { + await prefixed.KeyRenameAsync("key", "newKey", When.Exists, CommandFlags.None); + await mock.Received().KeyRenameAsync("prefix:key", "prefix:newKey", When.Exists, CommandFlags.None); + } + + [Fact] + public async Task KeyRestoreAsync() + { + byte[] value = Array.Empty(); + TimeSpan expiry = TimeSpan.FromSeconds(123); + await prefixed.KeyRestoreAsync("key", value, expiry, CommandFlags.None); + await mock.Received().KeyRestoreAsync("prefix:key", value, expiry, CommandFlags.None); + } + + [Fact] + public async Task KeyTimeToLiveAsync() + { + await prefixed.KeyTimeToLiveAsync("key", CommandFlags.None); + await mock.Received().KeyTimeToLiveAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task KeyTypeAsync() + { + await prefixed.KeyTypeAsync("key", CommandFlags.None); + await mock.Received().KeyTypeAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task ListGetByIndexAsync() + { + await prefixed.ListGetByIndexAsync("key", 123, CommandFlags.None); + await mock.Received().ListGetByIndexAsync("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public async Task ListInsertAfterAsync() + { + await prefixed.ListInsertAfterAsync("key", "pivot", "value", CommandFlags.None); + await mock.Received().ListInsertAfterAsync("prefix:key", "pivot", "value", CommandFlags.None); + } + + [Fact] + public async Task ListInsertBeforeAsync() + { + await prefixed.ListInsertBeforeAsync("key", "pivot", "value", CommandFlags.None); + await mock.Received().ListInsertBeforeAsync("prefix:key", "pivot", "value", CommandFlags.None); + } + + [Fact] + public async Task ListLeftPopAsync() + { + await prefixed.ListLeftPopAsync("key", CommandFlags.None); + await mock.Received().ListLeftPopAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task ListLeftPopAsync_1() + { + await prefixed.ListLeftPopAsync("key", 123, CommandFlags.None); + await mock.Received().ListLeftPopAsync("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public async Task ListLeftPushAsync_1() + { + await prefixed.ListLeftPushAsync("key", "value", When.Exists, CommandFlags.None); + await mock.Received().ListLeftPushAsync("prefix:key", "value", When.Exists, CommandFlags.None); + } + + [Fact] + public async Task ListLeftPushAsync_2() + { + RedisValue[] values = Array.Empty(); + await prefixed.ListLeftPushAsync("key", values, CommandFlags.None); + await mock.Received().ListLeftPushAsync("prefix:key", values, CommandFlags.None); + } + + [Fact] + public async Task ListLeftPushAsync_3() + { + RedisValue[] values = ["value1", "value2"]; + await prefixed.ListLeftPushAsync("key", values, When.Exists, CommandFlags.None); + await mock.Received().ListLeftPushAsync("prefix:key", values, When.Exists, CommandFlags.None); + } + + [Fact] + public async Task ListLengthAsync() + { + await prefixed.ListLengthAsync("key", CommandFlags.None); + await mock.Received().ListLengthAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task ListMoveAsync() + { + await prefixed.ListMoveAsync("key", "destination", ListSide.Left, ListSide.Right, CommandFlags.None); + await mock.Received().ListMoveAsync("prefix:key", "prefix:destination", ListSide.Left, ListSide.Right, CommandFlags.None); + } + + [Fact] + public async Task ListRangeAsync() + { + await prefixed.ListRangeAsync("key", 123, 456, CommandFlags.None); + await mock.Received().ListRangeAsync("prefix:key", 123, 456, CommandFlags.None); + } + + [Fact] + public async Task ListRemoveAsync() + { + await prefixed.ListRemoveAsync("key", "value", 123, CommandFlags.None); + await mock.Received().ListRemoveAsync("prefix:key", "value", 123, CommandFlags.None); + } + + [Fact] + public async Task ListRightPopAsync() + { + await prefixed.ListRightPopAsync("key", CommandFlags.None); + await mock.Received().ListRightPopAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task ListRightPopAsync_1() + { + await prefixed.ListRightPopAsync("key", 123, CommandFlags.None); + await mock.Received().ListRightPopAsync("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public async Task ListRightPopLeftPushAsync() + { + await prefixed.ListRightPopLeftPushAsync("source", "destination", CommandFlags.None); + await mock.Received().ListRightPopLeftPushAsync("prefix:source", "prefix:destination", CommandFlags.None); + } + + [Fact] + public async Task ListRightPushAsync_1() + { + await prefixed.ListRightPushAsync("key", "value", When.Exists, CommandFlags.None); + await mock.Received().ListRightPushAsync("prefix:key", "value", When.Exists, CommandFlags.None); + } + + [Fact] + public async Task ListRightPushAsync_2() + { + RedisValue[] values = Array.Empty(); + await prefixed.ListRightPushAsync("key", values, CommandFlags.None); + await mock.Received().ListRightPushAsync("prefix:key", values, CommandFlags.None); + } + + [Fact] + public async Task ListRightPushAsync_3() + { + RedisValue[] values = ["value1", "value2"]; + await prefixed.ListRightPushAsync("key", values, When.Exists, CommandFlags.None); + await mock.Received().ListRightPushAsync("prefix:key", values, When.Exists, CommandFlags.None); + } + + [Fact] + public async Task ListSetByIndexAsync() + { + await prefixed.ListSetByIndexAsync("key", 123, "value", CommandFlags.None); + await mock.Received().ListSetByIndexAsync("prefix:key", 123, "value", CommandFlags.None); + } + + [Fact] + public async Task ListTrimAsync() + { + await prefixed.ListTrimAsync("key", 123, 456, CommandFlags.None); + await mock.Received().ListTrimAsync("prefix:key", 123, 456, CommandFlags.None); + } + + [Fact] + public async Task LockExtendAsync() + { + TimeSpan expiry = TimeSpan.FromSeconds(123); + await prefixed.LockExtendAsync("key", "value", expiry, CommandFlags.None); + await mock.Received().LockExtendAsync("prefix:key", "value", expiry, CommandFlags.None); + } + + [Fact] + public async Task LockQueryAsync() + { + await prefixed.LockQueryAsync("key", CommandFlags.None); + await mock.Received().LockQueryAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task LockReleaseAsync() + { + await prefixed.LockReleaseAsync("key", "value", CommandFlags.None); + await mock.Received().LockReleaseAsync("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public async Task LockTakeAsync() + { + TimeSpan expiry = TimeSpan.FromSeconds(123); + await prefixed.LockTakeAsync("key", "value", expiry, CommandFlags.None); + await mock.Received().LockTakeAsync("prefix:key", "value", expiry, CommandFlags.None); + } + + [Fact] + public async Task PublishAsync() + { + await prefixed.PublishAsync(RedisChannel.Literal("channel"), "message", CommandFlags.None); + await mock.Received().PublishAsync(RedisChannel.Literal("prefix:channel"), "message", CommandFlags.None); + } + + [Fact] + public async Task ScriptEvaluateAsync_1() + { + byte[] hash = Array.Empty(); + RedisValue[] values = Array.Empty(); + RedisKey[] keys = ["a", "b"]; + await prefixed.ScriptEvaluateAsync(hash, keys, values, CommandFlags.None); + await mock.Received().ScriptEvaluateAsync(hash, IsKeys("prefix:a", "prefix:b"), values, CommandFlags.None); + } + + [Fact] + public async Task ScriptEvaluateAsync_2() + { + RedisValue[] values = Array.Empty(); + RedisKey[] keys = ["a", "b"]; + await prefixed.ScriptEvaluateAsync("script", keys, values, CommandFlags.None); + await mock.Received().ScriptEvaluateAsync(script: "script", keys: IsKeys("prefix:a", "prefix:b"), values: values, flags: CommandFlags.None); + } + + [Fact] + public async Task SetAddAsync_1() + { + await prefixed.SetAddAsync("key", "value", CommandFlags.None); + await mock.Received().SetAddAsync("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public async Task SetAddAsync_2() + { + RedisValue[] values = Array.Empty(); + await prefixed.SetAddAsync("key", values, CommandFlags.None); + await mock.Received().SetAddAsync("prefix:key", values, CommandFlags.None); + } + + [Fact] + public async Task SetCombineAndStoreAsync_1() + { + await prefixed.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", "first", "second", CommandFlags.None); + await mock.Received().SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.None); + } + + [Fact] + public async Task SetCombineAndStoreAsync_2() + { + RedisKey[] keys = ["a", "b"]; + await prefixed.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", keys, CommandFlags.None); + await mock.Received().SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", IsKeys("prefix:a", "prefix:b"), CommandFlags.None); + } + + [Fact] + public async Task SetCombineAsync_1() + { + await prefixed.SetCombineAsync(SetOperation.Intersect, "first", "second", CommandFlags.None); + await mock.Received().SetCombineAsync(SetOperation.Intersect, "prefix:first", "prefix:second", CommandFlags.None); + } + + [Fact] + public async Task SetCombineAsync_2() + { + RedisKey[] keys = ["a", "b"]; + await prefixed.SetCombineAsync(SetOperation.Intersect, keys, CommandFlags.None); + await mock.Received().SetCombineAsync(SetOperation.Intersect, IsKeys("prefix:a", "prefix:b"), CommandFlags.None); + } + + [Fact] + public async Task SetContainsAsync() + { + await prefixed.SetContainsAsync("key", "value", CommandFlags.None); + await mock.Received().SetContainsAsync("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public async Task SetContainsAsync_2() + { + RedisValue[] values = ["value1", "value2"]; + await prefixed.SetContainsAsync("key", values, CommandFlags.None); + await mock.Received().SetContainsAsync("prefix:key", values, CommandFlags.None); + } + + [Fact] + public async Task SetIntersectionLengthAsync() + { + await prefixed.SetIntersectionLengthAsync(["key1", "key2"]); + await mock.Received().SetIntersectionLengthAsync(IsKeys("prefix:key1", "prefix:key2"), 0, CommandFlags.None); + } + + [Fact] + public async Task SetLengthAsync() + { + await prefixed.SetLengthAsync("key", CommandFlags.None); + await mock.Received().SetLengthAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task SetMembersAsync() + { + await prefixed.SetMembersAsync("key", CommandFlags.None); + await mock.Received().SetMembersAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task SetMoveAsync() + { + await prefixed.SetMoveAsync("source", "destination", "value", CommandFlags.None); + await mock.Received().SetMoveAsync("prefix:source", "prefix:destination", "value", CommandFlags.None); + } + + [Fact] + public async Task SetPopAsync_1() + { + await prefixed.SetPopAsync("key", CommandFlags.None); + await mock.Received().SetPopAsync("prefix:key", CommandFlags.None); + + await prefixed.SetPopAsync("key", 5, CommandFlags.None); + await mock.Received().SetPopAsync("prefix:key", 5, CommandFlags.None); + } + + [Fact] + public async Task SetPopAsync_2() + { + await prefixed.SetPopAsync("key", 5, CommandFlags.None); + await mock.Received().SetPopAsync("prefix:key", 5, CommandFlags.None); + } + + [Fact] + public async Task SetRandomMemberAsync() + { + await prefixed.SetRandomMemberAsync("key", CommandFlags.None); + await mock.Received().SetRandomMemberAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task SetRandomMembersAsync() + { + await prefixed.SetRandomMembersAsync("key", 123, CommandFlags.None); + await mock.Received().SetRandomMembersAsync("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public async Task SetRemoveAsync_1() + { + await prefixed.SetRemoveAsync("key", "value", CommandFlags.None); + await mock.Received().SetRemoveAsync("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public async Task SetRemoveAsync_2() + { + RedisValue[] values = Array.Empty(); + await prefixed.SetRemoveAsync("key", values, CommandFlags.None); + await mock.Received().SetRemoveAsync("prefix:key", values, CommandFlags.None); + } + + [Fact] + public async Task SortAndStoreAsync() + { + RedisValue[] get = ["a", "#"]; + + await prefixed.SortAndStoreAsync("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.None); + await prefixed.SortAndStoreAsync("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.None); + + await mock.Received().SortAndStoreAsync("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", IsValues("prefix:a", "#"), CommandFlags.None); + await mock.Received().SortAndStoreAsync("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", IsValues("prefix:a", "#"), CommandFlags.None); + } + + [Fact] + public async Task SortAsync() + { + RedisValue[] get = ["a", "#"]; + + await prefixed.SortAsync("key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.None); + await prefixed.SortAsync("key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.None); + + await mock.Received().SortAsync("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", IsValues("prefix:a", "#"), CommandFlags.None); + await mock.Received().SortAsync("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", IsValues("prefix:a", "#"), CommandFlags.None); + } + + [Fact] + public async Task SortedSetAddAsync_1() + { + await prefixed.SortedSetAddAsync("key", "member", 1.23, When.Exists, CommandFlags.None); + await mock.Received().SortedSetAddAsync("prefix:key", "member", 1.23, When.Exists, CommandFlags.None); + } + + [Fact] + public async Task SortedSetAddAsync_2() + { + SortedSetEntry[] values = Array.Empty(); + await prefixed.SortedSetAddAsync("key", values, When.Exists, CommandFlags.None); + await mock.Received().SortedSetAddAsync("prefix:key", values, When.Exists, CommandFlags.None); + } + + [Fact] + public async Task SortedSetAddAsync_3() + { + SortedSetEntry[] values = Array.Empty(); + await prefixed.SortedSetAddAsync("key", values, SortedSetWhen.GreaterThan, CommandFlags.None); + await mock.Received().SortedSetAddAsync("prefix:key", values, SortedSetWhen.GreaterThan, CommandFlags.None); + } + + [Fact] + public async Task SortedSetCombineAsync() + { + await prefixed.SortedSetCombineAsync(SetOperation.Intersect, ["a", "b"]); + await mock.Received().SortedSetCombineAsync(SetOperation.Intersect, IsKeys("prefix:a", "prefix:b"), null, Aggregate.Sum, CommandFlags.None); + } + + [Fact] + public async Task SortedSetCombineWithScoresAsync() + { + await prefixed.SortedSetCombineWithScoresAsync(SetOperation.Intersect, ["a", "b"]); + await mock.Received().SortedSetCombineWithScoresAsync(SetOperation.Intersect, IsKeys("prefix:a", "prefix:b"), null, Aggregate.Sum, CommandFlags.None); + } + + [Fact] + public async Task SortedSetCombineAndStoreAsync_1() + { + await prefixed.SortedSetCombineAndStoreAsync(SetOperation.Intersect, "destination", "first", "second", Aggregate.Max, CommandFlags.None); + await mock.Received().SortedSetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", Aggregate.Max, CommandFlags.None); + } + + [Fact] + public async Task SortedSetCombineAndStoreAsync_2() + { + RedisKey[] keys = ["a", "b"]; + await prefixed.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", keys, CommandFlags.None); + await mock.Received().SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", IsKeys("prefix:a", "prefix:b"), CommandFlags.None); + } + + [Fact] + public async Task SortedSetDecrementAsync() + { + await prefixed.SortedSetDecrementAsync("key", "member", 1.23, CommandFlags.None); + await mock.Received().SortedSetDecrementAsync("prefix:key", "member", 1.23, CommandFlags.None); + } + + [Fact] + public async Task SortedSetIncrementAsync() + { + await prefixed.SortedSetIncrementAsync("key", "member", 1.23, CommandFlags.None); + await mock.Received().SortedSetIncrementAsync("prefix:key", "member", 1.23, CommandFlags.None); + } + + [Fact] + public async Task SortedSetIntersectionLengthAsync() + { + await prefixed.SortedSetIntersectionLengthAsync(["a", "b"], 1, CommandFlags.None); + await mock.Received().SortedSetIntersectionLengthAsync(IsKeys("prefix:a", "prefix:b"), 1, CommandFlags.None); + } + + [Fact] + public async Task SortedSetLengthAsync() + { + await prefixed.SortedSetLengthAsync("key", 1.23, 1.23, Exclude.Start, CommandFlags.None); + await mock.Received().SortedSetLengthAsync("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.None); + } + + [Fact] + public async Task SortedSetLengthByValueAsync() + { + await prefixed.SortedSetLengthByValueAsync("key", "min", "max", Exclude.Start, CommandFlags.None); + await mock.Received().SortedSetLengthByValueAsync("prefix:key", "min", "max", Exclude.Start, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRandomMemberAsync() + { + await prefixed.SortedSetRandomMemberAsync("key", CommandFlags.None); + await mock.Received().SortedSetRandomMemberAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task SortedSetRandomMembersAsync() + { + await prefixed.SortedSetRandomMembersAsync("key", 2, CommandFlags.None); + await mock.Received().SortedSetRandomMembersAsync("prefix:key", 2, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRandomMemberWithScoresAsync() + { + await prefixed.SortedSetRandomMembersWithScoresAsync("key", 2, CommandFlags.None); + await mock.Received().SortedSetRandomMembersWithScoresAsync("prefix:key", 2, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRangeByRankAsync() + { + await prefixed.SortedSetRangeByRankAsync("key", 123, 456, Order.Descending, CommandFlags.None); + await mock.Received().SortedSetRangeByRankAsync("prefix:key", 123, 456, Order.Descending, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRangeByRankWithScoresAsync() + { + await prefixed.SortedSetRangeByRankWithScoresAsync("key", 123, 456, Order.Descending, CommandFlags.None); + await mock.Received().SortedSetRangeByRankWithScoresAsync("prefix:key", 123, 456, Order.Descending, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRangeByScoreAsync() + { + await prefixed.SortedSetRangeByScoreAsync("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.None); + await mock.Received().SortedSetRangeByScoreAsync("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRangeByScoreWithScoresAsync() + { + await prefixed.SortedSetRangeByScoreWithScoresAsync("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.None); + await mock.Received().SortedSetRangeByScoreWithScoresAsync("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRangeByValueAsync() + { + await prefixed.SortedSetRangeByValueAsync("key", "min", "max", Exclude.Start, 123, 456, CommandFlags.None); + await mock.Received().SortedSetRangeByValueAsync("prefix:key", "min", "max", Exclude.Start, Order.Ascending, 123, 456, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRangeByValueDescAsync() + { + await prefixed.SortedSetRangeByValueAsync("key", "min", "max", Exclude.Start, Order.Descending, 123, 456, CommandFlags.None); + await mock.Received().SortedSetRangeByValueAsync("prefix:key", "min", "max", Exclude.Start, Order.Descending, 123, 456, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRankAsync() + { + await prefixed.SortedSetRankAsync("key", "member", Order.Descending, CommandFlags.None); + await mock.Received().SortedSetRankAsync("prefix:key", "member", Order.Descending, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRemoveAsync_1() + { + await prefixed.SortedSetRemoveAsync("key", "member", CommandFlags.None); + await mock.Received().SortedSetRemoveAsync("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public async Task SortedSetRemoveAsync_2() + { + RedisValue[] members = Array.Empty(); + await prefixed.SortedSetRemoveAsync("key", members, CommandFlags.None); + await mock.Received().SortedSetRemoveAsync("prefix:key", members, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRemoveRangeByRankAsync() + { + await prefixed.SortedSetRemoveRangeByRankAsync("key", 123, 456, CommandFlags.None); + await mock.Received().SortedSetRemoveRangeByRankAsync("prefix:key", 123, 456, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRemoveRangeByScoreAsync() + { + await prefixed.SortedSetRemoveRangeByScoreAsync("key", 1.23, 1.23, Exclude.Start, CommandFlags.None); + await mock.Received().SortedSetRemoveRangeByScoreAsync("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.None); + } + + [Fact] + public async Task SortedSetRemoveRangeByValueAsync() + { + await prefixed.SortedSetRemoveRangeByValueAsync("key", "min", "max", Exclude.Start, CommandFlags.None); + await mock.Received().SortedSetRemoveRangeByValueAsync("prefix:key", "min", "max", Exclude.Start, CommandFlags.None); + } + + [Fact] + public async Task SortedSetScoreAsync() + { + await prefixed.SortedSetScoreAsync("key", "member", CommandFlags.None); + await mock.Received().SortedSetScoreAsync("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public async Task SortedSetScoreAsync_Multiple() + { + var values = new RedisValue[] { "member1", "member2" }; + await prefixed.SortedSetScoresAsync("key", values, CommandFlags.None); + await mock.Received().SortedSetScoresAsync("prefix:key", values, CommandFlags.None); + } + + [Fact] + public async Task SortedSetUpdateAsync() + { + SortedSetEntry[] values = Array.Empty(); + await prefixed.SortedSetUpdateAsync("key", values, SortedSetWhen.GreaterThan, CommandFlags.None); + await mock.Received().SortedSetUpdateAsync("prefix:key", values, SortedSetWhen.GreaterThan, CommandFlags.None); + } + + [Fact] + public async Task StreamAcknowledgeAsync_1() + { + await prefixed.StreamAcknowledgeAsync("key", "group", "0-0", CommandFlags.None); + await mock.Received().StreamAcknowledgeAsync("prefix:key", "group", "0-0", CommandFlags.None); + } + + [Fact] + public async Task StreamAcknowledgeAsync_2() + { + var messageIds = new RedisValue[] { "0-0", "0-1", "0-2" }; + await prefixed.StreamAcknowledgeAsync("key", "group", messageIds, CommandFlags.None); + await mock.Received().StreamAcknowledgeAsync("prefix:key", "group", messageIds, CommandFlags.None); + } + + [Fact] + public async Task StreamAddAsync_1() + { + await prefixed.StreamAddAsync("key", "field1", "value1", "*", 1000, true, CommandFlags.None); + await mock.Received().StreamAddAsync("prefix:key", "field1", "value1", "*", 1000, true, CommandFlags.None); + } + + [Fact] + public async Task StreamAddAsync_2() + { + var fields = Array.Empty(); + await prefixed.StreamAddAsync("key", fields, "*", 1000, true, CommandFlags.None); + await mock.Received().StreamAddAsync("prefix:key", fields, "*", 1000, true, CommandFlags.None); + } + + [Fact] + public async Task StreamAutoClaimAsync() + { + await prefixed.StreamAutoClaimAsync("key", "group", "consumer", 0, "0-0", 100, CommandFlags.None); + await mock.Received().StreamAutoClaimAsync("prefix:key", "group", "consumer", 0, "0-0", 100, CommandFlags.None); + } + + [Fact] + public async Task StreamAutoClaimIdsOnlyAsync() + { + await prefixed.StreamAutoClaimIdsOnlyAsync("key", "group", "consumer", 0, "0-0", 100, CommandFlags.None); + await mock.Received().StreamAutoClaimIdsOnlyAsync("prefix:key", "group", "consumer", 0, "0-0", 100, CommandFlags.None); + } + + [Fact] + public async Task StreamClaimMessagesAsync() + { + var messageIds = Array.Empty(); + await prefixed.StreamClaimAsync("key", "group", "consumer", 1000, messageIds, CommandFlags.None); + await mock.Received().StreamClaimAsync("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.None); + } + + [Fact] + public async Task StreamClaimMessagesReturningIdsAsync() + { + var messageIds = Array.Empty(); + await prefixed.StreamClaimIdsOnlyAsync("key", "group", "consumer", 1000, messageIds, CommandFlags.None); + await mock.Received().StreamClaimIdsOnlyAsync("prefix:key", "group", "consumer", 1000, messageIds, CommandFlags.None); + } + + [Fact] + public async Task StreamConsumerInfoGetAsync() + { + await prefixed.StreamConsumerInfoAsync("key", "group", CommandFlags.None); + await mock.Received().StreamConsumerInfoAsync("prefix:key", "group", CommandFlags.None); + } + + [Fact] + public async Task StreamConsumerGroupSetPositionAsync() + { + await prefixed.StreamConsumerGroupSetPositionAsync("key", "group", StreamPosition.Beginning, CommandFlags.None); + await mock.Received().StreamConsumerGroupSetPositionAsync("prefix:key", "group", StreamPosition.Beginning, CommandFlags.None); + } + + [Fact] + public async Task StreamCreateConsumerGroupAsync() + { + await prefixed.StreamCreateConsumerGroupAsync("key", "group", "0-0", false, CommandFlags.None); + await mock.Received().StreamCreateConsumerGroupAsync("prefix:key", "group", "0-0", false, CommandFlags.None); + } + + [Fact] + public async Task StreamGroupInfoGetAsync() + { + await prefixed.StreamGroupInfoAsync("key", CommandFlags.None); + await mock.Received().StreamGroupInfoAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task StreamInfoGetAsync() + { + await prefixed.StreamInfoAsync("key", CommandFlags.None); + await mock.Received().StreamInfoAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task StreamLengthAsync() + { + await prefixed.StreamLengthAsync("key", CommandFlags.None); + await mock.Received().StreamLengthAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task StreamMessagesDeleteAsync() + { + var messageIds = Array.Empty(); + await prefixed.StreamDeleteAsync("key", messageIds, CommandFlags.None); + await mock.Received().StreamDeleteAsync("prefix:key", messageIds, CommandFlags.None); + } + + [Fact] + public async Task StreamDeleteConsumerAsync() + { + await prefixed.StreamDeleteConsumerAsync("key", "group", "consumer", CommandFlags.None); + await mock.Received().StreamDeleteConsumerAsync("prefix:key", "group", "consumer", CommandFlags.None); + } + + [Fact] + public async Task StreamDeleteConsumerGroupAsync() + { + await prefixed.StreamDeleteConsumerGroupAsync("key", "group", CommandFlags.None); + await mock.Received().StreamDeleteConsumerGroupAsync("prefix:key", "group", CommandFlags.None); + } + + [Fact] + public async Task StreamPendingInfoGetAsync() + { + await prefixed.StreamPendingAsync("key", "group", CommandFlags.None); + await mock.Received().StreamPendingAsync("prefix:key", "group", CommandFlags.None); + } + + [Fact] + public async Task StreamPendingMessageInfoGetAsync() + { + await prefixed.StreamPendingMessagesAsync("key", "group", 10, RedisValue.Null, "-", "+", 1000, CommandFlags.None); + await mock.Received().StreamPendingMessagesAsync("prefix:key", "group", 10, RedisValue.Null, "-", "+", 1000, CommandFlags.None); + } + + [Fact] + public async Task StreamRangeAsync() + { + await prefixed.StreamRangeAsync("key", "-", "+", null, Order.Ascending, CommandFlags.None); + await mock.Received().StreamRangeAsync("prefix:key", "-", "+", null, Order.Ascending, CommandFlags.None); + } + + [Fact] + public async Task StreamReadAsync_1() + { + var streamPositions = Array.Empty(); + await prefixed.StreamReadAsync(streamPositions, null, CommandFlags.None); + await mock.Received().StreamReadAsync(streamPositions, null, CommandFlags.None); + } + + [Fact] + public async Task StreamReadAsync_2() + { + await prefixed.StreamReadAsync("key", "0-0", null, CommandFlags.None); + await mock.Received().StreamReadAsync("prefix:key", "0-0", null, CommandFlags.None); + } + + [Fact] + public async Task StreamReadGroupAsync_1() + { + await prefixed.StreamReadGroupAsync("key", "group", "consumer", StreamPosition.Beginning, 10, false, CommandFlags.None); + await mock.Received().StreamReadGroupAsync("prefix:key", "group", "consumer", StreamPosition.Beginning, 10, false, CommandFlags.None); + } + + [Fact] + public async Task StreamStreamReadGroupAsync_2() + { + var streamPositions = Array.Empty(); + await prefixed.StreamReadGroupAsync(streamPositions, "group", "consumer", 10, false, CommandFlags.None); + await mock.Received().StreamReadGroupAsync(streamPositions, "group", "consumer", 10, false, CommandFlags.None); + } + + [Fact] + public async Task StreamTrimAsync() + { + await prefixed.StreamTrimAsync("key", 1000, true, CommandFlags.None); + await mock.Received().StreamTrimAsync("prefix:key", 1000, true, CommandFlags.None); + } + + [Fact] + public async Task StreamTrimByMinIdAsync() + { + await prefixed.StreamTrimByMinIdAsync("key", 1111111111); + await mock.Received().StreamTrimByMinIdAsync("prefix:key", 1111111111); + } + + [Fact] + public async Task StreamTrimByMinIdAsyncWithApproximate() + { + await prefixed.StreamTrimByMinIdAsync("key", 1111111111, useApproximateMaxLength: true); + await mock.Received().StreamTrimByMinIdAsync("prefix:key", 1111111111, useApproximateMaxLength: true); + } + + [Fact] + public async Task StreamTrimByMinIdAsyncWithApproximateAndLimit() + { + await prefixed.StreamTrimByMinIdAsync("key", 1111111111, useApproximateMaxLength: true, limit: 100); + await mock.Received().StreamTrimByMinIdAsync("prefix:key", 1111111111, useApproximateMaxLength: true, limit: 100); + } + + [Fact] + public async Task StringAppendAsync() + { + await prefixed.StringAppendAsync("key", "value", CommandFlags.None); + await mock.Received().StringAppendAsync("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public async Task StringBitCountAsync() + { + await prefixed.StringBitCountAsync("key", 123, 456, CommandFlags.None); + await mock.Received().StringBitCountAsync("prefix:key", 123, 456, CommandFlags.None); + } + + [Fact] + public async Task StringBitCountAsync_2() + { + await prefixed.StringBitCountAsync("key", 123, 456, StringIndexType.Byte, CommandFlags.None); + await mock.Received().StringBitCountAsync("prefix:key", 123, 456, StringIndexType.Byte, CommandFlags.None); + } + + [Fact] + public async Task StringBitOperationAsync_1() + { + await prefixed.StringBitOperationAsync(Bitwise.Xor, "destination", "first", "second", CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.Xor, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.None); + } + + [Fact] + public async Task StringBitOperationAsync_2() + { + RedisKey[] keys = ["a", "b"]; + await prefixed.StringBitOperationAsync(Bitwise.Xor, "destination", keys, CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.Xor, "prefix:destination", IsKeys("prefix:a", "prefix:b"), CommandFlags.None); + } + + [Fact] + public async Task StringBitOperationAsync_Diff() + { + RedisKey[] keys = ["x", "y1", "y2"]; + await prefixed.StringBitOperationAsync(Bitwise.Diff, "destination", keys, CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.Diff, "prefix:destination", IsKeys("prefix:x", "prefix:y1", "prefix:y2"), CommandFlags.None); + } + + [Fact] + public async Task StringBitOperationAsync_Diff1() + { + RedisKey[] keys = ["x", "y1", "y2"]; + await prefixed.StringBitOperationAsync(Bitwise.Diff1, "destination", keys, CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.Diff1, "prefix:destination", IsKeys("prefix:x", "prefix:y1", "prefix:y2"), CommandFlags.None); + } + + [Fact] + public async Task StringBitOperationAsync_AndOr() + { + RedisKey[] keys = ["x", "y1", "y2"]; + await prefixed.StringBitOperationAsync(Bitwise.AndOr, "destination", keys, CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.AndOr, "prefix:destination", IsKeys("prefix:x", "prefix:y1", "prefix:y2"), CommandFlags.None); + } + + [Fact] + public async Task StringBitOperationAsync_One() + { + RedisKey[] keys = ["a", "b", "c"]; + await prefixed.StringBitOperationAsync(Bitwise.One, "destination", keys, CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.One, "prefix:destination", IsKeys("prefix:a", "prefix:b", "prefix:c"), CommandFlags.None); + } + + [Fact] + public async Task StringBitPositionAsync() + { + await prefixed.StringBitPositionAsync("key", true, 123, 456, CommandFlags.None); + await mock.Received().StringBitPositionAsync("prefix:key", true, 123, 456, CommandFlags.None); + } + + [Fact] + public async Task StringBitPositionAsync_2() + { + await prefixed.StringBitPositionAsync("key", true, 123, 456, StringIndexType.Byte, CommandFlags.None); + await mock.Received().StringBitPositionAsync("prefix:key", true, 123, 456, StringIndexType.Byte, CommandFlags.None); + } + + [Fact] + public async Task StringDecrementAsync_1() + { + await prefixed.StringDecrementAsync("key", 123, CommandFlags.None); + await mock.Received().StringDecrementAsync("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public async Task StringDecrementAsync_2() + { + await prefixed.StringDecrementAsync("key", 1.23, CommandFlags.None); + await mock.Received().StringDecrementAsync("prefix:key", 1.23, CommandFlags.None); + } + + [Fact] + public async Task StringGetAsync_1() + { + await prefixed.StringGetAsync("key", CommandFlags.None); + await mock.Received().StringGetAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task StringGetAsync_2() + { + RedisKey[] keys = ["a", "b"]; + await prefixed.StringGetAsync(keys, CommandFlags.None); + await mock.Received().StringGetAsync(IsKeys("prefix:a", "prefix:b"), CommandFlags.None); + } + + [Fact] + public async Task StringGetBitAsync() + { + await prefixed.StringGetBitAsync("key", 123, CommandFlags.None); + await mock.Received().StringGetBitAsync("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public async Task StringGetRangeAsync() + { + await prefixed.StringGetRangeAsync("key", 123, 456, CommandFlags.None); + await mock.Received().StringGetRangeAsync("prefix:key", 123, 456, CommandFlags.None); + } + + [Fact] + public async Task StringGetSetAsync() + { + await prefixed.StringGetSetAsync("key", "value", CommandFlags.None); + await mock.Received().StringGetSetAsync("prefix:key", "value", CommandFlags.None); + } + + [Fact] + public async Task StringGetDeleteAsync() + { + await prefixed.StringGetDeleteAsync("key", CommandFlags.None); + await mock.Received().StringGetDeleteAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task StringGetWithExpiryAsync() + { + await prefixed.StringGetWithExpiryAsync("key", CommandFlags.None); + await mock.Received().StringGetWithExpiryAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task StringIncrementAsync_1() + { + await prefixed.StringIncrementAsync("key", 123, CommandFlags.None); + await mock.Received().StringIncrementAsync("prefix:key", 123, CommandFlags.None); + } + + [Fact] + public async Task StringIncrementAsync_2() + { + await prefixed.StringIncrementAsync("key", 1.23, CommandFlags.None); + await mock.Received().StringIncrementAsync("prefix:key", 1.23, CommandFlags.None); + } + + [Fact] + public async Task StringLengthAsync() + { + await prefixed.StringLengthAsync("key", CommandFlags.None); + await mock.Received().StringLengthAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task StringSetAsync_1() + { + TimeSpan expiry = TimeSpan.FromSeconds(123); + await prefixed.StringSetAsync("key", "value", expiry, When.Exists, CommandFlags.None); + await mock.Received().StringSetAsync("prefix:key", "value", expiry, When.Exists, CommandFlags.None); + } + + [Fact] + public async Task StringSetAsync_2() + { + TimeSpan? expiry = null; + await prefixed.StringSetAsync("key", "value", expiry, true, When.Exists, CommandFlags.None); + await mock.Received().StringSetAsync("prefix:key", "value", expiry, true, When.Exists, CommandFlags.None); + } + + [Fact] + public async Task StringSetAsync_3() + { + KeyValuePair[] values = [new KeyValuePair("a", "x"), new KeyValuePair("b", "y")]; + Expression[]>> valid = _ => _.Length == 2 && _[0].Key == "prefix:a" && _[0].Value == "x" && _[1].Key == "prefix:b" && _[1].Value == "y"; + await prefixed.StringSetAsync(values, When.Exists, CommandFlags.None); + await mock.Received().StringSetAsync(Arg.Is(valid), When.Exists, CommandFlags.None); + } + + [Fact] + public async Task StringSetAsync_Compat() + { + TimeSpan expiry = TimeSpan.FromSeconds(123); + await prefixed.StringSetAsync("key", "value", expiry, When.Exists); + await mock.Received().StringSetAsync("prefix:key", "value", expiry, When.Exists); + } + + [Fact] + public async Task StringSetBitAsync() + { + await prefixed.StringSetBitAsync("key", 123, true, CommandFlags.None); + await mock.Received().StringSetBitAsync("prefix:key", 123, true, CommandFlags.None); + } + + [Fact] + public async Task StringSetRangeAsync() + { + await prefixed.StringSetRangeAsync("key", 123, "value", CommandFlags.None); + await mock.Received().StringSetRangeAsync("prefix:key", 123, "value", CommandFlags.None); + } + + [Fact] + public async Task KeyTouchAsync_1() + { + await prefixed.KeyTouchAsync("key", CommandFlags.None); + await mock.Received().KeyTouchAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task KeyTouchAsync_2() + { + RedisKey[] keys = ["a", "b"]; + await prefixed.KeyTouchAsync(keys, CommandFlags.None); + await mock.Received().KeyTouchAsync(IsKeys("prefix:a", "prefix:b"), CommandFlags.None); + } + [Fact] + public async Task ExecuteAsync_1() + { + await prefixed.ExecuteAsync("CUSTOM", "arg1", (RedisKey)"arg2"); + await mock.Received().ExecuteAsync("CUSTOM", Arg.Is(args => args.Length == 2 && args[0].Equals("arg1") && args[1].Equals((RedisKey)"prefix:arg2")), CommandFlags.None); + } + + [Fact] + public async Task ExecuteAsync_2() + { + var args = new List { "arg1", (RedisKey)"arg2" }; + await prefixed.ExecuteAsync("CUSTOM", args, CommandFlags.None); + await mock.Received().ExecuteAsync("CUSTOM", Arg.Is?>(a => a != null && a.Count == 2 && a.ElementAt(0).Equals("arg1") && a.ElementAt(1).Equals((RedisKey)"prefix:arg2")), CommandFlags.None); + } + [Fact] + public async Task GeoAddAsync_1() + { + await prefixed.GeoAddAsync("key", 1.23, 4.56, "member", CommandFlags.None); + await mock.Received().GeoAddAsync("prefix:key", 1.23, 4.56, "member", CommandFlags.None); + } + + [Fact] + public async Task GeoAddAsync_2() + { + var geoEntry = new GeoEntry(1.23, 4.56, "member"); + await prefixed.GeoAddAsync("key", geoEntry, CommandFlags.None); + await mock.Received().GeoAddAsync("prefix:key", geoEntry, CommandFlags.None); + } + + [Fact] + public async Task GeoAddAsync_3() + { + var geoEntries = new GeoEntry[] { new GeoEntry(1.23, 4.56, "member1") }; + await prefixed.GeoAddAsync("key", geoEntries, CommandFlags.None); + await mock.Received().GeoAddAsync("prefix:key", geoEntries, CommandFlags.None); + } + + [Fact] + public async Task GeoRemoveAsync() + { + await prefixed.GeoRemoveAsync("key", "member", CommandFlags.None); + await mock.Received().GeoRemoveAsync("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public async Task GeoDistanceAsync() + { + await prefixed.GeoDistanceAsync("key", "member1", "member2", GeoUnit.Meters, CommandFlags.None); + await mock.Received().GeoDistanceAsync("prefix:key", "member1", "member2", GeoUnit.Meters, CommandFlags.None); + } + + [Fact] + public async Task GeoHashAsync_1() + { + await prefixed.GeoHashAsync("key", "member", CommandFlags.None); + await mock.Received().GeoHashAsync("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public async Task GeoHashAsync_2() + { + var members = new RedisValue[] { "member1", "member2" }; + await prefixed.GeoHashAsync("key", members, CommandFlags.None); + await mock.Received().GeoHashAsync("prefix:key", members, CommandFlags.None); + } + + [Fact] + public async Task GeoPositionAsync_1() + { + await prefixed.GeoPositionAsync("key", "member", CommandFlags.None); + await mock.Received().GeoPositionAsync("prefix:key", "member", CommandFlags.None); + } + + [Fact] + public async Task GeoPositionAsync_2() + { + var members = new RedisValue[] { "member1", "member2" }; + await prefixed.GeoPositionAsync("key", members, CommandFlags.None); + await mock.Received().GeoPositionAsync("prefix:key", members, CommandFlags.None); + } + + [Fact] + public async Task GeoRadiusAsync_1() + { + await prefixed.GeoRadiusAsync("key", "member", 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + await mock.Received().GeoRadiusAsync("prefix:key", "member", 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public async Task GeoRadiusAsync_2() + { + await prefixed.GeoRadiusAsync("key", 1.23, 4.56, 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + await mock.Received().GeoRadiusAsync("prefix:key", 1.23, 4.56, 100, GeoUnit.Meters, 10, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public async Task GeoSearchAsync_1() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + await prefixed.GeoSearchAsync("key", "member", shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + await mock.Received().GeoSearchAsync("prefix:key", "member", shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public async Task GeoSearchAsync_2() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + await prefixed.GeoSearchAsync("key", 1.23, 4.56, shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + await mock.Received().GeoSearchAsync("prefix:key", 1.23, 4.56, shape, 10, true, Order.Ascending, GeoRadiusOptions.Default, CommandFlags.None); + } + + [Fact] + public async Task GeoSearchAndStoreAsync_1() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + await prefixed.GeoSearchAndStoreAsync("source", "destination", "member", shape, 10, true, Order.Ascending, false, CommandFlags.None); + await mock.Received().GeoSearchAndStoreAsync("prefix:source", "prefix:destination", "member", shape, 10, true, Order.Ascending, false, CommandFlags.None); + } + + [Fact] + public async Task GeoSearchAndStoreAsync_2() + { + var shape = new GeoSearchCircle(100, GeoUnit.Meters); + await prefixed.GeoSearchAndStoreAsync("source", "destination", 1.23, 4.56, shape, 10, true, Order.Ascending, false, CommandFlags.None); + await mock.Received().GeoSearchAndStoreAsync("prefix:source", "prefix:destination", 1.23, 4.56, shape, 10, true, Order.Ascending, false, CommandFlags.None); + } + [Fact] + public async Task HashFieldExpireAsync_1() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + var expiry = TimeSpan.FromSeconds(60); + await prefixed.HashFieldExpireAsync("key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + await mock.Received().HashFieldExpireAsync("prefix:key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + } + + [Fact] + public async Task HashFieldExpireAsync_2() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + var expiry = DateTime.Now.AddMinutes(1); + await prefixed.HashFieldExpireAsync("key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + await mock.Received().HashFieldExpireAsync("prefix:key", hashFields, expiry, ExpireWhen.Always, CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetExpireDateTimeAsync() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + await prefixed.HashFieldGetExpireDateTimeAsync("key", hashFields, CommandFlags.None); + await mock.Received().HashFieldGetExpireDateTimeAsync("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public async Task HashFieldPersistAsync() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + await prefixed.HashFieldPersistAsync("key", hashFields, CommandFlags.None); + await mock.Received().HashFieldPersistAsync("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetTimeToLiveAsync() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + await prefixed.HashFieldGetTimeToLiveAsync("key", hashFields, CommandFlags.None); + await mock.Received().HashFieldGetTimeToLiveAsync("prefix:key", hashFields, CommandFlags.None); + } + [Fact] + public async Task HashGetLeaseAsync() + { + await prefixed.HashGetLeaseAsync("key", "field", CommandFlags.None); + await mock.Received().HashGetLeaseAsync("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetAndDeleteAsync_1() + { + await prefixed.HashFieldGetAndDeleteAsync("key", "field", CommandFlags.None); + await mock.Received().HashFieldGetAndDeleteAsync("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetAndDeleteAsync_2() + { + var hashFields = new RedisValue[] { "field1", "field2" }; + await prefixed.HashFieldGetAndDeleteAsync("key", hashFields, CommandFlags.None); + await mock.Received().HashFieldGetAndDeleteAsync("prefix:key", hashFields, CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetLeaseAndDeleteAsync() + { + await prefixed.HashFieldGetLeaseAndDeleteAsync("key", "field", CommandFlags.None); + await mock.Received().HashFieldGetLeaseAndDeleteAsync("prefix:key", "field", CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetAndSetExpiryAsync_1() + { + var expiry = TimeSpan.FromMinutes(5); + await prefixed.HashFieldGetAndSetExpiryAsync("key", "field", expiry, false, CommandFlags.None); + await mock.Received().HashFieldGetAndSetExpiryAsync("prefix:key", "field", expiry, false, CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetAndSetExpiryAsync_2() + { + var expiry = DateTime.Now.AddMinutes(5); + await prefixed.HashFieldGetAndSetExpiryAsync("key", "field", expiry, CommandFlags.None); + await mock.Received().HashFieldGetAndSetExpiryAsync("prefix:key", "field", expiry, CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetLeaseAndSetExpiryAsync_1() + { + var expiry = TimeSpan.FromMinutes(5); + await prefixed.HashFieldGetLeaseAndSetExpiryAsync("key", "field", expiry, false, CommandFlags.None); + await mock.Received().HashFieldGetLeaseAndSetExpiryAsync("prefix:key", "field", expiry, false, CommandFlags.None); + } + + [Fact] + public async Task HashFieldGetLeaseAndSetExpiryAsync_2() + { + var expiry = DateTime.Now.AddMinutes(5); + await prefixed.HashFieldGetLeaseAndSetExpiryAsync("key", "field", expiry, CommandFlags.None); + await mock.Received().HashFieldGetLeaseAndSetExpiryAsync("prefix:key", "field", expiry, CommandFlags.None); + } + [Fact] + public async Task StringGetLeaseAsync() + { + await prefixed.StringGetLeaseAsync("key", CommandFlags.None); + await mock.Received().StringGetLeaseAsync("prefix:key", CommandFlags.None); + } + + [Fact] + public async Task StringGetSetExpiryAsync_1() + { + var expiry = TimeSpan.FromMinutes(5); + await prefixed.StringGetSetExpiryAsync("key", expiry, CommandFlags.None); + await mock.Received().StringGetSetExpiryAsync("prefix:key", expiry, CommandFlags.None); + } + + [Fact] + public async Task StringGetSetExpiryAsync_2() + { + var expiry = DateTime.Now.AddMinutes(5); + await prefixed.StringGetSetExpiryAsync("key", expiry, CommandFlags.None); + await mock.Received().StringGetSetExpiryAsync("prefix:key", expiry, CommandFlags.None); + } + + [Fact] + public async Task StringSetAndGetAsync_1() + { + var expiry = TimeSpan.FromMinutes(5); + await prefixed.StringSetAndGetAsync("key", "value", expiry, When.Always, CommandFlags.None); + await mock.Received().StringSetAndGetAsync("prefix:key", "value", expiry, When.Always, CommandFlags.None); + } + + [Fact] + public async Task StringSetAndGetAsync_2() + { + var expiry = TimeSpan.FromMinutes(5); + await prefixed.StringSetAndGetAsync("key", "value", expiry, false, When.Always, CommandFlags.None); + await mock.Received().StringSetAndGetAsync("prefix:key", "value", expiry, false, When.Always, CommandFlags.None); + } + [Fact] + public async Task StringLongestCommonSubsequenceAsync() + { + await prefixed.StringLongestCommonSubsequenceAsync("key1", "key2", CommandFlags.None); + await mock.Received().StringLongestCommonSubsequenceAsync("prefix:key1", "prefix:key2", CommandFlags.None); + } + + [Fact] + public async Task StringLongestCommonSubsequenceLengthAsync() + { + await prefixed.StringLongestCommonSubsequenceLengthAsync("key1", "key2", CommandFlags.None); + await mock.Received().StringLongestCommonSubsequenceLengthAsync("prefix:key1", "prefix:key2", CommandFlags.None); + } + + [Fact] + public async Task StringLongestCommonSubsequenceWithMatchesAsync() + { + await prefixed.StringLongestCommonSubsequenceWithMatchesAsync("key1", "key2", 5, CommandFlags.None); + await mock.Received().StringLongestCommonSubsequenceWithMatchesAsync("prefix:key1", "prefix:key2", 5, CommandFlags.None); + } + [Fact] + public async Task KeyIdleTimeAsync() + { + await prefixed.KeyIdleTimeAsync("key", CommandFlags.None); + await mock.Received().KeyIdleTimeAsync("prefix:key", CommandFlags.None); + } + [Fact] + public async Task StreamAddAsync_WithTrimMode_1() + { + await prefixed.StreamAddAsync("key", "field", "value", "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + await mock.Received().StreamAddAsync("prefix:key", "field", "value", "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public async Task StreamAddAsync_WithTrimMode_2() + { + var fields = new NameValueEntry[] { new NameValueEntry("field", "value") }; + await prefixed.StreamAddAsync("key", fields, "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + await mock.Received().StreamAddAsync("prefix:key", fields, "*", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public async Task StreamTrimAsync_WithMode() + { + await prefixed.StreamTrimAsync("key", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + await mock.Received().StreamTrimAsync("prefix:key", 1000, false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public async Task StreamTrimByMinIdAsync_WithMode() + { + await prefixed.StreamTrimByMinIdAsync("key", "1111111111", false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + await mock.Received().StreamTrimByMinIdAsync("prefix:key", "1111111111", false, 100, StreamTrimMode.KeepReferences, CommandFlags.None); + } + + [Fact] + public async Task StreamReadGroupAsync_WithNoAck_1() + { + await prefixed.StreamReadGroupAsync("key", "group", "consumer", "0-0", 10, true, CommandFlags.None); + await mock.Received().StreamReadGroupAsync("prefix:key", "group", "consumer", "0-0", 10, true, CommandFlags.None); + } + + [Fact] + public async Task StreamReadGroupAsync_WithNoAck_2() + { + var streamPositions = new StreamPosition[] { new StreamPosition("key", "0-0") }; + await prefixed.StreamReadGroupAsync(streamPositions, "group", "consumer", 10, true, CommandFlags.None); + await mock.Received().StreamReadGroupAsync(streamPositions, "group", "consumer", 10, true, CommandFlags.None); + } + + [Fact] + public async Task StreamTrimAsync_Simple() + { + await prefixed.StreamTrimAsync("key", 1000, true, CommandFlags.None); + await mock.Received().StreamTrimAsync("prefix:key", 1000, true, CommandFlags.None); + } + + [Fact] + public async Task StreamReadGroupAsync_Simple_1() + { + await prefixed.StreamReadGroupAsync("key", "group", "consumer", "0-0", 10, CommandFlags.None); + await mock.Received().StreamReadGroupAsync("prefix:key", "group", "consumer", "0-0", 10, CommandFlags.None); + } + + [Fact] + public async Task StreamReadGroupAsync_Simple_2() + { + var streamPositions = new StreamPosition[] { new StreamPosition("key", "0-0") }; + await prefixed.StreamReadGroupAsync(streamPositions, "group", "consumer", 10, CommandFlags.None); + await mock.Received().StreamReadGroupAsync(streamPositions, "group", "consumer", 10, CommandFlags.None); + } + + [Fact] + public void HashScanAsync() + { + var result = prefixed.HashScanAsync("key", "pattern*", 10, 1, 2, CommandFlags.None); + _ = mock.Received().HashScanAsync("prefix:key", "pattern*", 10, 1, 2, CommandFlags.None); + } + + [Fact] + public void HashScanNoValuesAsync() + { + var result = prefixed.HashScanNoValuesAsync("key", "pattern*", 10, 1, 2, CommandFlags.None); + _ = mock.Received().HashScanNoValuesAsync("prefix:key", "pattern*", 10, 1, 2, CommandFlags.None); + } + + [Fact] + public void SetScanAsync() + { + var result = prefixed.SetScanAsync("key", "pattern*", 10, 1, 2, CommandFlags.None); + _ = mock.Received().SetScanAsync("prefix:key", "pattern*", 10, 1, 2, CommandFlags.None); + } + + [Fact] + public void SortedSetScanAsync() + { + var result = prefixed.SortedSetScanAsync("key", "pattern*", 10, 1, 2, CommandFlags.None); + _ = mock.Received().SortedSetScanAsync("prefix:key", "pattern*", 10, 1, 2, CommandFlags.None); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedTransactionTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedTransactionTests.cs new file mode 100644 index 000000000..04a974808 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedTransactionTests.cs @@ -0,0 +1,132 @@ +using System.Text; +using System.Threading.Tasks; +using NSubstitute; +using StackExchange.Redis.KeyspaceIsolation; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(nameof(SubstituteDependentCollection))] +public sealed class KeyPrefixedTransactionTests +{ + private readonly ITransaction mock; + private readonly KeyPrefixedTransaction prefixed; + + public KeyPrefixedTransactionTests() + { + mock = Substitute.For(); + prefixed = new KeyPrefixedTransaction(mock, Encoding.UTF8.GetBytes("prefix:")); + } + + [Fact] + public void AddCondition_HashEqual() + { + prefixed.AddCondition(Condition.HashEqual("key", "field", "value")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key Hash > field == value" == value.ToString())); + } + + [Fact] + public void AddCondition_HashNotEqual() + { + prefixed.AddCondition(Condition.HashNotEqual("key", "field", "value")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key Hash > field != value" == value.ToString())); + } + + [Fact] + public void AddCondition_HashExists() + { + prefixed.AddCondition(Condition.HashExists("key", "field")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key Hash > field exists" == value.ToString())); + } + + [Fact] + public void AddCondition_HashNotExists() + { + prefixed.AddCondition(Condition.HashNotExists("key", "field")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key Hash > field does not exists" == value.ToString())); + } + + [Fact] + public void AddCondition_KeyExists() + { + prefixed.AddCondition(Condition.KeyExists("key")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key exists" == value.ToString())); + } + + [Fact] + public void AddCondition_KeyNotExists() + { + prefixed.AddCondition(Condition.KeyNotExists("key")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key does not exists" == value.ToString())); + } + + [Fact] + public void AddCondition_StringEqual() + { + prefixed.AddCondition(Condition.StringEqual("key", "value")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key == value" == value.ToString())); + } + + [Fact] + public void AddCondition_StringNotEqual() + { + prefixed.AddCondition(Condition.StringNotEqual("key", "value")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key != value" == value.ToString())); + } + + [Fact] + public void AddCondition_SortedSetEqual() + { + prefixed.AddCondition(Condition.SortedSetEqual("key", "member", "score")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key SortedSet > member == score" == value.ToString())); + } + + [Fact] + public void AddCondition_SortedSetNotEqual() + { + prefixed.AddCondition(Condition.SortedSetNotEqual("key", "member", "score")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key SortedSet > member != score" == value.ToString())); + } + + [Fact] + public void AddCondition_SortedSetScoreExists() + { + prefixed.AddCondition(Condition.SortedSetScoreExists("key", "score")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key not contains 0 members with score: score" == value.ToString())); + } + + [Fact] + public void AddCondition_SortedSetScoreNotExists() + { + prefixed.AddCondition(Condition.SortedSetScoreNotExists("key", "score")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key contains 0 members with score: score" == value.ToString())); + } + + [Fact] + public void AddCondition_SortedSetScoreCountExists() + { + prefixed.AddCondition(Condition.SortedSetScoreExists("key", "score", "count")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key contains count members with score: score" == value.ToString())); + } + + [Fact] + public void AddCondition_SortedSetScoreCountNotExists() + { + prefixed.AddCondition(Condition.SortedSetScoreNotExists("key", "score", "count")); + mock.Received().AddCondition(Arg.Is(value => "prefix:key not contains count members with score: score" == value.ToString())); + } + + [Fact] + public async Task ExecuteAsync() + { + await prefixed.ExecuteAsync(CommandFlags.None); + await mock.Received(1).ExecuteAsync(CommandFlags.None); + } + + [Fact] + public void Execute() + { + prefixed.Execute(CommandFlags.None); + mock.Received(1).Execute(CommandFlags.None); + } +} diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedVectorSetTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedVectorSetTests.cs new file mode 100644 index 000000000..b4ff2091b --- /dev/null +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedVectorSetTests.cs @@ -0,0 +1,214 @@ +using System; +using System.Text; +using NSubstitute; +using Xunit; + +namespace StackExchange.Redis.Tests +{ + [Collection(nameof(SubstituteDependentCollection))] + public sealed class KeyPrefixedVectorSetTests + { + private readonly IDatabase mock; + private readonly IDatabase prefixed; + + public KeyPrefixedVectorSetTests() + { + mock = Substitute.For(); + prefixed = new KeyspaceIsolation.KeyPrefixedDatabase(mock, Encoding.UTF8.GetBytes("prefix:")); + } + + [Fact] + public void VectorSetAdd_Fp32() + { + if (BitConverter.IsLittleEndian) + { + Assert.True(VectorSetAddMessage.UseFp32); +#if DEBUG // can be suppressed + VectorSetAddMessage.SuppressFp32(); + Assert.False(VectorSetAddMessage.UseFp32); + VectorSetAddMessage.RestoreFp32(); + Assert.True(VectorSetAddMessage.UseFp32); +#endif + } + else + { + Assert.False(VectorSetAddMessage.UseFp32); + } + } + + [Fact] + public void VectorSetAdd_BasicCall() + { + var vector = new[] { 1.0f, 2.0f, 3.0f }.AsMemory(); + + var request = VectorSetAddRequest.Member("element1", vector); + prefixed.VectorSetAdd("vectorset", request); + + mock.Received().VectorSetAdd( + "prefix:vectorset", + request); + } + + [Fact] + public void VectorSetAdd_WithAllParameters() + { + var vector = new[] { 1.0f, 2.0f, 3.0f }.AsMemory(); + var attributes = """{"category":"test"}"""; + + var request = VectorSetAddRequest.Member( + "element1", + vector, + attributes); + request.ReducedDimensions = 64; + request.Quantization = VectorSetQuantization.Binary; + request.BuildExplorationFactor = 300; + request.MaxConnections = 32; + request.UseCheckAndSet = true; + prefixed.VectorSetAdd( + "vectorset", + request, + flags: CommandFlags.FireAndForget); + + mock.Received().VectorSetAdd( + "prefix:vectorset", + request, + CommandFlags.FireAndForget); + } + + [Fact] + public void VectorSetLength() + { + prefixed.VectorSetLength("vectorset"); + mock.Received().VectorSetLength("prefix:vectorset"); + } + + [Fact] + public void VectorSetDimension() + { + prefixed.VectorSetDimension("vectorset"); + mock.Received().VectorSetDimension("prefix:vectorset"); + } + + [Fact] + public void VectorSetGetApproximateVector() + { + prefixed.VectorSetGetApproximateVector("vectorset", "member1"); + mock.Received().VectorSetGetApproximateVector("prefix:vectorset", "member1"); + } + + [Fact] + public void VectorSetGetAttributesJson() + { + prefixed.VectorSetGetAttributesJson("vectorset", "member1"); + mock.Received().VectorSetGetAttributesJson("prefix:vectorset", "member1"); + } + + [Fact] + public void VectorSetInfo() + { + prefixed.VectorSetInfo("vectorset"); + mock.Received().VectorSetInfo("prefix:vectorset"); + } + + [Fact] + public void VectorSetContains() + { + prefixed.VectorSetContains("vectorset", "member1"); + mock.Received().VectorSetContains("prefix:vectorset", "member1"); + } + + [Fact] + public void VectorSetGetLinks() + { + prefixed.VectorSetGetLinks("vectorset", "member1"); + mock.Received().VectorSetGetLinks("prefix:vectorset", "member1"); + } + + [Fact] + public void VectorSetGetLinksWithScores() + { + prefixed.VectorSetGetLinksWithScores("vectorset", "member1"); + mock.Received().VectorSetGetLinksWithScores("prefix:vectorset", "member1"); + } + + [Fact] + public void VectorSetRandomMember() + { + prefixed.VectorSetRandomMember("vectorset"); + mock.Received().VectorSetRandomMember("prefix:vectorset"); + } + + [Fact] + public void VectorSetRandomMembers() + { + prefixed.VectorSetRandomMembers("vectorset", 5); + mock.Received().VectorSetRandomMembers("prefix:vectorset", 5); + } + + [Fact] + public void VectorSetRemove() + { + prefixed.VectorSetRemove("vectorset", "member1"); + mock.Received().VectorSetRemove("prefix:vectorset", "member1"); + } + + [Fact] + public void VectorSetSetAttributesJson() + { + var attributes = """{"category":"test"}"""; + + prefixed.VectorSetSetAttributesJson("vectorset", "member1", attributes); + mock.Received().VectorSetSetAttributesJson("prefix:vectorset", "member1", attributes); + } + + [Fact] + public void VectorSetSimilaritySearchByVector() + { + var vector = new[] { 1.0f, 2.0f, 3.0f }.AsMemory(); + + var query = VectorSetSimilaritySearchRequest.ByVector(vector); + prefixed.VectorSetSimilaritySearch( + "vectorset", + query); + mock.Received().VectorSetSimilaritySearch( + "prefix:vectorset", + query); + } + + [Fact] + public void VectorSetSimilaritySearchByMember() + { + var query = VectorSetSimilaritySearchRequest.ByMember("member1"); + query.Count = 5; + query.WithScores = true; + query.WithAttributes = true; + query.Epsilon = 0.1; + query.SearchExplorationFactor = 400; + query.FilterExpression = "category='test'"; + query.MaxFilteringEffort = 1000; + query.UseExactSearch = true; + query.DisableThreading = true; + prefixed.VectorSetSimilaritySearch( + "vectorset", + query, + CommandFlags.FireAndForget); + mock.Received().VectorSetSimilaritySearch( + "prefix:vectorset", + query, + CommandFlags.FireAndForget); + } + + [Fact] + public void VectorSetSimilaritySearchByVector_DefaultParameters() + { + var vector = new[] { 1.0f, 2.0f }.AsMemory(); + + // Test that default parameters work correctly + var query = VectorSetSimilaritySearchRequest.ByVector(vector); + prefixed.VectorSetSimilaritySearch("vectorset", query); + mock.Received().VectorSetSimilaritySearch( + "prefix:vectorset", + query); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/KeyTests.cs b/tests/StackExchange.Redis.Tests/KeyTests.cs new file mode 100644 index 000000000..e956af4ff --- /dev/null +++ b/tests/StackExchange.Redis.Tests/KeyTests.cs @@ -0,0 +1,457 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class KeyTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task TestScan() + { + await using var conn = Create(allowAdmin: true); + + var dbId = TestConfig.GetDedicatedDB(conn); + var db = conn.GetDatabase(dbId); + var server = GetAnyPrimary(conn); + var prefix = Me(); + server.FlushDatabase(dbId, flags: CommandFlags.FireAndForget); + + const int Count = 1000; + for (int i = 0; i < Count; i++) + db.StringSet(prefix + "x" + i, "y" + i, flags: CommandFlags.FireAndForget); + + var count = server.Keys(dbId, prefix + "*").Count(); + Assert.Equal(Count, count); + } + + [Fact] + public async Task FlushFetchRandomKey() + { + await using var conn = Create(allowAdmin: true); + + var dbId = TestConfig.GetDedicatedDB(conn); + Skip.IfMissingDatabase(conn, dbId); + var db = conn.GetDatabase(dbId); + var prefix = Me(); + conn.GetServer(TestConfig.Current.PrimaryServerAndPort).FlushDatabase(dbId, CommandFlags.FireAndForget); + string? anyKey = db.KeyRandom(); + + Assert.Null(anyKey); + db.StringSet(prefix + "abc", "def"); + byte[]? keyBytes = db.KeyRandom(); + + Assert.NotNull(keyBytes); + Assert.Equal(prefix + "abc", Encoding.UTF8.GetString(keyBytes)); + } + + [Fact] + public async Task Zeros() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, 123, flags: CommandFlags.FireAndForget); + int k = (int)db.StringGet(key); + Assert.Equal(123, k); + + db.KeyDelete(key, CommandFlags.FireAndForget); + int i = (int)db.StringGet(key); + Assert.Equal(0, i); + + Assert.True(db.StringGet(key).IsNull); + int? value = (int?)db.StringGet(key); + Assert.False(value.HasValue); + } + + [Fact] + public void PrependAppend() + { + { + // simple + RedisKey key = "world"; + var ret = key.Prepend("hello"); + Assert.Equal("helloworld", ret); + } + + { + RedisKey key1 = "world"; + RedisKey key2 = Encoding.UTF8.GetBytes("hello"); + var key3 = key1.Prepend(key2); + Assert.True(ReferenceEquals(key1.KeyValue, key3.KeyValue)); + Assert.True(ReferenceEquals(key2.KeyValue, key3.KeyPrefix)); + Assert.Equal("helloworld", key3); + } + + { + RedisKey key = "hello"; + var ret = key.Append("world"); + Assert.Equal("helloworld", ret); + } + + { + RedisKey key1 = Encoding.UTF8.GetBytes("hello"); + RedisKey key2 = "world"; + var key3 = key1.Append(key2); + Assert.True(ReferenceEquals(key2.KeyValue, key3.KeyValue)); + Assert.True(ReferenceEquals(key1.KeyValue, key3.KeyPrefix)); + Assert.Equal("helloworld", key3); + } + } + + [Fact] + public async Task Exists() + { + await using var conn = Create(); + + RedisKey key = Me(); + RedisKey key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + Assert.False(db.KeyExists(key)); + Assert.False(db.KeyExists(key2)); + Assert.Equal(0, db.KeyExists([key, key2])); + + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + Assert.True(db.KeyExists(key)); + Assert.False(db.KeyExists(key2)); + Assert.Equal(1, db.KeyExists([key, key2])); + + db.StringSet(key2, "new value", flags: CommandFlags.FireAndForget); + Assert.True(db.KeyExists(key)); + Assert.True(db.KeyExists(key2)); + Assert.Equal(2, db.KeyExists([key, key2])); + } + + [Fact] + public async Task ExistsAsync() + { + await using var conn = Create(); + + RedisKey key = Me(); + RedisKey key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + var a1 = db.KeyExistsAsync(key).ForAwait(); + var a2 = db.KeyExistsAsync(key2).ForAwait(); + var a3 = db.KeyExistsAsync([key, key2]).ForAwait(); + + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + + var b1 = db.KeyExistsAsync(key).ForAwait(); + var b2 = db.KeyExistsAsync(key2).ForAwait(); + var b3 = db.KeyExistsAsync([key, key2]).ForAwait(); + + db.StringSet(key2, "new value", flags: CommandFlags.FireAndForget); + + var c1 = db.KeyExistsAsync(key).ForAwait(); + var c2 = db.KeyExistsAsync(key2).ForAwait(); + var c3 = db.KeyExistsAsync([key, key2]).ForAwait(); + + Assert.False(await a1); + Assert.False(await a2); + Assert.Equal(0, await a3); + + Assert.True(await b1); + Assert.False(await b2); + Assert.Equal(1, await b3); + + Assert.True(await c1); + Assert.True(await c2); + Assert.Equal(2, await c3); + } + + [Fact] + public async Task KeyEncoding() + { + await using var conn = Create(); + + var key = Me(); + var db = conn.GetDatabase(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + + Assert.True(db.KeyEncoding(key) is "embstr" or "raw"); // server-version dependent + Assert.True(await db.KeyEncodingAsync(key) is "embstr" or "raw"); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.ListLeftPush(key, "new value", flags: CommandFlags.FireAndForget); + + // Depending on server version, this is going to vary - we're sanity checking here. + var listTypes = new[] { "ziplist", "quicklist", "listpack" }; + Assert.Contains(db.KeyEncoding(key), listTypes); + Assert.Contains(await db.KeyEncodingAsync(key), listTypes); + + var keyNotExists = key + "no-exist"; + Assert.Null(db.KeyEncoding(keyNotExists)); + Assert.Null(await db.KeyEncodingAsync(keyNotExists)); + } + + [Fact] + public async Task KeyRefCount() + { + await using var conn = Create(); + + var key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + + Assert.Equal(1, db.KeyRefCount(key)); + Assert.Equal(1, await db.KeyRefCountAsync(key)); + + var keyNotExists = key + "no-exist"; + Assert.Null(db.KeyRefCount(keyNotExists)); + Assert.Null(await db.KeyRefCountAsync(keyNotExists)); + } + + [Fact] + public async Task KeyFrequency() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v4_0_0); + + var key = Me(); + var db = conn.GetDatabase(); + var server = GetServer(conn); + + var serverConfig = server.ConfigGet("maxmemory-policy"); + var maxMemoryPolicy = serverConfig.Length == 1 ? serverConfig[0].Value : ""; + Log($"maxmemory-policy detected as {maxMemoryPolicy}"); + var isLfu = maxMemoryPolicy.Contains("lfu"); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, "new value", flags: CommandFlags.FireAndForget); + db.StringGet(key); + + if (isLfu) + { + var count = db.KeyFrequency(key); + Assert.True(count > 0); + + count = await db.KeyFrequencyAsync(key); + Assert.True(count > 0); + + // Key not exists + db.KeyDelete(key, CommandFlags.FireAndForget); + var res = db.KeyFrequency(key); + Assert.Null(res); + + res = await db.KeyFrequencyAsync(key); + Assert.Null(res); + } + else + { + var ex = Assert.Throws(() => db.KeyFrequency(key)); + Assert.Contains("An LFU maxmemory policy is not selected", ex.Message); + ex = await Assert.ThrowsAsync(() => db.KeyFrequencyAsync(key)); + Assert.Contains("An LFU maxmemory policy is not selected", ex.Message); + } + } + + private static void TestTotalLengthAndCopyTo(in RedisKey key, int expectedLength) + { + var length = key.TotalLength(); + Assert.Equal(expectedLength, length); + var arr = ArrayPool.Shared.Rent(length + 20); // deliberately over-sized + try + { + var written = key.CopyTo(arr); + Assert.Equal(length, written); + + var viaCast = (byte[]?)key; + ReadOnlySpan x = viaCast, y = new ReadOnlySpan(arr, 0, length); + Assert.True(x.SequenceEqual(y)); + Assert.True(key.IsNull == viaCast is null); + } + finally + { + ArrayPool.Shared.Return(arr); + } + } + + [Fact] + public void NullKeySlot() + { + RedisKey key = RedisKey.Null; + Assert.True(key.TryGetSimpleBuffer(out var buffer)); + Assert.Empty(buffer); + TestTotalLengthAndCopyTo(key, 0); + + Assert.Equal(-1, GetHashSlot(key)); + } + + private static readonly byte[] KeyPrefix = Encoding.UTF8.GetBytes("abcde"); + + private static int GetHashSlot(in RedisKey key) + { + var strategy = new ServerSelectionStrategy(null!) + { + ServerType = ServerType.Cluster, + }; + return strategy.HashSlot(key); + } + + [Theory] + [InlineData(false, null, -1)] + [InlineData(false, "", 0)] + [InlineData(false, "f", 3168)] + [InlineData(false, "abcde", 16097)] + [InlineData(false, "abcdef", 15101)] + [InlineData(false, "abcdeffsdkjhsdfgkjh sdkjhsdkjf hsdkjfh skudrfy7 348iu yksef78 dssdhkfh ##$OIU", 5073)] + [InlineData(false, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras lobortis quam ac molestie ultricies. Duis maximus, nunc a auctor faucibus, risus turpis porttitor nibh, sit amet consequat lacus nibh quis nisi. Aliquam ipsum quam, dapibus ut ex eu, efficitur vestibulum dui. Sed a nibh ut felis congue tempor vel vel lectus. Phasellus a neque placerat, blandit massa sed, imperdiet urna. Praesent scelerisque lorem ipsum, non facilisis libero hendrerit quis. Nullam sit amet malesuada velit, ac lacinia lacus. Donec mollis a massa sed egestas. Suspendisse vitae augue quis erat gravida consectetur. Aenean interdum neque id lacinia eleifend.", 4954)] + [InlineData(true, null, 16097)] + [InlineData(true, "", 16097)] // note same as false/abcde + [InlineData(true, "f", 15101)] // note same as false/abcdef + [InlineData(true, "abcde", 4089)] + [InlineData(true, "abcdef", 1167)] + [InlineData(true, "👻👩‍👩‍👦‍👦", 8494)] + [InlineData(true, "abcdeffsdkjhsdfgkjh sdkjhsdkjf hsdkjfh skudrfy7 348iu yksef78 dssdhkfh ##$OIU", 10923)] + [InlineData(true, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras lobortis quam ac molestie ultricies. Duis maximus, nunc a auctor faucibus, risus turpis porttitor nibh, sit amet consequat lacus nibh quis nisi. Aliquam ipsum quam, dapibus ut ex eu, efficitur vestibulum dui. Sed a nibh ut felis congue tempor vel vel lectus. Phasellus a neque placerat, blandit massa sed, imperdiet urna. Praesent scelerisque lorem ipsum, non facilisis libero hendrerit quis. Nullam sit amet malesuada velit, ac lacinia lacus. Donec mollis a massa sed egestas. Suspendisse vitae augue quis erat gravida consectetur. Aenean interdum neque id lacinia eleifend.", 4452)] + public void TestStringKeySlot(bool prefixed, string? s, int slot) + { + RedisKey key = prefixed ? new RedisKey(KeyPrefix, s) : s; + if (s is null && !prefixed) + { + Assert.True(key.TryGetSimpleBuffer(out var buffer)); + Assert.Empty(buffer); + TestTotalLengthAndCopyTo(key, 0); + } + else + { + Assert.False(key.TryGetSimpleBuffer(out var _)); + } + TestTotalLengthAndCopyTo(key, Encoding.UTF8.GetByteCount(s ?? "") + (prefixed ? KeyPrefix.Length : 0)); + + Assert.Equal(slot, GetHashSlot(key)); + } + + [Theory] + [InlineData(false, -1, -1)] + [InlineData(false, 0, 0)] + [InlineData(false, 1, 10242)] + [InlineData(false, 6, 10015)] + [InlineData(false, 47, 849)] + [InlineData(false, 14123, 2356)] + [InlineData(true, -1, 16097)] + [InlineData(true, 0, 16097)] + [InlineData(true, 1, 7839)] + [InlineData(true, 6, 6509)] + [InlineData(true, 47, 2217)] + [InlineData(true, 14123, 6773)] + public void TestBlobKeySlot(bool prefixed, int count, int slot) + { + byte[]? blob = null; + if (count >= 0) + { + blob = new byte[count]; + new Random(count).NextBytes(blob); + for (int i = 0; i < blob.Length; i++) + { + if (blob[i] == (byte)'{') blob[i] = (byte)'!'; // avoid unexpected hash tags + } + } + RedisKey key = prefixed ? new RedisKey(KeyPrefix, blob) : blob; + if (prefixed) + { + Assert.False(key.TryGetSimpleBuffer(out _)); + } + else + { + Assert.True(key.TryGetSimpleBuffer(out var buffer)); + if (blob is null) + { + Assert.Empty(buffer); + } + else + { + Assert.Same(blob, buffer); + } + } + TestTotalLengthAndCopyTo(key, (blob?.Length ?? 0) + (prefixed ? KeyPrefix.Length : 0)); + + Assert.Equal(slot, GetHashSlot(key)); + } + + [Theory] + [MemberData(nameof(KeyEqualityData))] + public void KeyEquality(RedisKey x, RedisKey y, bool equal) + { + if (equal) + { + Assert.Equal(x, y); + Assert.True(x == y); + Assert.False(x != y); + Assert.True(x.Equals(y)); + Assert.True(x.Equals((object)y)); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } + else + { + Assert.NotEqual(x, y); + Assert.False(x == y); + Assert.True(x != y); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + // note that this last one is not strictly required, but: we pass, so: yay! + Assert.NotEqual(x.GetHashCode(), y.GetHashCode()); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1046:Avoid using TheoryDataRow arguments that are not serializable", Justification = "No options at the moment.")] + public static IEnumerable> KeyEqualityData() + { + RedisKey abcString = "abc", abcBytes = Encoding.UTF8.GetBytes("abc"); + RedisKey abcdefString = "abcdef", abcdefBytes = Encoding.UTF8.GetBytes("abcdef"); + + yield return new(RedisKey.Null, abcString, false); + yield return new(RedisKey.Null, abcBytes, false); + yield return new(abcString, RedisKey.Null, false); + yield return new(abcBytes, RedisKey.Null, false); + yield return new(RedisKey.Null, RedisKey.Null, true); + yield return new(new RedisKey((string?)null), RedisKey.Null, true); + yield return new(new RedisKey(null, (byte[]?)null), RedisKey.Null, true); + yield return new(new RedisKey(""), RedisKey.Null, false); + yield return new(new RedisKey(null, Array.Empty()), RedisKey.Null, false); + + yield return new(abcString, abcString, true); + yield return new(abcBytes, abcBytes, true); + yield return new(abcString, abcBytes, true); + yield return new(abcBytes, abcString, true); + + yield return new(abcdefString, abcdefString, true); + yield return new(abcdefBytes, abcdefBytes, true); + yield return new(abcdefString, abcdefBytes, true); + yield return new(abcdefBytes, abcdefString, true); + + yield return new(abcString, abcdefString, false); + yield return new(abcBytes, abcdefBytes, false); + yield return new(abcString, abcdefBytes, false); + yield return new(abcBytes, abcdefString, false); + + yield return new(abcdefString, abcString, false); + yield return new(abcdefBytes, abcBytes, false); + yield return new(abcdefString, abcBytes, false); + yield return new(abcdefBytes, abcString, false); + + var x = abcString.Append("def"); + yield return new(abcdefString, x, true); + yield return new(abcdefBytes, x, true); + yield return new(x, abcdefBytes, true); + yield return new(x, abcdefString, true); + yield return new(abcString, x, false); + yield return new(abcString, x, false); + yield return new(x, abcString, false); + yield return new(x, abcString, false); + } +} diff --git a/tests/StackExchange.Redis.Tests/LatencyTests.cs b/tests/StackExchange.Redis.Tests/LatencyTests.cs new file mode 100644 index 000000000..42b4d7b05 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/LatencyTests.cs @@ -0,0 +1,81 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class LatencyTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task CanCallDoctor() + { + await using var conn = Create(); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + string? doctor = server.LatencyDoctor(); + Assert.NotNull(doctor); + Assert.NotEqual("", doctor); + + doctor = await server.LatencyDoctorAsync(); + Assert.NotNull(doctor); + Assert.NotEqual("", doctor); + } + + [Fact] + public async Task CanReset() + { + await using var conn = Create(); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + _ = server.LatencyReset(); + var count = await server.LatencyResetAsync(["command"]); + Assert.Equal(0, count); + + count = await server.LatencyResetAsync(["command", "fast-command"]); + Assert.Equal(0, count); + } + + [Fact] + public async Task GetLatest() + { + Skip.UnlessLongRunning(); + await using var conn = Create(allowAdmin: true); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + server.ConfigSet("latency-monitor-threshold", 50); + server.LatencyReset(); + var arr = server.LatencyLatest(); + Assert.Empty(arr); + + var now = await server.TimeAsync(); + server.Execute("debug", "sleep", "0.5"); // cause something to be slow + + arr = await server.LatencyLatestAsync(); + var item = Assert.Single(arr); + Assert.Equal("command", item.EventName); + Assert.True(item.DurationMilliseconds >= 400 && item.DurationMilliseconds <= 600); + Assert.Equal(item.DurationMilliseconds, item.MaxDurationMilliseconds); + Assert.True(item.Timestamp >= now.AddSeconds(-2) && item.Timestamp <= now.AddSeconds(2)); + } + + [Fact] + public async Task GetHistory() + { + Skip.UnlessLongRunning(); + await using var conn = Create(allowAdmin: true); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + server.ConfigSet("latency-monitor-threshold", 50); + server.LatencyReset(); + var arr = server.LatencyHistory("command"); + Assert.Empty(arr); + + var now = await server.TimeAsync(); + server.Execute("debug", "sleep", "0.5"); // cause something to be slow + + arr = await server.LatencyHistoryAsync("command"); + var item = Assert.Single(arr); + Assert.True(item.DurationMilliseconds >= 400 && item.DurationMilliseconds <= 600); + Assert.True(item.Timestamp >= now.AddSeconds(-2) && item.Timestamp <= now.AddSeconds(2)); + } +} diff --git a/tests/StackExchange.Redis.Tests/LexTests.cs b/tests/StackExchange.Redis.Tests/LexTests.cs new file mode 100644 index 000000000..b70fdda7e --- /dev/null +++ b/tests/StackExchange.Redis.Tests/LexTests.cs @@ -0,0 +1,108 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class LexTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task QueryRangeAndLengthByLex() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.SortedSetAdd( + key, + [ + new SortedSetEntry("a", 0), + new SortedSetEntry("b", 0), + new SortedSetEntry("c", 0), + new SortedSetEntry("d", 0), + new SortedSetEntry("e", 0), + new SortedSetEntry("f", 0), + new SortedSetEntry("g", 0), + ], + CommandFlags.FireAndForget); + + var set = db.SortedSetRangeByValue(key, default(RedisValue), "c"); + var count = db.SortedSetLengthByValue(key, default(RedisValue), "c"); + Equate(set, count, "a", "b", "c"); + + set = db.SortedSetRangeByValue(key, default(RedisValue), "c", Exclude.Stop); + count = db.SortedSetLengthByValue(key, default(RedisValue), "c", Exclude.Stop); + Equate(set, count, "a", "b"); + + set = db.SortedSetRangeByValue(key, "aaa", "g", Exclude.Stop); + count = db.SortedSetLengthByValue(key, "aaa", "g", Exclude.Stop); + Equate(set, count, "b", "c", "d", "e", "f"); + + set = db.SortedSetRangeByValue(key, "aaa", "g", Exclude.Stop, 1, 3); + Equate(set, set.Length, "c", "d", "e"); + + set = db.SortedSetRangeByValue(key, "aaa", "g", Exclude.Stop, Order.Descending, 1, 3); + Equate(set, set.Length, "e", "d", "c"); + + set = db.SortedSetRangeByValue(key, "g", "aaa", Exclude.Start, Order.Descending, 1, 3); + Equate(set, set.Length, "e", "d", "c"); + + set = db.SortedSetRangeByValue(key, "e", default(RedisValue)); + count = db.SortedSetLengthByValue(key, "e", default(RedisValue)); + Equate(set, count, "e", "f", "g"); + + set = db.SortedSetRangeByValue(key, RedisValue.Null, RedisValue.Null, Exclude.None, Order.Descending, 0, 3); // added to test Null-min- and max-param + Equate(set, set.Length, "g", "f", "e"); + } + + [Fact] + public async Task RemoveRangeByLex() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.SortedSetAdd( + key, + [ + new SortedSetEntry("aaaa", 0), + new SortedSetEntry("b", 0), + new SortedSetEntry("c", 0), + new SortedSetEntry("d", 0), + new SortedSetEntry("e", 0), + ], + CommandFlags.FireAndForget); + db.SortedSetAdd( + key, + [ + new SortedSetEntry("foo", 0), + new SortedSetEntry("zap", 0), + new SortedSetEntry("zip", 0), + new SortedSetEntry("ALPHA", 0), + new SortedSetEntry("alpha", 0), + ], + CommandFlags.FireAndForget); + + var set = db.SortedSetRangeByRank(key); + Equate(set, set.Length, "ALPHA", "aaaa", "alpha", "b", "c", "d", "e", "foo", "zap", "zip"); + + long removed = db.SortedSetRemoveRangeByValue(key, "alpha", "omega"); + Assert.Equal(6, removed); + + set = db.SortedSetRangeByRank(key); + Equate(set, set.Length, "ALPHA", "aaaa", "zap", "zip"); + } + + private static void Equate(RedisValue[] actual, long count, params string[] expected) + { + Assert.Equal(expected.Length, count); + Assert.Equal(expected.Length, actual.Length); + for (int i = 0; i < actual.Length; i++) + { + Assert.Equal(expected[i], actual[i]); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/ListTests.cs b/tests/StackExchange.Redis.Tests/ListTests.cs new file mode 100644 index 000000000..cd0f2e0a3 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ListTests.cs @@ -0,0 +1,957 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class ListTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task Ranges() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.ListRightPush(key, "abcdefghijklmnopqrstuvwxyz".Select(x => (RedisValue)x.ToString()).ToArray(), CommandFlags.FireAndForget); + + Assert.Equal(26, db.ListLength(key)); + Assert.Equal("abcdefghijklmnopqrstuvwxyz", string.Concat(db.ListRange(key))); + + var last10 = db.ListRange(key, -10, -1); + Assert.Equal("qrstuvwxyz", string.Concat(last10)); + db.ListTrim(key, 0, -11, CommandFlags.FireAndForget); + + Assert.Equal(16, db.ListLength(key)); + Assert.Equal("abcdefghijklmnop", string.Concat(db.ListRange(key))); + } + + [Fact] + public async Task ListLeftPushEmptyValues() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + var result = db.ListLeftPush(key, Array.Empty(), When.Always, CommandFlags.None); + Assert.Equal(0, result); + } + + [Fact] + public async Task ListLeftPushKeyDoesNotExists() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + var result = db.ListLeftPush(key, ["testvalue"], When.Exists, CommandFlags.None); + Assert.Equal(0, result); + } + + [Fact] + public async Task ListLeftPushToExisitingKey() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var pushResult = db.ListLeftPush(key, ["testvalue1"], CommandFlags.None); + Assert.Equal(1, pushResult); + var pushXResult = db.ListLeftPush(key, ["testvalue2"], When.Exists, CommandFlags.None); + Assert.Equal(2, pushXResult); + + var rangeResult = db.ListRange(key, 0, -1); + Assert.Equal(2, rangeResult.Length); + Assert.Equal("testvalue2", rangeResult[0]); + Assert.Equal("testvalue1", rangeResult[1]); + } + + [Fact] + public async Task ListLeftPushMultipleToExisitingKey() + { + await using var conn = Create(require: RedisFeatures.v4_0_0); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var pushResult = db.ListLeftPush(key, ["testvalue1"], CommandFlags.None); + Assert.Equal(1, pushResult); + var pushXResult = db.ListLeftPush(key, ["testvalue2", "testvalue3"], When.Exists, CommandFlags.None); + Assert.Equal(3, pushXResult); + + var rangeResult = db.ListRange(key, 0, -1); + Assert.Equal(3, rangeResult.Length); + Assert.Equal("testvalue3", rangeResult[0]); + Assert.Equal("testvalue2", rangeResult[1]); + Assert.Equal("testvalue1", rangeResult[2]); + } + + [Fact] + public async Task ListLeftPushAsyncEmptyValues() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + var result = await db.ListLeftPushAsync(key, Array.Empty(), When.Always, CommandFlags.None); + Assert.Equal(0, result); + } + + [Fact] + public async Task ListLeftPushAsyncKeyDoesNotExists() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + var result = await db.ListLeftPushAsync(key, ["testvalue"], When.Exists, CommandFlags.None); + Assert.Equal(0, result); + } + + [Fact] + public async Task ListLeftPushAsyncToExisitingKey() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var pushResult = await db.ListLeftPushAsync(key, ["testvalue1"], CommandFlags.None); + Assert.Equal(1, pushResult); + var pushXResult = await db.ListLeftPushAsync(key, ["testvalue2"], When.Exists, CommandFlags.None); + Assert.Equal(2, pushXResult); + + var rangeResult = db.ListRange(key, 0, -1); + Assert.Equal(2, rangeResult.Length); + Assert.Equal("testvalue2", rangeResult[0]); + Assert.Equal("testvalue1", rangeResult[1]); + } + + [Fact] + public async Task ListLeftPushAsyncMultipleToExisitingKey() + { + await using var conn = Create(require: RedisFeatures.v4_0_0); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var pushResult = await db.ListLeftPushAsync(key, ["testvalue1"], CommandFlags.None); + Assert.Equal(1, pushResult); + var pushXResult = await db.ListLeftPushAsync(key, ["testvalue2", "testvalue3"], When.Exists, CommandFlags.None); + Assert.Equal(3, pushXResult); + + var rangeResult = db.ListRange(key, 0, -1); + Assert.Equal(3, rangeResult.Length); + Assert.Equal("testvalue3", rangeResult[0]); + Assert.Equal("testvalue2", rangeResult[1]); + Assert.Equal("testvalue1", rangeResult[2]); + } + + [Fact] + public async Task ListRightPushEmptyValues() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + var result = db.ListRightPush(key, Array.Empty(), When.Always, CommandFlags.None); + Assert.Equal(0, result); + } + + [Fact] + public async Task ListRightPushKeyDoesNotExists() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + var result = db.ListRightPush(key, ["testvalue"], When.Exists, CommandFlags.None); + Assert.Equal(0, result); + } + + [Fact] + public async Task ListRightPushToExisitingKey() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var pushResult = db.ListRightPush(key, ["testvalue1"], CommandFlags.None); + Assert.Equal(1, pushResult); + var pushXResult = db.ListRightPush(key, ["testvalue2"], When.Exists, CommandFlags.None); + Assert.Equal(2, pushXResult); + + var rangeResult = db.ListRange(key, 0, -1); + Assert.Equal(2, rangeResult.Length); + Assert.Equal("testvalue1", rangeResult[0]); + Assert.Equal("testvalue2", rangeResult[1]); + } + + [Fact] + public async Task ListRightPushMultipleToExisitingKey() + { + await using var conn = Create(require: RedisFeatures.v4_0_0); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var pushResult = db.ListRightPush(key, ["testvalue1"], CommandFlags.None); + Assert.Equal(1, pushResult); + var pushXResult = db.ListRightPush(key, ["testvalue2", "testvalue3"], When.Exists, CommandFlags.None); + Assert.Equal(3, pushXResult); + + var rangeResult = db.ListRange(key, 0, -1); + Assert.Equal(3, rangeResult.Length); + Assert.Equal("testvalue1", rangeResult[0]); + Assert.Equal("testvalue2", rangeResult[1]); + Assert.Equal("testvalue3", rangeResult[2]); + } + + [Fact] + public async Task ListRightPushAsyncEmptyValues() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + var result = await db.ListRightPushAsync(key, Array.Empty(), When.Always, CommandFlags.None); + Assert.Equal(0, result); + } + + [Fact] + public async Task ListRightPushAsyncKeyDoesNotExists() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + var result = await db.ListRightPushAsync(key, ["testvalue"], When.Exists, CommandFlags.None); + Assert.Equal(0, result); + } + + [Fact] + public async Task ListRightPushAsyncToExisitingKey() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var pushResult = await db.ListRightPushAsync(key, ["testvalue1"], CommandFlags.None); + Assert.Equal(1, pushResult); + var pushXResult = await db.ListRightPushAsync(key, ["testvalue2"], When.Exists, CommandFlags.None); + Assert.Equal(2, pushXResult); + + var rangeResult = db.ListRange(key, 0, -1); + Assert.Equal(2, rangeResult.Length); + Assert.Equal("testvalue1", rangeResult[0]); + Assert.Equal("testvalue2", rangeResult[1]); + } + + [Fact] + public async Task ListRightPushAsyncMultipleToExisitingKey() + { + await using var conn = Create(require: RedisFeatures.v4_0_0); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var pushResult = await db.ListRightPushAsync(key, ["testvalue1"], CommandFlags.None); + Assert.Equal(1, pushResult); + var pushXResult = await db.ListRightPushAsync(key, ["testvalue2", "testvalue3"], When.Exists, CommandFlags.None); + Assert.Equal(3, pushXResult); + + var rangeResult = db.ListRange(key, 0, -1); + Assert.Equal(3, rangeResult.Length); + Assert.Equal("testvalue1", rangeResult[0]); + Assert.Equal("testvalue2", rangeResult[1]); + Assert.Equal("testvalue3", rangeResult[2]); + } + + [Fact] + public async Task ListMove() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + RedisKey src = Me(); + RedisKey dest = Me() + "dest"; + db.KeyDelete(src, CommandFlags.FireAndForget); + + var pushResult = await db.ListRightPushAsync(src, ["testvalue1", "testvalue2"]); + Assert.Equal(2, pushResult); + + var rangeResult1 = db.ListMove(src, dest, ListSide.Left, ListSide.Right); + var rangeResult2 = db.ListMove(src, dest, ListSide.Left, ListSide.Left); + var rangeResult3 = db.ListMove(dest, src, ListSide.Right, ListSide.Right); + var rangeResult4 = db.ListMove(dest, src, ListSide.Right, ListSide.Left); + Assert.Equal("testvalue1", rangeResult1); + Assert.Equal("testvalue2", rangeResult2); + Assert.Equal("testvalue1", rangeResult3); + Assert.Equal("testvalue2", rangeResult4); + } + + [Fact] + public async Task ListMoveKeyDoesNotExist() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + RedisKey src = Me(); + RedisKey dest = Me() + "dest"; + db.KeyDelete(src, CommandFlags.FireAndForget); + + var rangeResult1 = db.ListMove(src, dest, ListSide.Left, ListSide.Right); + Assert.True(rangeResult1.IsNull); + } + + [Fact] + public async Task ListPositionHappyPath() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string val = "foo"; + db.KeyDelete(key); + + db.ListLeftPush(key, val); + var res = db.ListPosition(key, val); + + Assert.Equal(0, res); + } + + [Fact] + public async Task ListPositionEmpty() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string val = "foo"; + db.KeyDelete(key); + + var res = db.ListPosition(key, val); + + Assert.Equal(-1, res); + } + + [Fact] + public async Task ListPositionsHappyPath() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + db.KeyDelete(key); + + for (var i = 0; i < 10; i++) + { + db.ListLeftPush(key, foo); + db.ListLeftPush(key, bar); + db.ListLeftPush(key, baz); + } + + var res = db.ListPositions(key, foo, 5); + + foreach (var item in res) + { + Assert.Equal(2, item % 3); + } + + Assert.Equal(5, res.Length); + } + + [Fact] + public async Task ListPositionsTooFew() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + db.KeyDelete(key); + + for (var i = 0; i < 10; i++) + { + db.ListLeftPush(key, bar); + db.ListLeftPush(key, baz); + } + + db.ListLeftPush(key, foo); + + var res = db.ListPositions(key, foo, 5); + Assert.Single(res); + Assert.Equal(0, res.Single()); + } + + [Fact] + public async Task ListPositionsAll() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + db.KeyDelete(key); + + for (var i = 0; i < 10; i++) + { + db.ListLeftPush(key, foo); + db.ListLeftPush(key, bar); + db.ListLeftPush(key, baz); + } + + var res = db.ListPositions(key, foo, 0); + + foreach (var item in res) + { + Assert.Equal(2, item % 3); + } + + Assert.Equal(10, res.Length); + } + + [Fact] + public async Task ListPositionsAllLimitLength() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + db.KeyDelete(key); + + for (var i = 0; i < 10; i++) + { + db.ListLeftPush(key, foo); + db.ListLeftPush(key, bar); + db.ListLeftPush(key, baz); + } + + var res = db.ListPositions(key, foo, 0, maxLength: 15); + + foreach (var item in res) + { + Assert.Equal(2, item % 3); + } + + Assert.Equal(5, res.Length); + } + + [Fact] + public async Task ListPositionsEmpty() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + db.KeyDelete(key); + + for (var i = 0; i < 10; i++) + { + db.ListLeftPush(key, bar); + db.ListLeftPush(key, baz); + } + + var res = db.ListPositions(key, foo, 5); + + Assert.Empty(res); + } + + [Fact] + public async Task ListPositionByRank() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + db.KeyDelete(key); + + for (var i = 0; i < 10; i++) + { + db.ListLeftPush(key, foo); + db.ListLeftPush(key, bar); + db.ListLeftPush(key, baz); + } + + const int rank = 6; + + var res = db.ListPosition(key, foo, rank: rank); + + Assert.Equal((3 * rank) - 1, res); + } + + [Fact] + public async Task ListPositionLimitSoNull() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + db.KeyDelete(key); + + for (var i = 0; i < 10; i++) + { + db.ListLeftPush(key, bar); + db.ListLeftPush(key, baz); + } + + db.ListRightPush(key, foo); + + var res = db.ListPosition(key, foo, maxLength: 20); + + Assert.Equal(-1, res); + } + + [Fact] + public async Task ListPositionHappyPathAsync() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string val = "foo"; + await db.KeyDeleteAsync(key); + + await db.ListLeftPushAsync(key, val); + var res = await db.ListPositionAsync(key, val); + + Assert.Equal(0, res); + } + + [Fact] + public async Task ListPositionEmptyAsync() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string val = "foo"; + await db.KeyDeleteAsync(key); + + var res = await db.ListPositionAsync(key, val); + + Assert.Equal(-1, res); + } + + [Fact] + public async Task ListPositionsHappyPathAsync() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + await db.KeyDeleteAsync(key); + + for (var i = 0; i < 10; i++) + { + await db.ListLeftPushAsync(key, foo); + await db.ListLeftPushAsync(key, bar); + await db.ListLeftPushAsync(key, baz); + } + + var res = await db.ListPositionsAsync(key, foo, 5); + + foreach (var item in res) + { + Assert.Equal(2, item % 3); + } + + Assert.Equal(5, res.Length); + } + + [Fact] + public async Task ListPositionsTooFewAsync() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + await db.KeyDeleteAsync(key); + + for (var i = 0; i < 10; i++) + { + await db.ListLeftPushAsync(key, bar); + await db.ListLeftPushAsync(key, baz); + } + + db.ListLeftPush(key, foo); + + var res = await db.ListPositionsAsync(key, foo, 5); + Assert.Single(res); + Assert.Equal(0, res.Single()); + } + + [Fact] + public async Task ListPositionsAllAsync() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + await db.KeyDeleteAsync(key); + + for (var i = 0; i < 10; i++) + { + await db.ListLeftPushAsync(key, foo); + await db.ListLeftPushAsync(key, bar); + await db.ListLeftPushAsync(key, baz); + } + + var res = await db.ListPositionsAsync(key, foo, 0); + + foreach (var item in res) + { + Assert.Equal(2, item % 3); + } + + Assert.Equal(10, res.Length); + } + + [Fact] + public async Task ListPositionsAllLimitLengthAsync() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + await db.KeyDeleteAsync(key); + + for (var i = 0; i < 10; i++) + { + await db.ListLeftPushAsync(key, foo); + await db.ListLeftPushAsync(key, bar); + await db.ListLeftPushAsync(key, baz); + } + + var res = await db.ListPositionsAsync(key, foo, 0, maxLength: 15); + + foreach (var item in res) + { + Assert.Equal(2, item % 3); + } + + Assert.Equal(5, res.Length); + } + + [Fact] + public async Task ListPositionsEmptyAsync() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + await db.KeyDeleteAsync(key); + + for (var i = 0; i < 10; i++) + { + await db.ListLeftPushAsync(key, bar); + await db.ListLeftPushAsync(key, baz); + } + + var res = await db.ListPositionsAsync(key, foo, 5); + + Assert.Empty(res); + } + + [Fact] + public async Task ListPositionByRankAsync() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + await db.KeyDeleteAsync(key); + + for (var i = 0; i < 10; i++) + { + await db.ListLeftPushAsync(key, foo); + await db.ListLeftPushAsync(key, bar); + await db.ListLeftPushAsync(key, baz); + } + + const int rank = 6; + + var res = await db.ListPositionAsync(key, foo, rank: rank); + + Assert.Equal((3 * rank) - 1, res); + } + + [Fact] + public async Task ListPositionLimitSoNullAsync() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + await db.KeyDeleteAsync(key); + + for (var i = 0; i < 10; i++) + { + await db.ListLeftPushAsync(key, bar); + await db.ListLeftPushAsync(key, baz); + } + + await db.ListRightPushAsync(key, foo); + + var res = await db.ListPositionAsync(key, foo, maxLength: 20); + + Assert.Equal(-1, res); + } + + [Fact] + public async Task ListPositionFireAndForgetAsync() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + await db.KeyDeleteAsync(key); + + for (var i = 0; i < 10; i++) + { + await db.ListLeftPushAsync(key, foo); + await db.ListLeftPushAsync(key, bar); + await db.ListLeftPushAsync(key, baz); + } + + await db.ListRightPushAsync(key, foo); + + var res = await db.ListPositionAsync(key, foo, maxLength: 20, flags: CommandFlags.FireAndForget); + + Assert.Equal(-1, res); + } + + [Fact] + public async Task ListPositionFireAndForget() + { + await using var conn = Create(require: RedisFeatures.v6_0_6); + + var db = conn.GetDatabase(); + var key = Me(); + const string foo = "foo", + bar = "bar", + baz = "baz"; + + db.KeyDelete(key); + + for (var i = 0; i < 10; i++) + { + db.ListLeftPush(key, foo); + db.ListLeftPush(key, bar); + db.ListLeftPush(key, baz); + } + + db.ListRightPush(key, foo); + + var res = db.ListPosition(key, foo, maxLength: 20, flags: CommandFlags.FireAndForget); + + Assert.Equal(-1, res); + } + + [Fact] + public async Task ListMultiPopSingleKeyAsync() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + await db.KeyDeleteAsync(key); + + db.ListLeftPush(key, "yankees"); + db.ListLeftPush(key, "blue jays"); + db.ListLeftPush(key, "orioles"); + db.ListLeftPush(key, "red sox"); + db.ListLeftPush(key, "rays"); + + var res = await db.ListLeftPopAsync([key], 1); + + Assert.False(res.IsNull); + Assert.Single(res.Values); + Assert.Equal("rays", res.Values[0]); + + res = await db.ListRightPopAsync([key], 2); + + Assert.False(res.IsNull); + Assert.Equal(2, res.Values.Length); + Assert.Equal("yankees", res.Values[0]); + Assert.Equal("blue jays", res.Values[1]); + } + + [Fact] + public async Task ListMultiPopMultipleKeysAsync() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + await db.KeyDeleteAsync(key); + + db.ListLeftPush(key, "yankees"); + db.ListLeftPush(key, "blue jays"); + db.ListLeftPush(key, "orioles"); + db.ListLeftPush(key, "red sox"); + db.ListLeftPush(key, "rays"); + + var res = await db.ListLeftPopAsync(["empty-key", key, "also-empty"], 2); + + Assert.False(res.IsNull); + Assert.Equal(2, res.Values.Length); + Assert.Equal("rays", res.Values[0]); + Assert.Equal("red sox", res.Values[1]); + + res = await db.ListRightPopAsync(["empty-key", key, "also-empty"], 1); + + Assert.False(res.IsNull); + Assert.Single(res.Values); + Assert.Equal("yankees", res.Values[0]); + } + + [Fact] + public async Task ListMultiPopSingleKey() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key); + + db.ListLeftPush(key, "yankees"); + db.ListLeftPush(key, "blue jays"); + db.ListLeftPush(key, "orioles"); + db.ListLeftPush(key, "red sox"); + db.ListLeftPush(key, "rays"); + + var res = db.ListLeftPop([key], 1); + + Assert.False(res.IsNull); + Assert.Single(res.Values); + Assert.Equal("rays", res.Values[0]); + + res = db.ListRightPop([key], 2); + + Assert.False(res.IsNull); + Assert.Equal(2, res.Values.Length); + Assert.Equal("yankees", res.Values[0]); + Assert.Equal("blue jays", res.Values[1]); + } + + [Fact] + public async Task ListMultiPopZeroCount() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key); + + var exception = await Assert.ThrowsAsync(() => db.ListLeftPopAsync([key], 0)); + Assert.Contains("ERR count should be greater than 0", exception.Message); + } + + [Fact] + public async Task ListMultiPopEmpty() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key); + + var res = await db.ListLeftPopAsync([key], 1); + Assert.True(res.IsNull); + } + + [Fact] + public async Task ListMultiPopEmptyKeys() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var exception = Assert.Throws(() => db.ListRightPop(Array.Empty(), 5)); + Assert.Contains("keys must have a size of at least 1", exception.Message); + + exception = Assert.Throws(() => db.ListLeftPop(Array.Empty(), 5)); + Assert.Contains("keys must have a size of at least 1", exception.Message); + } +} diff --git a/tests/StackExchange.Redis.Tests/LockingTests.cs b/tests/StackExchange.Redis.Tests/LockingTests.cs new file mode 100644 index 000000000..52d03bb83 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/LockingTests.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class LockingTests(ITestOutputHelper output) : TestBase(output) +{ + public enum TestMode + { + MultiExec, + NoMultiExec, + Twemproxy, + } + + public static IEnumerable> TestModes() + { + yield return new(TestMode.MultiExec); + yield return new(TestMode.NoMultiExec); + yield return new(TestMode.Twemproxy); + } + + [Theory, MemberData(nameof(TestModes))] + public void AggressiveParallel(TestMode testMode) + { + int count = 2; + int errorCount = 0; + int bgErrorCount = 0; + var evt = new ManualResetEvent(false); + var key = Me() + testMode; + using (var conn1 = Create(testMode)) + using (var conn2 = Create(testMode)) + { + void Inner(object? obj) + { + try + { + var conn = (IDatabase?)obj!; + conn.Multiplexer.ErrorMessage += (sender, e) => Interlocked.Increment(ref errorCount); + + for (int i = 0; i < 1000; i++) + { + conn.LockTakeAsync(key, "def", TimeSpan.FromSeconds(5)); + } + conn.Ping(); + if (Interlocked.Decrement(ref count) == 0) evt.Set(); + } + catch + { + Interlocked.Increment(ref bgErrorCount); + } + } + int db = testMode == TestMode.Twemproxy ? 0 : 2; + ThreadPool.QueueUserWorkItem(Inner, conn1.GetDatabase(db)); + ThreadPool.QueueUserWorkItem(Inner, conn2.GetDatabase(db)); + evt.WaitOne(8000); + } + Assert.Equal(0, Interlocked.CompareExchange(ref errorCount, 0, 0)); + Assert.Equal(0, bgErrorCount); + } + + [Fact] + public async Task TestOpCountByVersionLocal_UpLevel() + { + await using var conn = Create(shared: false); + + TestLockOpCountByVersion(conn, 1, false); + TestLockOpCountByVersion(conn, 1, true); + } + + private void TestLockOpCountByVersion(IConnectionMultiplexer conn, int expectedOps, bool existFirst) + { + const int LockDuration = 30; + RedisKey key = Me(); + + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + RedisValue newVal = "us:" + Guid.NewGuid().ToString(); + RedisValue expectedVal = newVal; + if (existFirst) + { + expectedVal = "other:" + Guid.NewGuid().ToString(); + db.StringSet(key, expectedVal, TimeSpan.FromSeconds(LockDuration), flags: CommandFlags.FireAndForget); + } + long countBefore = GetServer(conn).GetCounters().Interactive.OperationCount; + + var taken = db.LockTake(key, newVal, TimeSpan.FromSeconds(LockDuration)); + + long countAfter = GetServer(conn).GetCounters().Interactive.OperationCount; + var valAfter = db.StringGet(key); + + Assert.Equal(!existFirst, taken); + Assert.Equal(expectedVal, valAfter); + // note we get a ping from GetCounters + Assert.True(countAfter - countBefore >= expectedOps, $"({countAfter} - {countBefore}) >= {expectedOps}"); + } + + private IConnectionMultiplexer Create(TestMode mode) => mode switch + { + TestMode.MultiExec => Create(), + TestMode.NoMultiExec => Create(disabledCommands: ["multi", "exec"]), + TestMode.Twemproxy => Create(proxy: Proxy.Twemproxy), + _ => throw new NotSupportedException(mode.ToString()), + }; + + [Theory, MemberData(nameof(TestModes))] + public async Task TakeLockAndExtend(TestMode testMode) + { + await using var conn = Create(testMode); + + RedisValue right = Guid.NewGuid().ToString(), + wrong = Guid.NewGuid().ToString(); + + int dbId = testMode == TestMode.Twemproxy ? 0 : 7; + RedisKey key = Me() + testMode; + + var db = conn.GetDatabase(dbId); + + db.KeyDelete(key, CommandFlags.FireAndForget); + + bool withTran = testMode == TestMode.MultiExec; + var t1 = db.LockTakeAsync(key, right, TimeSpan.FromSeconds(20)); + var t1b = db.LockTakeAsync(key, wrong, TimeSpan.FromSeconds(10)); + var t2 = db.LockQueryAsync(key); + var t3 = withTran ? db.LockReleaseAsync(key, wrong) : null; + var t4 = db.LockQueryAsync(key); + var t5 = withTran ? db.LockExtendAsync(key, wrong, TimeSpan.FromSeconds(60)) : null; + var t6 = db.LockQueryAsync(key); + var t7 = db.KeyTimeToLiveAsync(key); + var t8 = db.LockExtendAsync(key, right, TimeSpan.FromSeconds(60)); + var t9 = db.LockQueryAsync(key); + var t10 = db.KeyTimeToLiveAsync(key); + var t11 = db.LockReleaseAsync(key, right); + var t12 = db.LockQueryAsync(key); + var t13 = db.LockTakeAsync(key, wrong, TimeSpan.FromSeconds(10)); + + Assert.NotEqual(default(RedisValue), right); + Assert.NotEqual(default(RedisValue), wrong); + Assert.NotEqual(right, wrong); + Assert.True(await t1, "1"); + Assert.False(await t1b, "1b"); + Assert.Equal(right, await t2); + if (withTran) Assert.False(await t3!, "3"); + Assert.Equal(right, await t4); + if (withTran) Assert.False(await t5!, "5"); + Assert.Equal(right, await t6); + var ttl = (await t7)!.Value.TotalSeconds; + Assert.True(ttl > 0 && ttl <= 20, "7"); + Assert.True(await t8, "8"); + Assert.Equal(right, await t9); + ttl = (await t10)!.Value.TotalSeconds; + Assert.True(ttl > 50 && ttl <= 60, "10"); + Assert.True(await t11, "11"); + Assert.Null((string?)await t12); + Assert.True(await t13, "13"); + } + + [Theory, MemberData(nameof(TestModes))] + public async Task TestBasicLockNotTaken(TestMode testMode) + { + await using var conn = Create(testMode); + + int errorCount = 0; + conn.ErrorMessage += (sender, e) => Interlocked.Increment(ref errorCount); + Task? taken = null; + Task? newValue = null; + Task? ttl = null; + + const int LOOP = 50; + var db = conn.GetDatabase(); + var key = Me() + testMode; + for (int i = 0; i < LOOP; i++) + { + _ = db.KeyDeleteAsync(key); + taken = db.LockTakeAsync(key, "new-value", TimeSpan.FromSeconds(10)); + newValue = db.StringGetAsync(key); + ttl = db.KeyTimeToLiveAsync(key); + } + Assert.True(await taken!, "taken"); + Assert.Equal("new-value", await newValue!); + var ttlValue = (await ttl!)!.Value.TotalSeconds; + Assert.True(ttlValue >= 8 && ttlValue <= 10, "ttl"); + + Assert.Equal(0, errorCount); + } + + [Theory, MemberData(nameof(TestModes))] + public async Task TestBasicLockTaken(TestMode testMode) + { + await using var conn = Create(testMode); + + var db = conn.GetDatabase(); + var key = Me() + testMode; + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, "old-value", TimeSpan.FromSeconds(20), flags: CommandFlags.FireAndForget); + var taken = db.LockTakeAsync(key, "new-value", TimeSpan.FromSeconds(10)); + var newValue = db.StringGetAsync(key); + var ttl = db.KeyTimeToLiveAsync(key); + + Assert.False(await taken, "taken"); + Assert.Equal("old-value", await newValue); + var ttlValue = (await ttl)!.Value.TotalSeconds; + Assert.True(ttlValue >= 18 && ttlValue <= 20, "ttl"); + } +} diff --git a/tests/StackExchange.Redis.Tests/LoggerTests.cs b/tests/StackExchange.Redis.Tests/LoggerTests.cs new file mode 100644 index 000000000..682856baa --- /dev/null +++ b/tests/StackExchange.Redis.Tests/LoggerTests.cs @@ -0,0 +1,129 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class LoggerTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task BasicLoggerConfig() + { + var traceLogger = new TestLogger(LogLevel.Trace, Writer); + var debugLogger = new TestLogger(LogLevel.Debug, Writer); + var infoLogger = new TestLogger(LogLevel.Information, Writer); + var warningLogger = new TestLogger(LogLevel.Warning, Writer); + var errorLogger = new TestLogger(LogLevel.Error, Writer); + var criticalLogger = new TestLogger(LogLevel.Critical, Writer); + + var options = ConfigurationOptions.Parse(GetConfiguration()); + options.LoggerFactory = new TestWrapperLoggerFactory(new TestMultiLogger(traceLogger, debugLogger, infoLogger, warningLogger, errorLogger, criticalLogger)); + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options); + // We expect more at the trace level: GET, ECHO, PING on commands + Assert.True(traceLogger.CallCount > debugLogger.CallCount); + // Many calls for all log lines - don't set exact here since every addition would break the test + Assert.True(debugLogger.CallCount > 30); + Assert.True(infoLogger.CallCount > 30); + // No debug calls at this time + // We expect no error/critical level calls to have happened here + Assert.Equal(0, errorLogger.CallCount); + Assert.Equal(0, criticalLogger.CallCount); + } + + [Fact] + public async Task WrappedLogger() + { + var options = ConfigurationOptions.Parse(GetConfiguration()); + var wrapped = new TestWrapperLoggerFactory(NullLogger.Instance); + options.LoggerFactory = wrapped; + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options); + Assert.True(wrapped.Logger.LogCount > 0); + } + + public class TestWrapperLoggerFactory(ILogger logger) : ILoggerFactory + { + public TestWrapperLogger Logger { get; } = new TestWrapperLogger(logger); + + public void AddProvider(ILoggerProvider provider) { } + public ILogger CreateLogger(string categoryName) => Logger; + public void Dispose() { } + } + + public class TestWrapperLogger(ILogger toWrap) : ILogger + { + public int LogCount = 0; + private ILogger Inner { get; } = toWrap; + +#if NET8_0_OR_GREATER + public IDisposable? BeginScope(TState state) where TState : notnull => Inner.BeginScope(state); +#else + public IDisposable BeginScope(TState state) => Inner.BeginScope(state); +#endif + public bool IsEnabled(LogLevel logLevel) => Inner.IsEnabled(logLevel); + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + Interlocked.Increment(ref LogCount); + Inner.Log(logLevel, eventId, state, exception, formatter); + } + } + + /// + /// To save on test time, no reason to spin up n connections just to test n logging implementations... + /// + private sealed class TestMultiLogger(params ILogger[] loggers) : ILogger + { +#if NET8_0_OR_GREATER + public IDisposable? BeginScope(TState state) where TState : notnull => null; +#else + public IDisposable BeginScope(TState state) => null!; +#endif + public bool IsEnabled(LogLevel logLevel) => true; + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + foreach (var logger in loggers) + { + logger.Log(logLevel, eventId, state, exception, formatter); + } + } + } + + private sealed class TestLogger : ILogger + { + private readonly StringBuilder sb = new StringBuilder(); + private long _callCount; + private readonly LogLevel _logLevel; + private readonly TextWriter _output; + public TestLogger(LogLevel logLevel, TextWriter output) => + (_logLevel, _output) = (logLevel, output); + +#if NET8_0_OR_GREATER + public IDisposable? BeginScope(TState state) where TState : notnull => null; +#else + public IDisposable BeginScope(TState state) => null!; +#endif + public bool IsEnabled(LogLevel logLevel) => logLevel >= _logLevel; + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (!IsEnabled(logLevel)) + { + return; + } + + Interlocked.Increment(ref _callCount); + var logLine = $"{_logLevel}> [LogLevel: {logLevel}, EventId: {eventId}]: {formatter?.Invoke(state, exception)}"; + sb.AppendLine(logLine); + _output.WriteLine(logLine); + } + + public long CallCount => Interlocked.Read(ref _callCount); + public override string ToString() => sb.ToString(); + } +} diff --git a/tests/StackExchange.Redis.Tests/MassiveOpsTests.cs b/tests/StackExchange.Redis.Tests/MassiveOpsTests.cs new file mode 100644 index 000000000..0140c7c97 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/MassiveOpsTests.cs @@ -0,0 +1,115 @@ +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class MassiveOpsTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task LongRunning() + { + Skip.UnlessLongRunning(); + await using var conn = Create(); + + var key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, "test value", flags: CommandFlags.FireAndForget); + for (var i = 0; i < 200; i++) + { + var val = await db.StringGetAsync(key).ForAwait(); + Assert.Equal("test value", val); + await Task.Delay(50).ForAwait(); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task MassiveBulkOpsAsync(bool withContinuation) + { + await using var conn = Create(); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + await db.PingAsync().ForAwait(); + static void NonTrivial(Task unused) + { + Thread.SpinWait(5); + } + var watch = Stopwatch.StartNew(); + for (int i = 0; i <= AsyncOpsQty; i++) + { + var t = db.StringSetAsync(key, i); + if (withContinuation) + { + // Intentionally unawaited + _ = t.ContinueWith(NonTrivial); + } + } + Assert.Equal(AsyncOpsQty, await db.StringGetAsync(key).ForAwait()); + watch.Stop(); + Log($"{Me()}: Time for {AsyncOpsQty} ops: {watch.ElapsedMilliseconds}ms ({(withContinuation ? "with continuation" : "no continuation")}, any order); ops/s: {AsyncOpsQty / watch.Elapsed.TotalSeconds}"); + } + + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(10)] + [InlineData(50)] + public async Task MassiveBulkOpsSync(int threads) + { + Skip.UnlessLongRunning(); + await using var conn = Create(syncTimeout: 30000); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + int workPerThread = SyncOpsQty / threads; + var timeTaken = RunConcurrent( + () => + { + for (int i = 0; i < workPerThread; i++) + { + db.StringIncrement(key, flags: CommandFlags.FireAndForget); + } + }, + threads); + + int val = (int)db.StringGet(key); + Assert.Equal(workPerThread * threads, val); + Log($"{Me()}: Time for {threads * workPerThread} ops on {threads} threads: {timeTaken.TotalMilliseconds}ms (any order); ops/s: {(workPerThread * threads) / timeTaken.TotalSeconds}"); + } + + [Theory] + [InlineData(1)] + [InlineData(5)] + public async Task MassiveBulkOpsFireAndForget(int threads) + { + await using var conn = Create(syncTimeout: 30000); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + await db.PingAsync(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + int perThread = AsyncOpsQty / threads; + var elapsed = RunConcurrent( + () => + { + for (int i = 0; i < perThread; i++) + { + db.StringIncrement(key, flags: CommandFlags.FireAndForget); + } + db.Ping(); + }, + threads); + var val = (long)db.StringGet(key); + Assert.Equal(perThread * threads, val); + + Log($"{Me()}: Time for {val} ops over {threads} threads: {elapsed.TotalMilliseconds:###,###}ms (any order); ops/s: {val / elapsed.TotalSeconds:###,###,##0}"); + } +} diff --git a/tests/StackExchange.Redis.Tests/MemoryTests.cs b/tests/StackExchange.Redis.Tests/MemoryTests.cs new file mode 100644 index 000000000..48d3ea705 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/MemoryTests.cs @@ -0,0 +1,73 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class MemoryTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task CanCallDoctor() + { + await using var conn = Create(require: RedisFeatures.v4_0_0); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + string? doctor = server.MemoryDoctor(); + Assert.NotNull(doctor); + Assert.NotEqual("", doctor); + + doctor = await server.MemoryDoctorAsync(); + Assert.NotNull(doctor); + Assert.NotEqual("", doctor); + } + + [Fact] + public async Task CanPurge() + { + await using var conn = Create(require: RedisFeatures.v4_0_0); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + server.MemoryPurge(); + await server.MemoryPurgeAsync(); + + await server.MemoryPurgeAsync(); + } + + [Fact] + public async Task GetAllocatorStats() + { + await using var conn = Create(require: RedisFeatures.v4_0_0); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + + var stats = server.MemoryAllocatorStats(); + Assert.False(string.IsNullOrWhiteSpace(stats)); + + stats = await server.MemoryAllocatorStatsAsync(); + Assert.False(string.IsNullOrWhiteSpace(stats)); + } + + [Fact] + public async Task GetStats() + { + await using var conn = Create(require: RedisFeatures.v4_0_0); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + var stats = server.MemoryStats(); + Assert.NotNull(stats); + Assert.Equal(ResultType.Array, stats.Resp2Type); + + var parsed = stats.ToDictionary(); + + var alloc = parsed["total.allocated"]; + Assert.Equal(ResultType.Integer, alloc.Resp2Type); + Assert.True(alloc.AsInt64() > 0); + + stats = await server.MemoryStatsAsync(); + Assert.NotNull(stats); + Assert.Equal(ResultType.Array, stats.Resp2Type); + + alloc = parsed["total.allocated"]; + Assert.Equal(ResultType.Integer, alloc.Resp2Type); + Assert.True(alloc.AsInt64() > 0); + } +} diff --git a/tests/StackExchange.Redis.Tests/MigrateTests.cs b/tests/StackExchange.Redis.Tests/MigrateTests.cs new file mode 100644 index 000000000..9939e0632 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/MigrateTests.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class MigrateTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task Basic() + { + Skip.UnlessLongRunning(); + var fromConfig = new ConfigurationOptions { EndPoints = { { TestConfig.Current.SecureServer, TestConfig.Current.SecurePort } }, Password = TestConfig.Current.SecurePassword, AllowAdmin = true }; + var toConfig = new ConfigurationOptions { EndPoints = { { TestConfig.Current.PrimaryServer, TestConfig.Current.PrimaryPort } }, AllowAdmin = true }; + + await using var fromConn = ConnectionMultiplexer.Connect(fromConfig, Writer); + await using var toConn = ConnectionMultiplexer.Connect(toConfig, Writer); + + if (await IsWindows(fromConn) || await IsWindows(toConn)) + Assert.Skip("'migrate' is unreliable on redis-64"); + + RedisKey key = Me(); + var fromDb = fromConn.GetDatabase(); + var toDb = toConn.GetDatabase(); + fromDb.KeyDelete(key, CommandFlags.FireAndForget); + toDb.KeyDelete(key, CommandFlags.FireAndForget); + fromDb.StringSet(key, "foo", flags: CommandFlags.FireAndForget); + var dest = toConn.GetEndPoints(true).Single(); + Log("Migrating key..."); + fromDb.KeyMigrate(key, dest, migrateOptions: MigrateOptions.Replace); + Log("Migration command complete"); + + // this is *meant* to be synchronous at the redis level, but + // we keep seeing it fail on the CI server where the key has *left* the origin, but + // has *not* yet arrived at the destination; adding a pause while we investigate with + // the redis folks + await UntilConditionAsync(TimeSpan.FromSeconds(15), () => !fromDb.KeyExists(key) && toDb.KeyExists(key)); + + Assert.False(fromDb.KeyExists(key), "Exists at source"); + Assert.True(toDb.KeyExists(key), "Exists at destination"); + string? s = toDb.StringGet(key); + Assert.Equal("foo", s); + } + + private static async Task IsWindows(ConnectionMultiplexer conn) + { + var server = conn.GetServer(conn.GetEndPoints().First()); + var section = (await server.InfoAsync("server")).Single(); + var os = section.FirstOrDefault( + x => string.Equals("os", x.Key, StringComparison.OrdinalIgnoreCase)); + // note: WSL returns things like "os:Linux 4.4.0-17134-Microsoft x86_64" + return string.Equals("windows", os.Value, StringComparison.OrdinalIgnoreCase); + } +} diff --git a/tests/StackExchange.Redis.Tests/MultiAddTests.cs b/tests/StackExchange.Redis.Tests/MultiAddTests.cs new file mode 100644 index 000000000..f5fb66335 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/MultiAddTests.cs @@ -0,0 +1,141 @@ +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class MultiAddTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task AddSortedSetEveryWay() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SortedSetAdd(key, "a", 1, CommandFlags.FireAndForget); + db.SortedSetAdd( + key, + [ + new SortedSetEntry("b", 2), + ], + CommandFlags.FireAndForget); + db.SortedSetAdd( + key, + [ + new SortedSetEntry("c", 3), + new SortedSetEntry("d", 4), + ], + CommandFlags.FireAndForget); + db.SortedSetAdd( + key, + [ + new SortedSetEntry("e", 5), + new SortedSetEntry("f", 6), + new SortedSetEntry("g", 7), + ], + CommandFlags.FireAndForget); + db.SortedSetAdd( + key, + [ + new SortedSetEntry("h", 8), + new SortedSetEntry("i", 9), + new SortedSetEntry("j", 10), + new SortedSetEntry("k", 11), + ], + CommandFlags.FireAndForget); + var vals = db.SortedSetRangeByScoreWithScores(key); + string s = string.Join(",", vals.OrderByDescending(x => x.Score).Select(x => x.Element)); + Assert.Equal("k,j,i,h,g,f,e,d,c,b,a", s); + s = string.Join(",", vals.OrderBy(x => x.Score).Select(x => x.Score)); + Assert.Equal("1,2,3,4,5,6,7,8,9,10,11", s); + } + + [Fact] + public async Task AddHashEveryWay() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.HashSet(key, "a", 1, flags: CommandFlags.FireAndForget); + db.HashSet( + key, + [ + new HashEntry("b", 2), + ], + CommandFlags.FireAndForget); + db.HashSet( + key, + [ + new HashEntry("c", 3), + new HashEntry("d", 4), + ], + CommandFlags.FireAndForget); + db.HashSet( + key, + [ + new HashEntry("e", 5), + new HashEntry("f", 6), + new HashEntry("g", 7), + ], + CommandFlags.FireAndForget); + db.HashSet( + key, + [ + new HashEntry("h", 8), + new HashEntry("i", 9), + new HashEntry("j", 10), + new HashEntry("k", 11), + ], + CommandFlags.FireAndForget); + var vals = db.HashGetAll(key); + string s = string.Join(",", vals.OrderByDescending(x => (double)x.Value).Select(x => x.Name)); + Assert.Equal("k,j,i,h,g,f,e,d,c,b,a", s); + s = string.Join(",", vals.OrderBy(x => (double)x.Value).Select(x => x.Value)); + Assert.Equal("1,2,3,4,5,6,7,8,9,10,11", s); + } + + [Fact] + public async Task AddSetEveryWay() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + RedisKey key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SetAdd(key, "a", CommandFlags.FireAndForget); + db.SetAdd(key, ["b"], CommandFlags.FireAndForget); + db.SetAdd(key, ["c", "d"], CommandFlags.FireAndForget); + db.SetAdd(key, ["e", "f", "g"], CommandFlags.FireAndForget); + db.SetAdd(key, ["h", "i", "j", "k"], CommandFlags.FireAndForget); + + var vals = db.SetMembers(key); + string s = string.Join(",", vals.OrderByDescending(x => x)); + Assert.Equal("k,j,i,h,g,f,e,d,c,b,a", s); + } + + [Fact] + public async Task AddSetEveryWayNumbers() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + RedisKey key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SetAdd(key, "a", CommandFlags.FireAndForget); + db.SetAdd(key, ["1"], CommandFlags.FireAndForget); + db.SetAdd(key, ["11", "2"], CommandFlags.FireAndForget); + db.SetAdd(key, ["10", "3", "1.5"], CommandFlags.FireAndForget); + db.SetAdd(key, ["2.2", "-1", "s", "t"], CommandFlags.FireAndForget); + + var vals = db.SetMembers(key); + string s = string.Join(",", vals.OrderByDescending(x => x)); + Assert.Equal("t,s,a,11,10,3,2.2,2,1.5,1,-1", s); + } +} diff --git a/tests/StackExchange.Redis.Tests/MultiPrimaryTests.cs b/tests/StackExchange.Redis.Tests/MultiPrimaryTests.cs new file mode 100644 index 000000000..3d88e097c --- /dev/null +++ b/tests/StackExchange.Redis.Tests/MultiPrimaryTests.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class MultiPrimaryTests(ITestOutputHelper output) : TestBase(output) +{ + protected override string GetConfiguration() => + TestConfig.Current.PrimaryServerAndPort + "," + TestConfig.Current.SecureServerAndPort + ",password=" + TestConfig.Current.SecurePassword; + + [Fact] + public async Task CannotFlushReplica() + { + var ex = await Assert.ThrowsAsync(async () => + { + await using var conn = await ConnectionMultiplexer.ConnectAsync(TestConfig.Current.ReplicaServerAndPort + ",allowAdmin=true"); + + var servers = conn.GetEndPoints().Select(e => conn.GetServer(e)); + var replica = servers.FirstOrDefault(x => x.IsReplica); + Assert.NotNull(replica); // replica not found, ruh roh + replica.FlushDatabase(); + }); + Assert.Equal("Command cannot be issued to a replica: FLUSHDB", ex.Message); + } + + [Fact] + public void TestMultiNoTieBreak() + { + var log = new StringBuilder(); + Writer.EchoTo(log); + using (Create(log: Writer, tieBreaker: "")) + { + Assert.Contains("Choosing primary arbitrarily", log.ToString()); + } + } + + public static IEnumerable GetConnections() + { + yield return new object[] { TestConfig.Current.PrimaryServerAndPort, TestConfig.Current.PrimaryServerAndPort, TestConfig.Current.PrimaryServerAndPort }; + yield return new object[] { TestConfig.Current.SecureServerAndPort, TestConfig.Current.SecureServerAndPort, TestConfig.Current.SecureServerAndPort }; + yield return new object?[] { TestConfig.Current.SecureServerAndPort, TestConfig.Current.PrimaryServerAndPort, null }; + yield return new object?[] { TestConfig.Current.PrimaryServerAndPort, TestConfig.Current.SecureServerAndPort, null }; + + yield return new object?[] { null, TestConfig.Current.PrimaryServerAndPort, null }; + yield return new object?[] { TestConfig.Current.PrimaryServerAndPort, null, TestConfig.Current.PrimaryServerAndPort }; + yield return new object?[] { null, TestConfig.Current.SecureServerAndPort, TestConfig.Current.SecureServerAndPort }; + yield return new object?[] { TestConfig.Current.SecureServerAndPort, null, TestConfig.Current.SecureServerAndPort }; + yield return new object?[] { null, null, null }; + } + + [Theory, MemberData(nameof(GetConnections))] + public void TestMultiWithTiebreak(string a, string b, string elected) + { + const string TieBreak = "__tie__"; + // set the tie-breakers to the expected state + using (var aConn = ConnectionMultiplexer.Connect(TestConfig.Current.PrimaryServerAndPort)) + { + aConn.GetDatabase().StringSet(TieBreak, a); + } + using (var aConn = ConnectionMultiplexer.Connect(TestConfig.Current.SecureServerAndPort + ",password=" + TestConfig.Current.SecurePassword)) + { + aConn.GetDatabase().StringSet(TieBreak, b); + } + + // see what happens + var log = new StringBuilder(); + Writer.EchoTo(log); + + using (Create(log: Writer, tieBreaker: TieBreak)) + { + string text = log.ToString(); + Assert.False(text.Contains("failed to nominate"), "failed to nominate"); + if (elected != null) + { + Assert.True(text.Contains("Elected: " + elected), "elected"); + } + int nullCount = (a == null ? 1 : 0) + (b == null ? 1 : 0); + if ((a == b && nullCount == 0) || nullCount == 1) + { + Assert.True(text.Contains("Election: Tie-breaker unanimous"), "unanimous"); + Assert.False(text.Contains("Election: Choosing primary arbitrarily"), "arbitrarily"); + } + else + { + Assert.False(text.Contains("Election: Tie-breaker unanimous"), "unanimous"); + Assert.True(text.Contains("Election: Choosing primary arbitrarily"), "arbitrarily"); + } + } + } +} diff --git a/tests/StackExchange.Redis.Tests/NamingTests.cs b/tests/StackExchange.Redis.Tests/NamingTests.cs new file mode 100644 index 000000000..9d9e032ad --- /dev/null +++ b/tests/StackExchange.Redis.Tests/NamingTests.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class NamingTests(ITestOutputHelper output) : TestBase(output) +{ + [Theory] + [InlineData(typeof(IDatabase), false)] + [InlineData(typeof(IDatabaseAsync), true)] + [InlineData(typeof(Condition), false)] + public void CheckSignatures(Type type, bool isAsync) + { + // check that all methods and interfaces look appropriate for their sync/async nature + CheckName(type, isAsync); + var members = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); + foreach (var member in members) + { + if (member.Name.StartsWith("get_") || member.Name.StartsWith("set_") || member.Name.StartsWith("add_") || member.Name.StartsWith("remove_")) continue; + CheckMethod(member, isAsync); + } + } + + /// + /// This test iterates over all s to ensure we have everything accounted for as primary-only or not. + /// + [Fact] + public void CheckReadOnlyOperations() + { + List primaryReplica = new(), + primaryOnly = new(); + foreach (var val in (RedisCommand[])Enum.GetValues(typeof(RedisCommand))) + { + bool isPrimaryOnly = val.IsPrimaryOnly(); + (isPrimaryOnly ? primaryOnly : primaryReplica).Add(val); + + if (!isPrimaryOnly) + { + Log(val.ToString()); + } + } + // Ensure an unknown command from nowhere would violate the check above, as any not-yet-added one would. + Assert.Throws(() => ((RedisCommand)99999).IsPrimaryOnly()); + + Log("primary-only: {0}, vs primary/replica: {1}", primaryOnly.Count, primaryReplica.Count); + Log(""); + Log("primary-only:"); + foreach (var val in primaryOnly) + { + Log(val.ToString()); + } + Log(""); + Log("primary/replica:"); + foreach (var val in primaryReplica) + { + Log(val.ToString()); + } + } + + [Theory] + [InlineData(typeof(IDatabase))] + [InlineData(typeof(IDatabaseAsync))] + public void CheckDatabaseMethodsUseKeys(Type type) + { + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + if (IgnoreMethodConventions(method)) continue; + + switch (method.Name) + { + case nameof(IDatabase.KeyRandom): + case nameof(IDatabaseAsync.KeyRandomAsync): + case nameof(IDatabase.Publish): + case nameof(IDatabaseAsync.PublishAsync): + case nameof(IDatabase.Execute): + case nameof(IDatabaseAsync.ExecuteAsync): + case nameof(IDatabase.ScriptEvaluate): + case nameof(IDatabaseAsync.ScriptEvaluateAsync): + case nameof(IDatabase.StreamRead): + case nameof(IDatabase.StreamReadAsync): + case nameof(IDatabase.StreamReadGroup): + case nameof(IDatabase.StreamReadGroupAsync): + continue; // they're fine, but don't want to widen check to return type + } + + bool usesKey = method.GetParameters().Any(p => UsesKey(p.ParameterType)); + Assert.True(usesKey, type.Name + ":" + method.Name); + } + } + + private static bool UsesKey(Type type) => + type == typeof(RedisKey) + || (type.IsArray && UsesKey(type.GetElementType()!)) + || (type.IsGenericType && type.GetGenericArguments().Any(UsesKey)); + + private static bool IgnoreMethodConventions(MethodInfo method) + { + string name = method.Name; + if (name.StartsWith("get_") || name.StartsWith("set_") || name.StartsWith("add_") || name.StartsWith("remove_")) return true; + switch (name) + { + case nameof(IDatabase.CreateBatch): + case nameof(IDatabase.CreateTransaction): + case nameof(IDatabase.Execute): + case nameof(IDatabaseAsync.ExecuteAsync): + case nameof(IDatabase.IsConnected): + case nameof(IDatabase.SetScan): + case nameof(IDatabase.SortedSetScan): + case nameof(IDatabase.HashScan): + case nameof(IDatabase.HashScanNoValues): + case nameof(ISubscriber.SubscribedEndpoint): + return true; + } + return false; + } + + [Theory] + [InlineData(typeof(IDatabase), typeof(IDatabaseAsync))] + [InlineData(typeof(IDatabaseAsync), typeof(IDatabase))] + public void CheckSyncAsyncMethodsMatch(Type from, Type to) + { + const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; + int count = 0; + foreach (var method in from.GetMethods(flags)) + { + if (IgnoreMethodConventions(method)) continue; + + string name = method.Name, huntName; + + if (name.EndsWith("Async")) huntName = name.Substring(0, name.Length - 5); + else huntName = name + "Async"; + var pFrom = method.GetParameters(); + Type[] args = pFrom.Select(x => x.ParameterType).ToArray(); + Log("Checking: {0}.{1}", from.Name, method.Name); + if (method.GetCustomAttribute() is EditorBrowsableAttribute attr && attr.State == EditorBrowsableState.Never) + { + // For compatibility overloads, explicitly don't ensure CommandFlags is last + } + else + { + Assert.Equal(typeof(CommandFlags), args.Last()); + } + var found = to.GetMethod(huntName, flags, null, method.CallingConvention, args, null); + Assert.NotNull(found); // "Found " + name + ", no " + huntName + var pTo = found.GetParameters(); + + for (int i = 0; i < pFrom.Length; i++) + { + Assert.Equal(pFrom[i].Name, pTo[i].Name); // method.Name + ":" + pFrom[i].Name + Assert.Equal(pFrom[i].ParameterType, pTo[i].ParameterType); // method.Name + ":" + pFrom[i].Name + } + + count++; + } + Log("Validated: {0} ({1} methods)", from.Name, count); + } + + private void CheckMethod(MethodInfo method, bool isAsync) + { + string shortName = method.Name, fullName = method.DeclaringType?.Name + "." + shortName; + + switch (shortName) + { + case nameof(IDatabaseAsync.IsConnected): + return; + case nameof(IDatabase.CreateBatch): + case nameof(IDatabase.CreateTransaction): + case nameof(IDatabase.IdentifyEndpoint): + case nameof(IDatabase.Sort): + case nameof(IDatabase.SortAndStore): + case nameof(IDatabaseAsync.IdentifyEndpointAsync): + case nameof(IDatabaseAsync.SortAsync): + case nameof(IDatabaseAsync.SortAndStoreAsync): + CheckName(method, isAsync); + break; + default: + CheckName(method, isAsync); + var isValid = shortName.StartsWith("Debug") + || shortName.StartsWith("Execute") + || shortName.StartsWith("Geo") + || shortName.StartsWith("Hash") + || shortName.StartsWith("HyperLogLog") + || shortName.StartsWith("Key") + || shortName.StartsWith("List") + || shortName.StartsWith("Lock") + || shortName.StartsWith("Publish") + || shortName.StartsWith("Set") + || shortName.StartsWith("Script") + || shortName.StartsWith("SortedSet") + || shortName.StartsWith("String") + || shortName.StartsWith("Stream") + || shortName.StartsWith("VectorSet"); + Log(fullName + ": " + (isValid ? "valid" : "invalid")); + Assert.True(isValid, fullName + ":Prefix"); + break; + } + + Assert.False(shortName.Contains("If"), fullName + ":If"); // should probably be a When option + + var returnType = method.ReturnType ?? typeof(void); + + if (isAsync) + { + Assert.True(IsAsyncMethod(returnType), fullName + ":Task"); + } + else + { + Assert.False(IsAsyncMethod(returnType), fullName + ":Task"); + } + + static bool IsAsyncMethod(Type returnType) + { + if (returnType == typeof(Task)) return true; + if (returnType == typeof(ValueTask)) return true; + + if (returnType.IsGenericType) + { + var genDef = returnType.GetGenericTypeDefinition(); + if (genDef == typeof(Task<>)) return true; + if (genDef == typeof(ValueTask<>)) return true; + if (genDef == typeof(IAsyncEnumerable<>)) return true; + } + + return false; + } + } + + private static void CheckName(MemberInfo member, bool isAsync) + { + if (isAsync) Assert.True(member.Name.EndsWith("Async"), member.Name + ":Name - end *Async"); + else Assert.False(member.Name.EndsWith("Async"), member.Name + ":Name - don't end *Async"); + } +} diff --git a/tests/StackExchange.Redis.Tests/OverloadCompatTests.cs b/tests/StackExchange.Redis.Tests/OverloadCompatTests.cs new file mode 100644 index 000000000..c695488f2 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/OverloadCompatTests.cs @@ -0,0 +1,248 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +/// +/// This test set is for when we add an overload, to making sure all +/// past versions work correctly and aren't source breaking. +/// +public class OverloadCompatTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task KeyExpire() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + var key = Me(); + var expiresIn = TimeSpan.FromSeconds(10); + var expireTime = DateTime.UtcNow.AddHours(1); + var when = ExpireWhen.Always; + var flags = CommandFlags.None; + + db.KeyExpire(key, expiresIn); + db.KeyExpire(key, expiresIn, when); + db.KeyExpire(key, expiresIn, when: when); + db.KeyExpire(key, expiresIn, flags); + db.KeyExpire(key, expiresIn, flags: flags); + db.KeyExpire(key, expiresIn, when, flags); + db.KeyExpire(key, expiresIn, when: when, flags: flags); + + db.KeyExpire(key, expireTime); + db.KeyExpire(key, expireTime, when); + db.KeyExpire(key, expireTime, when: when); + db.KeyExpire(key, expireTime, flags); + db.KeyExpire(key, expireTime, flags: flags); + db.KeyExpire(key, expireTime, when, flags); + db.KeyExpire(key, expireTime, when: when, flags: flags); + + // Async + await db.KeyExpireAsync(key, expiresIn); + await db.KeyExpireAsync(key, expiresIn, when); + await db.KeyExpireAsync(key, expiresIn, when: when); + await db.KeyExpireAsync(key, expiresIn, flags); + await db.KeyExpireAsync(key, expiresIn, flags: flags); + await db.KeyExpireAsync(key, expiresIn, when, flags); + await db.KeyExpireAsync(key, expiresIn, when: when, flags: flags); + + await db.KeyExpireAsync(key, expireTime); + await db.KeyExpireAsync(key, expireTime, when); + await db.KeyExpireAsync(key, expireTime, when: when); + await db.KeyExpireAsync(key, expireTime, flags); + await db.KeyExpireAsync(key, expireTime, flags: flags); + await db.KeyExpireAsync(key, expireTime, when, flags); + await db.KeyExpireAsync(key, expireTime, when: when, flags: flags); + } + + [Fact] + public async Task StringBitCount() + { + await using var conn = Create(require: RedisFeatures.v2_6_0); + + var db = conn.GetDatabase(); + var key = Me(); + var flags = CommandFlags.None; + + db.KeyDelete(key, flags: CommandFlags.FireAndForget); + db.StringSet(key, "foobar", flags: CommandFlags.FireAndForget); + + db.StringBitCount(key); + db.StringBitCount(key, 1); + db.StringBitCount(key, 0, 0); + db.StringBitCount(key, start: 1); + db.StringBitCount(key, end: 1); + db.StringBitCount(key, start: 1, end: 1); + + db.StringBitCount(key, flags: flags); + db.StringBitCount(key, 0, 0, flags); + db.StringBitCount(key, 1, flags: flags); + db.StringBitCount(key, 1, 1, flags: flags); + db.StringBitCount(key, start: 1, flags: flags); + db.StringBitCount(key, end: 1, flags: flags); + db.StringBitCount(key, start: 1, end: 1, flags); + db.StringBitCount(key, start: 1, end: 1, flags: flags); + + // Async + await db.StringBitCountAsync(key); + await db.StringBitCountAsync(key, 1); + await db.StringBitCountAsync(key, 0, 0); + await db.StringBitCountAsync(key, start: 1); + await db.StringBitCountAsync(key, end: 1); + await db.StringBitCountAsync(key, start: 1, end: 1); + + await db.StringBitCountAsync(key, flags: flags); + await db.StringBitCountAsync(key, 0, 0, flags); + await db.StringBitCountAsync(key, 1, flags: flags); + await db.StringBitCountAsync(key, 1, 1, flags: flags); + await db.StringBitCountAsync(key, start: 1, flags: flags); + await db.StringBitCountAsync(key, end: 1, flags: flags); + await db.StringBitCountAsync(key, start: 1, end: 1, flags); + await db.StringBitCountAsync(key, start: 1, end: 1, flags: flags); + } + + [Fact] + public async Task StringBitPosition() + { + await using var conn = Create(require: RedisFeatures.v2_6_0); + + var db = conn.GetDatabase(); + var key = Me(); + var flags = CommandFlags.None; + + db.KeyDelete(key, flags: CommandFlags.FireAndForget); + db.StringSet(key, "foo", flags: CommandFlags.FireAndForget); + + db.StringBitPosition(key, true); + db.StringBitPosition(key, true, 1); + db.StringBitPosition(key, true, 1, 3); + db.StringBitPosition(key, bit: true); + db.StringBitPosition(key, bit: true, start: 1); + db.StringBitPosition(key, bit: true, end: 1); + db.StringBitPosition(key, bit: true, start: 1, end: 1); + db.StringBitPosition(key, true, start: 1, end: 1); + + db.StringBitPosition(key, true, flags: flags); + db.StringBitPosition(key, true, 1, 3, flags); + db.StringBitPosition(key, true, 1, flags: flags); + db.StringBitPosition(key, bit: true, flags: flags); + db.StringBitPosition(key, bit: true, start: 1, flags: flags); + db.StringBitPosition(key, bit: true, end: 1, flags: flags); + db.StringBitPosition(key, bit: true, start: 1, end: 1, flags: flags); + db.StringBitPosition(key, true, start: 1, end: 1, flags: flags); + + // Async + await db.StringBitPositionAsync(key, true); + await db.StringBitPositionAsync(key, true, 1); + await db.StringBitPositionAsync(key, true, 1, 3); + await db.StringBitPositionAsync(key, bit: true); + await db.StringBitPositionAsync(key, bit: true, start: 1); + await db.StringBitPositionAsync(key, bit: true, end: 1); + await db.StringBitPositionAsync(key, bit: true, start: 1, end: 1); + await db.StringBitPositionAsync(key, true, start: 1, end: 1); + + await db.StringBitPositionAsync(key, true, flags: flags); + await db.StringBitPositionAsync(key, true, 1, 3, flags); + await db.StringBitPositionAsync(key, true, 1, flags: flags); + await db.StringBitPositionAsync(key, bit: true, flags: flags); + await db.StringBitPositionAsync(key, bit: true, start: 1, flags: flags); + await db.StringBitPositionAsync(key, bit: true, end: 1, flags: flags); + await db.StringBitPositionAsync(key, bit: true, start: 1, end: 1, flags: flags); + await db.StringBitPositionAsync(key, true, start: 1, end: 1, flags: flags); + } + + [Fact] + public async Task SortedSetAdd() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + RedisKey key = Me(); + RedisValue val = "myval"; + var score = 1.0d; + var values = new SortedSetEntry[] { new SortedSetEntry(val, score) }; + var when = When.Exists; + var flags = CommandFlags.None; + + db.SortedSetAdd(key, val, score); + db.SortedSetAdd(key, val, score, when); + db.SortedSetAdd(key, val, score, when: when); + db.SortedSetAdd(key, val, score, flags); + db.SortedSetAdd(key, val, score, flags: flags); + db.SortedSetAdd(key, val, score, when, flags); + db.SortedSetAdd(key, val, score, when, flags: flags); + db.SortedSetAdd(key, val, score, when: when, flags); + db.SortedSetAdd(key, val, score, when: when, flags: flags); + + db.SortedSetAdd(key, values); + db.SortedSetAdd(key, values, when); + db.SortedSetAdd(key, values, when: when); + db.SortedSetAdd(key, values, flags); + db.SortedSetAdd(key, values, flags: flags); + db.SortedSetAdd(key, values, when, flags); + db.SortedSetAdd(key, values, when, flags: flags); + db.SortedSetAdd(key, values, when: when, flags); + db.SortedSetAdd(key, values, when: when, flags: flags); + + // Async + await db.SortedSetAddAsync(key, val, score); + await db.SortedSetAddAsync(key, val, score, when); + await db.SortedSetAddAsync(key, val, score, when: when); + await db.SortedSetAddAsync(key, val, score, flags); + await db.SortedSetAddAsync(key, val, score, flags: flags); + await db.SortedSetAddAsync(key, val, score, when, flags); + await db.SortedSetAddAsync(key, val, score, when, flags: flags); + await db.SortedSetAddAsync(key, val, score, when: when, flags); + await db.SortedSetAddAsync(key, val, score, when: when, flags: flags); + + await db.SortedSetAddAsync(key, values); + await db.SortedSetAddAsync(key, values, when); + await db.SortedSetAddAsync(key, values, when: when); + await db.SortedSetAddAsync(key, values, flags); + await db.SortedSetAddAsync(key, values, flags: flags); + await db.SortedSetAddAsync(key, values, when, flags); + await db.SortedSetAddAsync(key, values, when, flags: flags); + await db.SortedSetAddAsync(key, values, when: when, flags); + await db.SortedSetAddAsync(key, values, when: when, flags: flags); + } + + [Fact] + public async Task StringSet() + { + await using var conn = Create(); + var db = conn.GetDatabase(); + var key = Me(); + var val = "myval"; + var expiresIn = TimeSpan.FromSeconds(10); + var when = When.Always; + var flags = CommandFlags.None; + + db.StringSet(key, val); + db.StringSet(key, val, expiry: expiresIn); + db.StringSet(key, val, when: when); + db.StringSet(key, val, flags: flags); + db.StringSet(key, val, expiry: expiresIn, when: when); + db.StringSet(key, val, expiry: expiresIn, when: when, flags: flags); + db.StringSet(key, val, expiry: expiresIn, when: when, flags: flags); + + db.StringSet(key, val, expiresIn, When.NotExists); + db.StringSet(key, val, expiresIn, When.NotExists, flags); + db.StringSet(key, val, null); + db.StringSet(key, val, null, When.NotExists); + db.StringSet(key, val, null, When.NotExists, flags); + + // Async + await db.StringSetAsync(key, val); + await db.StringSetAsync(key, val, expiry: expiresIn); + await db.StringSetAsync(key, val, when: when); + await db.StringSetAsync(key, val, flags: flags); + await db.StringSetAsync(key, val, expiry: expiresIn, when: when); + await db.StringSetAsync(key, val, expiry: expiresIn, when: when, flags: flags); + await db.StringSetAsync(key, val, expiry: expiresIn, when: when, flags: flags); + + await db.StringSetAsync(key, val, expiresIn, When.NotExists); + await db.StringSetAsync(key, val, expiresIn, When.NotExists, flags); + await db.StringSetAsync(key, val, null); + await db.StringSetAsync(key, val, null, When.NotExists); + await db.StringSetAsync(key, val, null, When.NotExists, flags); + } +} diff --git a/tests/StackExchange.Redis.Tests/ParseTests.cs b/tests/StackExchange.Redis.Tests/ParseTests.cs new file mode 100644 index 000000000..2621ddab9 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ParseTests.cs @@ -0,0 +1,96 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Text; +using Pipelines.Sockets.Unofficial.Arenas; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ParseTests(ITestOutputHelper output) : TestBase(output) +{ + public static IEnumerable GetTestData() + { + yield return new object[] { "$4\r\nPING\r\n$4\r\nPON", 1 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG", 1 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG\r", 1 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG\r\n", 2 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG\r\n$4", 2 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG\r\n$4\r", 2 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG\r\n$4\r\n", 2 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG\r\n$4\r\nP", 2 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG\r\n$4\r\nPO", 2 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG\r\n$4\r\nPON", 2 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG\r\n$4\r\nPONG", 2 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG\r\n$4\r\nPONG\r", 2 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG\r\n$4\r\nPONG\r\n", 3 }; + yield return new object[] { "$4\r\nPING\r\n$4\r\nPONG\r\n$4\r\nPONG\r\n$", 3 }; + } + + [Theory] + [MemberData(nameof(GetTestData))] + public void ParseAsSingleChunk(string ascii, int expected) + { + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes(ascii)); + using (var arena = new Arena()) + { + ProcessMessages(arena, buffer, expected); + } + } + + [Theory] + [MemberData(nameof(GetTestData))] + public void ParseAsLotsOfChunks(string ascii, int expected) + { + var bytes = Encoding.ASCII.GetBytes(ascii); + FragmentedSegment? chain = null, tail = null; + for (int i = 0; i < bytes.Length; i++) + { + var next = new FragmentedSegment(i, new ReadOnlyMemory(bytes, i, 1)); + if (tail == null) + { + chain = next; + } + else + { + tail.Next = next; + } + tail = next; + } + var buffer = new ReadOnlySequence(chain!, 0, tail!, 1); + Assert.Equal(bytes.Length, buffer.Length); + using (var arena = new Arena()) + { + ProcessMessages(arena, buffer, expected); + } + } + + private void ProcessMessages(Arena arena, ReadOnlySequence buffer, int expected) + { + Log($"chain: {buffer.Length}"); + var reader = new BufferReader(buffer); + RawResult result; + int found = 0; + while (!(result = PhysicalConnection.TryParseResult(false, arena, buffer, ref reader, false, null, false)).IsNull) + { + Log($"{result} - {result.GetString()}"); + found++; + } + Assert.Equal(expected, found); + } + + private sealed class FragmentedSegment : ReadOnlySequenceSegment + { + public FragmentedSegment(long runningIndex, ReadOnlyMemory memory) + { + RunningIndex = runningIndex; + Memory = memory; + } + + public new FragmentedSegment? Next + { + get => (FragmentedSegment?)base.Next; + set => base.Next = value; + } + } +} diff --git a/tests/StackExchange.Redis.Tests/PerformanceTests.cs b/tests/StackExchange.Redis.Tests/PerformanceTests.cs new file mode 100644 index 000000000..b308bf0ac --- /dev/null +++ b/tests/StackExchange.Redis.Tests/PerformanceTests.cs @@ -0,0 +1,130 @@ +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class PerformanceTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task VerifyPerformanceImprovement() + { + Skip.UnlessLongRunning(); + int asyncTimer, sync, op = 0, asyncFaF, syncFaF; + var key = Me(); + await using (var conn = Create()) + { + // do these outside the timings, just to ensure the core methods are JITted etc + for (int dbId = 0; dbId < 5; dbId++) + { + _ = conn.GetDatabase(dbId).KeyDeleteAsync(key); + } + + var timer = Stopwatch.StartNew(); + for (int i = 0; i < 100; i++) + { + // want to test multiplex scenario; test each db, but to make it fair we'll + // do in batches of 10 on each + for (int dbId = 0; dbId < 5; dbId++) + { + var db = conn.GetDatabase(dbId); + for (int j = 0; j < 10; j++) + { + _ = db.StringIncrementAsync(key); + } + } + } + asyncFaF = (int)timer.ElapsedMilliseconds; + var final = new Task[5]; + for (int db = 0; db < 5; db++) + final[db] = conn.GetDatabase(db).StringGetAsync(key); + conn.WaitAll(final); + timer.Stop(); + asyncTimer = (int)timer.ElapsedMilliseconds; + Log("async to completion (local): {0}ms", timer.ElapsedMilliseconds); + for (int db = 0; db < 5; db++) + { + Assert.Equal(1000, (long)final[db].Result); // "async, db:" + db + } + } + + using (var conn = new RedisSharp.Redis(TestConfig.Current.PrimaryServer, TestConfig.Current.PrimaryPort)) + { + // do these outside the timings, just to ensure the core methods are JITted etc + for (int db = 0; db < 5; db++) + { + conn.Db = db; + conn.Remove(key); + } + + var timer = Stopwatch.StartNew(); + for (int i = 0; i < 100; i++) + { + // want to test multiplex scenario; test each db, but to make it fair we'll + // do in batches of 10 on each + for (int db = 0; db < 5; db++) + { + conn.Db = db; + op++; + for (int j = 0; j < 10; j++) + { + conn.Increment(key); + op++; + } + } + } + syncFaF = (int)timer.ElapsedMilliseconds; + string[] final = new string[5]; + for (int db = 0; db < 5; db++) + { + conn.Db = db; + final[db] = Encoding.ASCII.GetString(conn.Get(key)); + } + timer.Stop(); + sync = (int)timer.ElapsedMilliseconds; + Log("sync to completion (local): {0}ms", timer.ElapsedMilliseconds); + for (int db = 0; db < 5; db++) + { + Assert.Equal("1000", final[db]); // "async, db:" + db + } + } + int effectiveAsync = ((10 * asyncTimer) + 3) / 10; + int effectiveSync = ((10 * sync) + (op * 3)) / 10; + Log("async to completion with assumed 0.3ms LAN latency: " + effectiveAsync); + Log("sync to completion with assumed 0.3ms LAN latency: " + effectiveSync); + Log("fire-and-forget: {0}ms sync vs {1}ms async ", syncFaF, asyncFaF); + Assert.True(effectiveAsync < effectiveSync, "Everything"); + Assert.True(asyncFaF < syncFaF, "Fire and Forget"); + } + + [Fact] + public async Task BasicStringGetPerf() + { + await using var conn = Create(); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + await db.StringSetAsync(key, "some value").ForAwait(); + + // this is just to JIT everything before we try testing + var syncVal = db.StringGet(key); + var asyncVal = await db.StringGetAsync(key).ForAwait(); + + var syncTimer = Stopwatch.StartNew(); + syncVal = db.StringGet(key); + syncTimer.Stop(); + + var asyncTimer = Stopwatch.StartNew(); + asyncVal = await db.StringGetAsync(key).ForAwait(); + asyncTimer.Stop(); + + Log($"Sync: {syncTimer.ElapsedMilliseconds}; Async: {asyncTimer.ElapsedMilliseconds}"); + Assert.Equal("some value", syncVal); + Assert.Equal("some value", asyncVal); + // let's allow 20% async overhead + // But with a floor, since the base can often be zero + Assert.True(asyncTimer.ElapsedMilliseconds <= System.Math.Max(syncTimer.ElapsedMilliseconds * 1.2M, 50)); + } +} diff --git a/tests/StackExchange.Redis.Tests/PreserveOrderTests.cs b/tests/StackExchange.Redis.Tests/PreserveOrderTests.cs new file mode 100644 index 000000000..ce20ccf7b --- /dev/null +++ b/tests/StackExchange.Redis.Tests/PreserveOrderTests.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class PreserveOrderTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task Execute() + { + await using var conn = Create(); + + var sub = conn.GetSubscriber(); + var channel = Me(); + var received = new List(); + Log("Subscribing..."); + const int COUNT = 500; + sub.Subscribe(RedisChannel.Literal(channel), (_, message) => + { + lock (received) + { + received.Add((int)message); + if (received.Count == COUNT) + Monitor.PulseAll(received); // wake the test rig + } + }); + Log(""); + Log("Sending (any order)..."); + lock (received) + { + received.Clear(); + // we'll also use received as a wait-detection mechanism; sneaky + + // note: this does not do any cheating; + // it all goes to the server and back + for (int i = 0; i < COUNT; i++) + { + sub.Publish(RedisChannel.Literal(channel), i, CommandFlags.FireAndForget); + } + + Log("Allowing time for delivery etc..."); + var watch = Stopwatch.StartNew(); + if (!Monitor.Wait(received, 10000)) + { + Log("Timed out; expect less data"); + } + watch.Stop(); + Log("Checking..."); + lock (received) + { + Log("Received: {0} in {1}ms", received.Count, watch.ElapsedMilliseconds); + int wrongOrder = 0; + for (int i = 0; i < Math.Min(COUNT, received.Count); i++) + { + if (received[i] != i) wrongOrder++; + } + Log("Out of order: " + wrongOrder); + } + } + } +} diff --git a/tests/StackExchange.Redis.Tests/ProfilingTests.cs b/tests/StackExchange.Redis.Tests/ProfilingTests.cs new file mode 100644 index 000000000..366abd395 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ProfilingTests.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using StackExchange.Redis.Profiling; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class ProfilingTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task Simple() + { + await using var conn = Create(); + + var server = conn.GetServer(TestConfig.Current.PrimaryServerAndPort); + var script = LuaScript.Prepare("return redis.call('get', @key)"); + var loaded = script.Load(server); + var key = Me(); + + var session = new ProfilingSession(); + + conn.RegisterProfiler(() => session); + + var dbId = TestConfig.GetDedicatedDB(conn); + var db = conn.GetDatabase(dbId); + db.StringSet(key, "world"); + var result = db.ScriptEvaluate(script, new { key = (RedisKey)key }); + Assert.NotNull(result); + Assert.Equal("world", result.AsString()); + var loadedResult = db.ScriptEvaluate(loaded, new { key = (RedisKey)key }); + Assert.NotNull(loadedResult); + Assert.Equal("world", loadedResult.AsString()); + var val = db.StringGet(key); + Assert.Equal("world", val); + var s = (string?)db.Execute("ECHO", "fii"); + Assert.Equal("fii", s); + + var cmds = session.FinishProfiling(); + var evalCmds = cmds.Where(c => c.Command == "EVAL").ToList(); + Assert.Equal(2, evalCmds.Count); + var i = 0; + foreach (var cmd in cmds) + { + Log($"Command {i++} (DB: {cmd.Db}): {cmd?.ToString()?.Replace("\n", ", ")}"); + } + + var all = string.Join(",", cmds.Select(x => x.Command)); + Assert.Equal("SET,EVAL,EVAL,GET,ECHO", all); + Log("Checking for SET"); + var set = cmds.SingleOrDefault(cmd => cmd.Command == "SET"); + Assert.NotNull(set); + Log("Checking for GET"); + var get = cmds.SingleOrDefault(cmd => cmd.Command == "GET"); + Assert.NotNull(get); + Log("Checking for EVAL"); + var eval1 = evalCmds[0]; + Log("Checking for EVAL"); + var eval2 = evalCmds[1]; + var echo = cmds.SingleOrDefault(cmd => cmd.Command == "ECHO"); + Assert.NotNull(echo); + + Assert.Equal(5, cmds.Count()); + + Assert.True(set.CommandCreated <= eval1.CommandCreated); + Assert.True(eval1.CommandCreated <= eval2.CommandCreated); + Assert.True(eval2.CommandCreated <= get.CommandCreated); + + AssertProfiledCommandValues(set, conn, dbId); + + AssertProfiledCommandValues(get, conn, dbId); + + AssertProfiledCommandValues(eval1, conn, dbId); + + AssertProfiledCommandValues(eval2, conn, dbId); + + AssertProfiledCommandValues(echo, conn, dbId); + } + + private static void AssertProfiledCommandValues(IProfiledCommand command, IConnectionMultiplexer conn, int dbId) + { + Assert.Equal(dbId, command.Db); + Assert.Equal(conn.GetEndPoints()[0], command.EndPoint); + Assert.True(command.CreationToEnqueued > TimeSpan.Zero, nameof(command.CreationToEnqueued)); + Assert.True(command.EnqueuedToSending > TimeSpan.Zero, nameof(command.EnqueuedToSending)); + Assert.True(command.SentToResponse > TimeSpan.Zero, nameof(command.SentToResponse)); + Assert.True(command.ResponseToCompletion >= TimeSpan.Zero, nameof(command.ResponseToCompletion)); + Assert.True(command.ElapsedTime > TimeSpan.Zero, nameof(command.ElapsedTime)); + Assert.True(command.ElapsedTime > command.CreationToEnqueued && command.ElapsedTime > command.EnqueuedToSending && command.ElapsedTime > command.SentToResponse, "Comparisons"); + Assert.True(command.RetransmissionOf == null, nameof(command.RetransmissionOf)); + Assert.True(command.RetransmissionReason == null, nameof(command.RetransmissionReason)); + } + + [Fact] + public async Task ManyThreads() + { + Skip.UnlessLongRunning(); + await using var conn = Create(); + + var session = new ProfilingSession(); + var prefix = Me(); + + conn.RegisterProfiler(() => session); + + var threads = new List(); + const int CountPer = 100; + for (var i = 1; i <= 16; i++) + { + var db = conn.GetDatabase(i); + + threads.Add(new Thread(() => + { + var threadTasks = new List(); + + for (var j = 0; j < CountPer; j++) + { + var task = db.StringSetAsync(prefix + j, "" + j); + threadTasks.Add(task); + } + + Task.WaitAll(threadTasks.ToArray()); + })); + } + + threads.ForEach(thread => thread.Start()); + threads.ForEach(thread => thread.Join()); + + var allVals = session.FinishProfiling(); + var relevant = allVals.Where(cmd => cmd.Db > 0).ToList(); + + var kinds = relevant.Select(cmd => cmd.Command).Distinct().ToList(); + foreach (var k in kinds) + { + Log("Kind Seen: " + k); + } + Assert.True(kinds.Count <= 2); + Assert.Contains("SET", kinds); + if (kinds.Count == 2 && !kinds.Contains("SELECT") && !kinds.Contains("GET")) + { + Assert.Fail("Non-SET, Non-SELECT, Non-GET command seen"); + } + + Assert.Equal(16 * CountPer, relevant.Count); + Assert.Equal(16, relevant.Select(cmd => cmd.Db).Distinct().Count()); + + for (var i = 1; i <= 16; i++) + { + var setsInDb = relevant.Count(cmd => cmd.Db == i); + Assert.Equal(CountPer, setsInDb); + } + } + + [Fact] + public async Task ManyContexts() + { + Skip.UnlessLongRunning(); + await using var conn = Create(); + + var profiler = new AsyncLocalProfiler(); + var prefix = Me(); + conn.RegisterProfiler(profiler.GetSession); + + var tasks = new Task[16]; + + var results = new ProfiledCommandEnumerable[tasks.Length]; + + for (var i = 0; i < tasks.Length; i++) + { + var ix = i; + tasks[ix] = Task.Run(async () => + { + var db = conn.GetDatabase(ix); + + var allTasks = new List(); + + for (var j = 0; j < 1000; j++) + { + var g = db.StringGetAsync(prefix + ix); + var s = db.StringSetAsync(prefix + ix, "world" + ix); + // overlap the g+s, just for fun + await g; + await s; + } + + results[ix] = profiler.GetSession().FinishProfiling(); + }); + } + Task.WhenAll(tasks).Wait(); + + for (var i = 0; i < results.Length; i++) + { + var res = results[i]; + + var numGets = res.Count(r => r.Command == "GET"); + var numSets = res.Count(r => r.Command == "SET"); + + Assert.Equal(1000, numGets); + Assert.Equal(1000, numSets); + Assert.True(res.All(cmd => cmd.Db == i)); + } + } + + internal sealed class PerThreadProfiler + { + private readonly ThreadLocal perThreadSession = new ThreadLocal(() => new ProfilingSession()); + + public ProfilingSession GetSession() => perThreadSession.Value!; + } + + internal sealed class AsyncLocalProfiler + { + private readonly AsyncLocal perThreadSession = new AsyncLocal(); + + public ProfilingSession GetSession() + { + var val = perThreadSession.Value; + if (val == null) + { + perThreadSession.Value = val = new ProfilingSession(); + } + return val; + } + } + + [Fact] + public async Task LowAllocationEnumerable() + { + await using var conn = Create(); + + const int OuterLoop = 1000; + var session = new ProfilingSession(); + conn.RegisterProfiler(() => session); + + var prefix = Me(); + var db = conn.GetDatabase(1); + + var allTasks = new List>(); + + foreach (var i in Enumerable.Range(0, OuterLoop)) + { + var t = db.StringSetAsync(prefix + i, "bar" + i).ContinueWith(async _ => (string?)(await db.StringGetAsync(prefix + i).ForAwait())); + + var finalResult = t.Unwrap(); + allTasks.Add(finalResult); + } + + conn.WaitAll(allTasks.ToArray()); + + var res = session.FinishProfiling(); + Assert.True(res.GetType().IsValueType); + + using (var e = res.GetEnumerator()) + { + Assert.True(e.GetType().IsValueType); + + Assert.True(e.MoveNext()); + var i = e.Current; + + e.Reset(); + Assert.True(e.MoveNext()); + var j = e.Current; + + Assert.True(ReferenceEquals(i, j)); + } + + Assert.Equal(OuterLoop, res.Count(r => r.Command == "GET" && r.Db > 0)); + Assert.Equal(OuterLoop, res.Count(r => r.Command == "SET" && r.Db > 0)); + Assert.Equal(OuterLoop * 2, res.Count(r => r.Db > 0)); + } + + [Fact] + public async Task ProfilingMD_Ex1() + { + Skip.UnlessLongRunning(); + await using var conn = Create(); + + var session = new ProfilingSession(); + var prefix = Me(); + + conn.RegisterProfiler(() => session); + + var threads = new List(); + + for (var i = 0; i < 16; i++) + { + var db = conn.GetDatabase(i); + + var thread = new Thread(() => + { + var threadTasks = new List(); + + for (var j = 0; j < 1000; j++) + { + var task = db.StringSetAsync(prefix + j, "" + j); + threadTasks.Add(task); + } + + Task.WaitAll(threadTasks.ToArray()); + }); + + threads.Add(thread); + } + + threads.ForEach(thread => thread.Start()); + threads.ForEach(thread => thread.Join()); + + IEnumerable timings = session.FinishProfiling(); + + Assert.Equal(16000, timings.Count()); + } + + [Fact] + public async Task ProfilingMD_Ex2() + { + Skip.UnlessLongRunning(); + await using var conn = Create(); + + var profiler = new PerThreadProfiler(); + var prefix = Me(); + + conn.RegisterProfiler(profiler.GetSession); + + var threads = new List(); + + var perThreadTimings = new ConcurrentDictionary>(); + + for (var i = 0; i < 16; i++) + { + var db = conn.GetDatabase(i); + + var thread = new Thread(() => + { + var threadTasks = new List(); + + for (var j = 0; j < 1000; j++) + { + var task = db.StringSetAsync(prefix + j, "" + j); + threadTasks.Add(task); + } + + Task.WaitAll(threadTasks.ToArray()); + + perThreadTimings[Thread.CurrentThread] = profiler.GetSession().FinishProfiling().ToList(); + }); + + threads.Add(thread); + } + + threads.ForEach(thread => thread.Start()); + threads.ForEach(thread => thread.Join()); + + Assert.Equal(16, perThreadTimings.Count); + Assert.True(perThreadTimings.All(kv => kv.Value.Count == 1000)); + } + + [Fact] + public async Task ProfilingMD_Ex2_Async() + { + Skip.UnlessLongRunning(); + await using var conn = Create(); + + var profiler = new AsyncLocalProfiler(); + var prefix = Me(); + + conn.RegisterProfiler(profiler.GetSession); + + var tasks = new List(); + + var perThreadTimings = new ConcurrentBag>(); + + for (var i = 0; i < 16; i++) + { + var db = conn.GetDatabase(i); + + var task = Task.Run(async () => + { + for (var j = 0; j < 100; j++) + { + await db.StringSetAsync(prefix + j, "" + j).ForAwait(); + } + + perThreadTimings.Add(profiler.GetSession().FinishProfiling().ToList()); + }); + + tasks.Add(task); + } + + var timeout = Task.Delay(10000); + var complete = Task.WhenAll(tasks); + if (timeout == await Task.WhenAny(timeout, complete).ForAwait()) + { + throw new TimeoutException(); + } + + Assert.Equal(16, perThreadTimings.Count); + foreach (var item in perThreadTimings) + { + Assert.Equal(100, item.Count); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/PubSubCommandTests.cs b/tests/StackExchange.Redis.Tests/PubSubCommandTests.cs new file mode 100644 index 000000000..71b82e262 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/PubSubCommandTests.cs @@ -0,0 +1,94 @@ +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class PubSubCommandTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task SubscriberCount() + { + await using var conn = Create(); + +#pragma warning disable CS0618 + RedisChannel channel = Me() + Guid.NewGuid(); + var server = conn.GetServer(conn.GetEndPoints()[0]); + + var channels = server.SubscriptionChannels(Me() + "*"); +#pragma warning restore CS0618 + Assert.DoesNotContain(channel, channels); + + _ = server.SubscriptionPatternCount(); + var count = server.SubscriptionSubscriberCount(channel); + Assert.Equal(0, count); + conn.GetSubscriber().Subscribe(channel, (channel, value) => { }); + count = server.SubscriptionSubscriberCount(channel); + Assert.Equal(1, count); + +#pragma warning disable CS0618 + channels = server.SubscriptionChannels(Me() + "*"); +#pragma warning restore CS0618 + Assert.Contains(channel, channels); + } + + [Fact] + public async Task SubscriberCountAsync() + { + await using var conn = Create(); + +#pragma warning disable CS0618 + RedisChannel channel = Me() + Guid.NewGuid(); +#pragma warning restore CS0618 + var server = conn.GetServer(conn.GetEndPoints()[0]); + +#pragma warning disable CS0618 + var channels = await server.SubscriptionChannelsAsync(Me() + "*").WithTimeout(2000); +#pragma warning restore CS0618 + Assert.DoesNotContain(channel, channels); + + _ = await server.SubscriptionPatternCountAsync().WithTimeout(2000); + var count = await server.SubscriptionSubscriberCountAsync(channel).WithTimeout(2000); + Assert.Equal(0, count); + await conn.GetSubscriber().SubscribeAsync(channel, (channel, value) => { }).WithTimeout(2000); + count = await server.SubscriptionSubscriberCountAsync(channel).WithTimeout(2000); + Assert.Equal(1, count); + +#pragma warning disable CS0618 + channels = await server.SubscriptionChannelsAsync(Me() + "*").WithTimeout(2000); +#pragma warning restore CS0618 + Assert.Contains(channel, channels); + } +} +internal static class Util +{ + public static async Task WithTimeout(this Task task, int timeoutMs, [CallerMemberName] string? caller = null, [CallerLineNumber] int line = 0) + { + var cts = new CancellationTokenSource(); + if (task == await Task.WhenAny(task, Task.Delay(timeoutMs, cts.Token)).ForAwait()) + { + cts.Cancel(); + await task.ForAwait(); + } + else + { + throw new TimeoutException($"timeout from {caller} line {line}"); + } + } + public static async Task WithTimeout(this Task task, int timeoutMs, [CallerMemberName] string? caller = null, [CallerLineNumber] int line = 0) + { + var cts = new CancellationTokenSource(); + if (task == await Task.WhenAny(task, Task.Delay(timeoutMs, cts.Token)).ForAwait()) + { + cts.Cancel(); + return await task.ForAwait(); + } + else + { + throw new TimeoutException($"timout from {caller} line {line}"); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/PubSubMultiserverTests.cs b/tests/StackExchange.Redis.Tests/PubSubMultiserverTests.cs new file mode 100644 index 000000000..43bb4b2b8 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/PubSubMultiserverTests.cs @@ -0,0 +1,209 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class PubSubMultiserverTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + protected override string GetConfiguration() => TestConfig.Current.ClusterServersAndPorts + ",connectTimeout=10000"; + + [Fact] + public async Task ChannelSharding() + { + await using var conn = Create(channelPrefix: Me()); + + var defaultSlot = conn.ServerSelectionStrategy.HashSlot(default(RedisChannel)); + var slot1 = conn.ServerSelectionStrategy.HashSlot(RedisChannel.Literal("hey")); + var slot2 = conn.ServerSelectionStrategy.HashSlot(RedisChannel.Literal("hey2")); + + Assert.NotEqual(defaultSlot, slot1); + Assert.NotEqual(ServerSelectionStrategy.NoSlot, slot1); + Assert.NotEqual(slot1, slot2); + } + + [Fact] + public async Task ClusterNodeSubscriptionFailover() + { + Skip.UnlessLongRunning(); + Log("Connecting..."); + + await using var conn = Create(allowAdmin: true, shared: false); + + var sub = conn.GetSubscriber(); + var channel = RedisChannel.Literal(Me()); + + var count = 0; + Log("Subscribing..."); + await sub.SubscribeAsync(channel, (_, val) => + { + Interlocked.Increment(ref count); + Log("Message: " + val); + }); + Assert.True(sub.IsConnected(channel)); + + Log("Publishing (1)..."); + Assert.Equal(0, count); + var publishedTo = await sub.PublishAsync(channel, "message1"); + // Client -> Redis -> Client -> handler takes just a moment + await UntilConditionAsync(TimeSpan.FromSeconds(2), () => Volatile.Read(ref count) == 1); + Assert.Equal(1, count); + Log($" Published (1) to {publishedTo} subscriber(s)."); + Assert.Equal(1, publishedTo); + + var endpoint = sub.SubscribedEndpoint(channel)!; + var subscribedServer = conn.GetServer(endpoint); + var subscribedServerEndpoint = conn.GetServerEndPoint(endpoint); + + Assert.True(subscribedServer.IsConnected, "subscribedServer.IsConnected"); + Assert.NotNull(subscribedServerEndpoint); + Assert.True(subscribedServerEndpoint.IsConnected, "subscribedServerEndpoint.IsConnected"); + Assert.True(subscribedServerEndpoint.IsSubscriberConnected, "subscribedServerEndpoint.IsSubscriberConnected"); + + Assert.True(conn.GetSubscriptions().TryGetValue(channel, out var subscription)); + var initialServer = subscription.GetCurrentServer(); + Assert.NotNull(initialServer); + Assert.True(initialServer.IsConnected); + Log("Connected to: " + initialServer); + + conn.AllowConnect = false; + if (TestContext.Current.IsResp3()) + { + subscribedServerEndpoint.SimulateConnectionFailure(SimulatedFailureType.All); + + Assert.False(subscribedServerEndpoint.IsConnected, "subscribedServerEndpoint.IsConnected"); + Assert.False(subscribedServerEndpoint.IsSubscriberConnected, "subscribedServerEndpoint.IsSubscriberConnected"); + } + else + { + subscribedServerEndpoint.SimulateConnectionFailure(SimulatedFailureType.AllSubscription); + + Assert.True(subscribedServerEndpoint.IsConnected, "subscribedServerEndpoint.IsConnected"); + Assert.False(subscribedServerEndpoint.IsSubscriberConnected, "subscribedServerEndpoint.IsSubscriberConnected"); + } + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => subscription.IsConnected); + Assert.True(subscription.IsConnected); + + var newServer = subscription.GetCurrentServer(); + Assert.NotNull(newServer); + Assert.NotEqual(newServer, initialServer); + Log("Now connected to: " + newServer); + + count = 0; + Log("Publishing (2)..."); + Assert.Equal(0, count); + publishedTo = await sub.PublishAsync(channel, "message2"); + // Client -> Redis -> Client -> handler takes just a moment + await UntilConditionAsync(TimeSpan.FromSeconds(2), () => Volatile.Read(ref count) == 1); + Assert.Equal(1, count); + Log($" Published (2) to {publishedTo} subscriber(s)."); + + ClearAmbientFailures(); + } + + [Theory(Skip="TODO: Hostile")] + [InlineData(CommandFlags.PreferMaster, true)] + [InlineData(CommandFlags.PreferReplica, true)] + [InlineData(CommandFlags.DemandMaster, false)] + [InlineData(CommandFlags.DemandReplica, false)] + public async Task PrimaryReplicaSubscriptionFailover(CommandFlags flags, bool expectSuccess) + { + var config = TestConfig.Current.PrimaryServerAndPort + "," + TestConfig.Current.ReplicaServerAndPort; + Log("Connecting..."); + + await using var conn = Create(configuration: config, shared: false, allowAdmin: true); + + var sub = conn.GetSubscriber(); + var channel = RedisChannel.Literal(Me() + flags.ToString()); // Individual channel per case to not overlap publishers + + var count = 0; + Log("Subscribing..."); + await sub.SubscribeAsync( + channel, + (_, val) => + { + Interlocked.Increment(ref count); + Log("Message: " + val); + }, + flags); + Assert.True(sub.IsConnected(channel)); + + Log("Publishing (1)..."); + Assert.Equal(0, count); + var publishedTo = await sub.PublishAsync(channel, "message1"); + // Client -> Redis -> Client -> handler takes just a moment + await UntilConditionAsync(TimeSpan.FromSeconds(2), () => Volatile.Read(ref count) == 1); + Assert.Equal(1, count); + Log($" Published (1) to {publishedTo} subscriber(s)."); + + var endpoint = sub.SubscribedEndpoint(channel)!; + var subscribedServer = conn.GetServer(endpoint); + var subscribedServerEndpoint = conn.GetServerEndPoint(endpoint); + + Assert.True(subscribedServer.IsConnected, "subscribedServer.IsConnected"); + Assert.NotNull(subscribedServerEndpoint); + Assert.True(subscribedServerEndpoint.IsConnected, "subscribedServerEndpoint.IsConnected"); + Assert.True(subscribedServerEndpoint.IsSubscriberConnected, "subscribedServerEndpoint.IsSubscriberConnected"); + + Assert.True(conn.GetSubscriptions().TryGetValue(channel, out var subscription)); + var initialServer = subscription.GetCurrentServer(); + Assert.NotNull(initialServer); + Assert.True(initialServer.IsConnected); + Log("Connected to: " + initialServer); + + conn.AllowConnect = false; + if (TestContext.Current.IsResp3()) + { + subscribedServerEndpoint.SimulateConnectionFailure(SimulatedFailureType.All); // need to kill the main connection + Assert.False(subscribedServerEndpoint.IsConnected, "subscribedServerEndpoint.IsConnected"); + Assert.False(subscribedServerEndpoint.IsSubscriberConnected, "subscribedServerEndpoint.IsSubscriberConnected"); + } + else + { + subscribedServerEndpoint.SimulateConnectionFailure(SimulatedFailureType.AllSubscription); + Assert.True(subscribedServerEndpoint.IsConnected, "subscribedServerEndpoint.IsConnected"); + Assert.False(subscribedServerEndpoint.IsSubscriberConnected, "subscribedServerEndpoint.IsSubscriberConnected"); + } + + if (expectSuccess) + { + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => subscription.IsConnected); + Assert.True(subscription.IsConnected); + + var newServer = subscription.GetCurrentServer(); + Assert.NotNull(newServer); + Assert.NotEqual(newServer, initialServer); + Log("Now connected to: " + newServer); + } + else + { + // This subscription shouldn't be able to reconnect by flags (demanding an unavailable server) + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => subscription.IsConnected); + Assert.False(subscription.IsConnected); + Log("Unable to reconnect (as expected)"); + + // Allow connecting back to the original + conn.AllowConnect = true; + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => subscription.IsConnected); + Assert.True(subscription.IsConnected); + + var newServer = subscription.GetCurrentServer(); + Assert.NotNull(newServer); + Assert.Equal(newServer, initialServer); + Log("Now connected to: " + newServer); + } + + count = 0; + Log("Publishing (2)..."); + Assert.Equal(0, count); + publishedTo = await sub.PublishAsync(channel, "message2"); + // Client -> Redis -> Client -> handler takes just a moment + await UntilConditionAsync(TimeSpan.FromSeconds(2), () => Volatile.Read(ref count) == 1); + Assert.Equal(1, count); + Log($" Published (2) to {publishedTo} subscriber(s)."); + + ClearAmbientFailures(); + } +} diff --git a/tests/StackExchange.Redis.Tests/PubSubTests.cs b/tests/StackExchange.Redis.Tests/PubSubTests.cs new file mode 100644 index 000000000..a3dadb07e --- /dev/null +++ b/tests/StackExchange.Redis.Tests/PubSubTests.cs @@ -0,0 +1,815 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using StackExchange.Redis.Maintenance; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class PubSubTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task ExplicitPublishMode() + { + await using var conn = Create(channelPrefix: "foo:", log: Writer); + + var pub = conn.GetSubscriber(); + int a = 0, b = 0, c = 0, d = 0; + pub.Subscribe(new RedisChannel("*bcd", RedisChannel.PatternMode.Literal), (x, y) => Interlocked.Increment(ref a)); + pub.Subscribe(new RedisChannel("a*cd", RedisChannel.PatternMode.Pattern), (x, y) => Interlocked.Increment(ref b)); + pub.Subscribe(new RedisChannel("ab*d", RedisChannel.PatternMode.Auto), (x, y) => Interlocked.Increment(ref c)); +#pragma warning disable CS0618 + pub.Subscribe("abc*", (x, y) => Interlocked.Increment(ref d)); + + pub.Publish("abcd", "efg"); +#pragma warning restore CS0618 + await UntilConditionAsync( + TimeSpan.FromSeconds(10), + () => Volatile.Read(ref b) == 1 + && Volatile.Read(ref c) == 1 + && Volatile.Read(ref d) == 1); + Assert.Equal(0, Volatile.Read(ref a)); + Assert.Equal(1, Volatile.Read(ref b)); + Assert.Equal(1, Volatile.Read(ref c)); + Assert.Equal(1, Volatile.Read(ref d)); + +#pragma warning disable CS0618 + pub.Publish("*bcd", "efg"); +#pragma warning restore CS0618 + await UntilConditionAsync(TimeSpan.FromSeconds(10), () => Volatile.Read(ref a) == 1); + Assert.Equal(1, Volatile.Read(ref a)); + } + + [Theory] + [InlineData(null, false, "a")] + [InlineData("", false, "b")] + [InlineData("Foo:", false, "c")] + [InlineData(null, true, "d")] + [InlineData("", true, "e")] + [InlineData("Foo:", true, "f")] + public async Task TestBasicPubSub(string? channelPrefix, bool wildCard, string breaker) + { + await using var conn = Create(channelPrefix: channelPrefix, shared: false, log: Writer); + + var pub = GetAnyPrimary(conn); + var sub = conn.GetSubscriber(); + await PingAsync(pub, sub).ForAwait(); + HashSet received = []; + int secondHandler = 0; + string subChannel = (wildCard ? "a*c" : "abc") + breaker; + string pubChannel = "abc" + breaker; + Action handler1 = (channel, payload) => + { + lock (received) + { + if (channel == pubChannel) + { + received.Add(payload); + } + else + { + Log(channel); + } + } + }, handler2 = (_, __) => Interlocked.Increment(ref secondHandler); +#pragma warning disable CS0618 + sub.Subscribe(subChannel, handler1); + sub.Subscribe(subChannel, handler2); +#pragma warning restore CS0618 + + lock (received) + { + Assert.Empty(received); + } + Assert.Equal(0, Volatile.Read(ref secondHandler)); +#pragma warning disable CS0618 + var count = sub.Publish(pubChannel, "def"); +#pragma warning restore CS0618 + + await PingAsync(pub, sub, 3).ForAwait(); + + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => received.Count == 1); + lock (received) + { + Assert.Single(received); + } + // Give handler firing a moment + await UntilConditionAsync(TimeSpan.FromSeconds(2), () => Volatile.Read(ref secondHandler) == 1); + Assert.Equal(1, Volatile.Read(ref secondHandler)); + + // unsubscribe from first; should still see second +#pragma warning disable CS0618 + sub.Unsubscribe(subChannel, handler1); + count = sub.Publish(pubChannel, "ghi"); +#pragma warning restore CS0618 + await PingAsync(pub, sub).ForAwait(); + lock (received) + { + Assert.Single(received); + } + + await UntilConditionAsync(TimeSpan.FromSeconds(2), () => Volatile.Read(ref secondHandler) == 2); + + var secondHandlerCount = Volatile.Read(ref secondHandler); + Log("Expecting 2 from second handler, got: " + secondHandlerCount); + Assert.Equal(2, secondHandlerCount); + Assert.Equal(1, count); + + // unsubscribe from second; should see nothing this time +#pragma warning disable CS0618 + sub.Unsubscribe(subChannel, handler2); + count = sub.Publish(pubChannel, "ghi"); +#pragma warning restore CS0618 + await PingAsync(pub, sub).ForAwait(); + lock (received) + { + Assert.Single(received); + } + secondHandlerCount = Volatile.Read(ref secondHandler); + Log("Expecting 2 from second handler, got: " + secondHandlerCount); + Assert.Equal(2, secondHandlerCount); + Assert.Equal(0, count); + } + + [Fact] + public async Task TestBasicPubSubFireAndForget() + { + await using var conn = Create(shared: false, log: Writer); + + var profiler = conn.AddProfiler(); + var pub = GetAnyPrimary(conn); + var sub = conn.GetSubscriber(); + + RedisChannel key = RedisChannel.Literal(Me() + Guid.NewGuid()); + HashSet received = []; + int secondHandler = 0; + await PingAsync(pub, sub).ForAwait(); + sub.Subscribe( + key, + (channel, payload) => + { + lock (received) + { + if (channel == key) + { + received.Add(payload); + } + } + }, + CommandFlags.FireAndForget); + + sub.Subscribe(key, (_, __) => Interlocked.Increment(ref secondHandler), CommandFlags.FireAndForget); + Log(profiler); + + lock (received) + { + Assert.Empty(received); + } + Assert.Equal(0, Volatile.Read(ref secondHandler)); + await PingAsync(pub, sub).ForAwait(); + var count = sub.Publish(key, "def", CommandFlags.FireAndForget); + await PingAsync(pub, sub).ForAwait(); + + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => received.Count == 1); + Log(profiler); + + lock (received) + { + Assert.Single(received); + } + Assert.Equal(1, Volatile.Read(ref secondHandler)); + + sub.Unsubscribe(key); + count = sub.Publish(key, "ghi", CommandFlags.FireAndForget); + + await PingAsync(pub, sub).ForAwait(); + Log(profiler); + lock (received) + { + Assert.Single(received); + } + Assert.Equal(0, count); + } + + private async Task PingAsync(IServer pub, ISubscriber sub, int times = 1) + { + while (times-- > 0) + { + // both use async because we want to drain the completion managers, and the only + // way to prove that is to use TPL objects + var subTask = sub.PingAsync(); + var pubTask = pub.PingAsync(); + await Task.WhenAll(subTask, pubTask).ForAwait(); + + Log($"Sub PING time: {subTask.Result.TotalMilliseconds} ms"); + Log($"Pub PING time: {pubTask.Result.TotalMilliseconds} ms"); + } + } + + [Fact] + public async Task TestPatternPubSub() + { + await using var conn = Create(shared: false, log: Writer); + + var pub = GetAnyPrimary(conn); + var sub = conn.GetSubscriber(); + + HashSet received = []; + int secondHandler = 0; +#pragma warning disable CS0618 + sub.Subscribe("a*c", (channel, payload) => +#pragma warning restore CS0618 + { + lock (received) + { + if (channel == "abc") + { + received.Add(payload); + } + } + }); + +#pragma warning disable CS0618 + sub.Subscribe("a*c", (_, __) => Interlocked.Increment(ref secondHandler)); +#pragma warning restore CS0618 + lock (received) + { + Assert.Empty(received); + } + Assert.Equal(0, Volatile.Read(ref secondHandler)); + + await PingAsync(pub, sub).ForAwait(); + var count = sub.Publish(RedisChannel.Literal("abc"), "def"); + await PingAsync(pub, sub).ForAwait(); + + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => received.Count == 1); + lock (received) + { + Assert.Single(received); + } + + // Give reception a bit, the handler could be delayed under load + await UntilConditionAsync(TimeSpan.FromSeconds(2), () => Volatile.Read(ref secondHandler) == 1); + Assert.Equal(1, Volatile.Read(ref secondHandler)); + +#pragma warning disable CS0618 + sub.Unsubscribe("a*c"); + count = sub.Publish("abc", "ghi"); +#pragma warning restore CS0618 + + await PingAsync(pub, sub).ForAwait(); + + lock (received) + { + Assert.Single(received); + } + } + + [Fact] + public async Task TestPublishWithNoSubscribers() + { + await using var conn = Create(); + + var sub = conn.GetSubscriber(); +#pragma warning disable CS0618 + Assert.Equal(0, sub.Publish(Me() + "channel", "message")); +#pragma warning restore CS0618 + } + + [Fact] + public async Task TestMassivePublishWithWithoutFlush_Local() + { + Skip.UnlessLongRunning(); + await using var conn = Create(); + + var sub = conn.GetSubscriber(); + TestMassivePublish(sub, Me(), "local"); + } + + [Fact] + public async Task TestMassivePublishWithWithoutFlush_Remote() + { + Skip.UnlessLongRunning(); + await using var conn = Create(configuration: TestConfig.Current.RemoteServerAndPort); + + var sub = conn.GetSubscriber(); + TestMassivePublish(sub, Me(), "remote"); + } + + private void TestMassivePublish(ISubscriber sub, string channel, string caption) + { + const int loop = 10000; + + var tasks = new Task[loop]; + + var withFAF = Stopwatch.StartNew(); + for (int i = 0; i < loop; i++) + { +#pragma warning disable CS0618 + sub.Publish(channel, "bar", CommandFlags.FireAndForget); +#pragma warning restore CS0618 + } + withFAF.Stop(); + + var withAsync = Stopwatch.StartNew(); + for (int i = 0; i < loop; i++) + { +#pragma warning disable CS0618 + tasks[i] = sub.PublishAsync(channel, "bar"); +#pragma warning restore CS0618 + } + sub.WaitAll(tasks); + withAsync.Stop(); + + Log($"{caption}: {withFAF.ElapsedMilliseconds}ms (F+F) vs {withAsync.ElapsedMilliseconds}ms (async)"); + // We've made async so far, this test isn't really valid anymore + // So let's check they're at least within a few seconds. + Assert.True(withFAF.ElapsedMilliseconds < withAsync.ElapsedMilliseconds + 3000, caption); + } + + [Fact] + public async Task SubscribeAsyncEnumerable() + { + await using var conn = Create(syncTimeout: 20000, shared: false, log: Writer); + + var sub = conn.GetSubscriber(); + RedisChannel channel = RedisChannel.Literal(Me()); + + const int TO_SEND = 5; + var gotall = new TaskCompletionSource(); + + var source = await sub.SubscribeAsync(channel); + var op = Task.Run(async () => + { + int count = 0; + await foreach (var item in source) + { + count++; + if (count == TO_SEND) gotall.TrySetResult(count); + } + return count; + }); + + for (int i = 0; i < TO_SEND; i++) + { + await sub.PublishAsync(channel, i); + } + await gotall.Task.WithTimeout(5000); + + // check the enumerator exits cleanly + sub.Unsubscribe(channel); + var count = await op.WithTimeout(1000); + Assert.Equal(5, count); + } + + [Fact] + public async Task PubSubGetAllAnyOrder() + { + await using var conn = Create(syncTimeout: 20000, shared: false, log: Writer); + + var sub = conn.GetSubscriber(); + RedisChannel channel = RedisChannel.Literal(Me()); + const int count = 1000; + var syncLock = new object(); + + Assert.True(sub.IsConnected(), nameof(sub.IsConnected)); + var data = new HashSet(); + await sub.SubscribeAsync(channel, (_, val) => + { + bool pulse; + lock (data) + { + data.Add(int.Parse(Encoding.UTF8.GetString(val!))); + pulse = data.Count == count; + if ((data.Count % 100) == 99) Log(data.Count.ToString()); + } + if (pulse) + { + lock (syncLock) + { + Monitor.PulseAll(syncLock); + } + } + }).ForAwait(); + + lock (syncLock) + { + for (int i = 0; i < count; i++) + { + sub.Publish(channel, i.ToString(), CommandFlags.FireAndForget); + } + sub.Ping(); + if (!Monitor.Wait(syncLock, 20000)) + { + throw new TimeoutException("Items: " + data.Count); + } + for (int i = 0; i < count; i++) + { + Assert.Contains(i, data); + } + } + } + + [Fact] + public async Task PubSubGetAllCorrectOrder() + { + await using (var conn = Create(configuration: TestConfig.Current.RemoteServerAndPort, syncTimeout: 20000, log: Writer)) + { + var sub = conn.GetSubscriber(); + RedisChannel channel = RedisChannel.Literal(Me()); + const int count = 250; + var syncLock = new object(); + + var data = new List(count); + var subChannel = await sub.SubscribeAsync(channel).ForAwait(); + + await sub.PingAsync().ForAwait(); + + async Task RunLoop() + { + while (!subChannel.Completion.IsCompleted) + { + var work = await subChannel.ReadAsync().ForAwait(); + int i = int.Parse(Encoding.UTF8.GetString(work.Message!)); + lock (data) + { + data.Add(i); + if (data.Count == count) break; + if ((data.Count % 100) == 99) Log("Received: " + data.Count.ToString()); + } + } + lock (syncLock) + { + Log("PulseAll."); + Monitor.PulseAll(syncLock); + } + } + + lock (syncLock) + { + // Intentionally not awaited - running in parallel + _ = Task.Run(RunLoop); + for (int i = 0; i < count; i++) + { + sub.Publish(channel, i.ToString()); + if ((i % 100) == 99) Log("Published: " + i.ToString()); + } + Log("Send loop complete."); + if (!Monitor.Wait(syncLock, 20000)) + { + throw new TimeoutException("Items: " + data.Count); + } + Log("Unsubscribe."); + subChannel.Unsubscribe(); + Log("Sub Ping."); + sub.Ping(); + Log("Database Ping."); + conn.GetDatabase().Ping(); + for (int i = 0; i < count; i++) + { + Assert.Equal(i, data[i]); + } + } + + Log("Awaiting completion."); + await subChannel.Completion; + Log("Completion awaited."); + await Assert.ThrowsAsync(async () => await subChannel.ReadAsync().ForAwait()).ForAwait(); + Log("End of muxer."); + } + Log("End of test."); + } + + [Fact] + public async Task PubSubGetAllCorrectOrder_OnMessage_Sync() + { + await using (var conn = Create(configuration: TestConfig.Current.RemoteServerAndPort, syncTimeout: 20000, log: Writer)) + { + var sub = conn.GetSubscriber(); + RedisChannel channel = RedisChannel.Literal(Me()); + const int count = 1000; + var syncLock = new object(); + + var data = new List(count); + var subChannel = await sub.SubscribeAsync(channel).ForAwait(); + subChannel.OnMessage(msg => + { + int i = int.Parse(Encoding.UTF8.GetString(msg.Message!)); + bool pulse = false; + lock (data) + { + data.Add(i); + if (data.Count == count) pulse = true; + if ((data.Count % 100) == 99) Log("Received: " + data.Count.ToString()); + } + if (pulse) + { + lock (syncLock) + { + Monitor.PulseAll(syncLock); + } + } + }); + await sub.PingAsync().ForAwait(); + + lock (syncLock) + { + for (int i = 0; i < count; i++) + { + sub.Publish(channel, i.ToString(), CommandFlags.FireAndForget); + if ((i % 100) == 99) Log("Published: " + i.ToString()); + } + Log("Send loop complete."); + if (!Monitor.Wait(syncLock, 20000)) + { + throw new TimeoutException("Items: " + data.Count); + } + Log("Unsubscribe."); + subChannel.Unsubscribe(); + Log("Sub Ping."); + sub.Ping(); + Log("Database Ping."); + conn.GetDatabase().Ping(); + for (int i = 0; i < count; i++) + { + Assert.Equal(i, data[i]); + } + } + + Log("Awaiting completion."); + await subChannel.Completion; + Log("Completion awaited."); + Assert.True(subChannel.Completion.IsCompleted); + await Assert.ThrowsAsync(async () => await subChannel.ReadAsync().ForAwait()).ForAwait(); + Log("End of muxer."); + } + Log("End of test."); + } + + [Fact] + public async Task PubSubGetAllCorrectOrder_OnMessage_Async() + { + await using (var conn = Create(configuration: TestConfig.Current.RemoteServerAndPort, syncTimeout: 20000, log: Writer)) + { + var sub = conn.GetSubscriber(); + RedisChannel channel = RedisChannel.Literal(Me()); + const int count = 1000; + var syncLock = new object(); + + var data = new List(count); + var subChannel = await sub.SubscribeAsync(channel).ForAwait(); + subChannel.OnMessage(msg => + { + int i = int.Parse(Encoding.UTF8.GetString(msg.Message!)); + bool pulse = false; + lock (data) + { + data.Add(i); + if (data.Count == count) pulse = true; + if ((data.Count % 100) == 99) Log("Received: " + data.Count.ToString()); + } + if (pulse) + { + lock (syncLock) + { + Monitor.PulseAll(syncLock); + } + } + // Making sure we cope with null being returned here by a handler + return i % 2 == 0 ? null! : Task.CompletedTask; + }); + await sub.PingAsync().ForAwait(); + + // Give a delay between subscriptions and when we try to publish to be safe + await Task.Delay(1000).ForAwait(); + + lock (syncLock) + { + for (int i = 0; i < count; i++) + { + sub.Publish(channel, i.ToString(), CommandFlags.FireAndForget); + if ((i % 100) == 99) Log("Published: " + i.ToString()); + } + Log("Send loop complete."); + if (!Monitor.Wait(syncLock, 20000)) + { + throw new TimeoutException("Items: " + data.Count); + } + Log("Unsubscribe."); + subChannel.Unsubscribe(); + Log("Sub Ping."); + sub.Ping(); + Log("Database Ping."); + conn.GetDatabase().Ping(); + for (int i = 0; i < count; i++) + { + Assert.Equal(i, data[i]); + } + } + + Log("Awaiting completion."); + await subChannel.Completion; + Log("Completion awaited."); + Assert.True(subChannel.Completion.IsCompleted); + await Assert.ThrowsAsync(async () => await subChannel.ReadAsync().ForAwait()).ForAwait(); + Log("End of muxer."); + } + Log("End of test."); + } + + [Fact] + public async Task TestPublishWithSubscribers() + { + await using var connA = Create(shared: false, log: Writer); + await using var connB = Create(shared: false, log: Writer); + await using var connPub = Create(); + + var channel = Me(); + var listenA = connA.GetSubscriber(); + var listenB = connB.GetSubscriber(); +#pragma warning disable CS0618 + var t1 = listenA.SubscribeAsync(channel, (arg1, arg2) => { }); + var t2 = listenB.SubscribeAsync(channel, (arg1, arg2) => { }); +#pragma warning restore CS0618 + + await Task.WhenAll(t1, t2).ForAwait(); + + // subscribe is just a thread-race-mess + await listenA.PingAsync(); + await listenB.PingAsync(); + +#pragma warning disable CS0618 + var pub = connPub.GetSubscriber().PublishAsync(channel, "message"); +#pragma warning restore CS0618 + Assert.Equal(2, await pub); // delivery count + } + + [Fact] + public async Task TestMultipleSubscribersGetMessage() + { + await using var connA = Create(shared: false, log: Writer); + await using var connB = Create(shared: false, log: Writer); + await using var connPub = Create(); + + var channel = RedisChannel.Literal(Me()); + var listenA = connA.GetSubscriber(); + var listenB = connB.GetSubscriber(); + await connPub.GetDatabase().PingAsync(); + var pub = connPub.GetSubscriber(); + int gotA = 0, gotB = 0; + var tA = listenA.SubscribeAsync(channel, (_, msg) => { if (msg == "message") Interlocked.Increment(ref gotA); }); + var tB = listenB.SubscribeAsync(channel, (_, msg) => { if (msg == "message") Interlocked.Increment(ref gotB); }); + await Task.WhenAll(tA, tB).ForAwait(); + Assert.Equal(2, pub.Publish(channel, "message")); + await AllowReasonableTimeToPublishAndProcess().ForAwait(); + Assert.Equal(1, Interlocked.CompareExchange(ref gotA, 0, 0)); + Assert.Equal(1, Interlocked.CompareExchange(ref gotB, 0, 0)); + + // and unsubscribe... + tA = listenA.UnsubscribeAsync(channel); + await tA; + Assert.Equal(1, pub.Publish(channel, "message")); + await AllowReasonableTimeToPublishAndProcess().ForAwait(); + Assert.Equal(1, Interlocked.CompareExchange(ref gotA, 0, 0)); + Assert.Equal(2, Interlocked.CompareExchange(ref gotB, 0, 0)); + } + + [Fact] + public async Task Issue38() + { + await using var conn = Create(log: Writer); + + var sub = conn.GetSubscriber(); + int count = 0; + var prefix = Me(); + void Handler(RedisChannel unused, RedisValue unused2) => Interlocked.Increment(ref count); +#pragma warning disable CS0618 + var a0 = sub.SubscribeAsync(prefix + "foo", Handler); + var a1 = sub.SubscribeAsync(prefix + "bar", Handler); + var b0 = sub.SubscribeAsync(prefix + "f*o", Handler); + var b1 = sub.SubscribeAsync(prefix + "b*r", Handler); +#pragma warning restore CS0618 + await Task.WhenAll(a0, a1, b0, b1).ForAwait(); + +#pragma warning disable CS0618 + var c = sub.PublishAsync(prefix + "foo", "foo"); + var d = sub.PublishAsync(prefix + "f@o", "f@o"); + var e = sub.PublishAsync(prefix + "bar", "bar"); + var f = sub.PublishAsync(prefix + "b@r", "b@r"); +#pragma warning restore CS0618 + await Task.WhenAll(c, d, e, f).ForAwait(); + + long total = c.Result + d.Result + e.Result + f.Result; + + await AllowReasonableTimeToPublishAndProcess().ForAwait(); + + Assert.Equal(6, total); // sent + Assert.Equal(6, Interlocked.CompareExchange(ref count, 0, 0)); // received + } + + internal static Task AllowReasonableTimeToPublishAndProcess() => Task.Delay(500); + + [Fact] + public async Task TestPartialSubscriberGetMessage() + { + await using var connA = Create(); + await using var connB = Create(); + await using var connPub = Create(); + + int gotA = 0, gotB = 0; + var listenA = connA.GetSubscriber(); + var listenB = connB.GetSubscriber(); + var pub = connPub.GetSubscriber(); + var prefix = Me(); +#pragma warning disable CS0618 + var tA = listenA.SubscribeAsync(prefix + "channel", (s, msg) => { if (s == prefix + "channel" && msg == "message") Interlocked.Increment(ref gotA); }); + var tB = listenB.SubscribeAsync(prefix + "chann*", (s, msg) => { if (s == prefix + "channel" && msg == "message") Interlocked.Increment(ref gotB); }); + await Task.WhenAll(tA, tB).ForAwait(); + Assert.Equal(2, pub.Publish(prefix + "channel", "message")); +#pragma warning restore CS0618 + await AllowReasonableTimeToPublishAndProcess().ForAwait(); + Assert.Equal(1, Interlocked.CompareExchange(ref gotA, 0, 0)); + Assert.Equal(1, Interlocked.CompareExchange(ref gotB, 0, 0)); + + // and unsubscibe... +#pragma warning disable CS0618 + tB = listenB.UnsubscribeAsync(prefix + "chann*", null); + await tB; + Assert.Equal(1, pub.Publish(prefix + "channel", "message")); +#pragma warning restore CS0618 + await AllowReasonableTimeToPublishAndProcess().ForAwait(); + Assert.Equal(2, Interlocked.CompareExchange(ref gotA, 0, 0)); + Assert.Equal(1, Interlocked.CompareExchange(ref gotB, 0, 0)); + } + + [Fact] + public async Task TestSubscribeUnsubscribeAndSubscribeAgain() + { + await using var connPub = Create(); + await using var connSub = Create(); + + var prefix = Me(); + var pub = connPub.GetSubscriber(); + var sub = connSub.GetSubscriber(); + int x = 0, y = 0; +#pragma warning disable CS0618 + var t1 = sub.SubscribeAsync(prefix + "abc", (arg1, arg2) => Interlocked.Increment(ref x)); + var t2 = sub.SubscribeAsync(prefix + "ab*", (arg1, arg2) => Interlocked.Increment(ref y)); + await Task.WhenAll(t1, t2).ForAwait(); + pub.Publish(prefix + "abc", ""); + await AllowReasonableTimeToPublishAndProcess().ForAwait(); + Assert.Equal(1, Volatile.Read(ref x)); + Assert.Equal(1, Volatile.Read(ref y)); + t1 = sub.UnsubscribeAsync(prefix + "abc", null); + t2 = sub.UnsubscribeAsync(prefix + "ab*", null); + await Task.WhenAll(t1, t2).ForAwait(); + pub.Publish(prefix + "abc", ""); + Assert.Equal(1, Volatile.Read(ref x)); + Assert.Equal(1, Volatile.Read(ref y)); + t1 = sub.SubscribeAsync(prefix + "abc", (arg1, arg2) => Interlocked.Increment(ref x)); + t2 = sub.SubscribeAsync(prefix + "ab*", (arg1, arg2) => Interlocked.Increment(ref y)); + await Task.WhenAll(t1, t2).ForAwait(); + pub.Publish(prefix + "abc", ""); +#pragma warning restore CS0618 + await AllowReasonableTimeToPublishAndProcess().ForAwait(); + Assert.Equal(2, Volatile.Read(ref x)); + Assert.Equal(2, Volatile.Read(ref y)); + } + + [Fact] + public async Task AzureRedisEventsAutomaticSubscribe() + { + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer); + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCachePassword), TestConfig.Current.AzureCachePassword); + + bool didUpdate = false; + var options = new ConfigurationOptions() + { + EndPoints = { TestConfig.Current.AzureCacheServer }, + Password = TestConfig.Current.AzureCachePassword, + Ssl = true, + }; + + using (var connection = await ConnectionMultiplexer.ConnectAsync(options)) + { + connection.ServerMaintenanceEvent += (_, e) => + { + if (e is AzureMaintenanceEvent) + { + didUpdate = true; + } + }; + + var pubSub = connection.GetSubscriber(); + await pubSub.PublishAsync(RedisChannel.Literal("AzureRedisEvents"), "HI"); + await Task.Delay(100); + + Assert.True(didUpdate); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/README.md b/tests/StackExchange.Redis.Tests/README.md new file mode 100644 index 000000000..cdd5be175 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/README.md @@ -0,0 +1,3 @@ +## Test Suite + +To test `StackExchange.Redis`, [see the documentation](https://stackexchange.github.io/StackExchange.Redis/Testing). \ No newline at end of file diff --git a/tests/StackExchange.Redis.Tests/RawResultTests.cs b/tests/StackExchange.Redis.Tests/RawResultTests.cs new file mode 100644 index 000000000..9cf578ee1 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/RawResultTests.cs @@ -0,0 +1,69 @@ +using System.Buffers; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class RawResultTests +{ + [Fact] + public void TypeLoads() + { + var type = typeof(RawResult); + Assert.Equal(nameof(RawResult), type.Name); + } + + [Theory] + [InlineData(ResultType.BulkString)] + [InlineData(ResultType.Null)] + public void NullWorks(ResultType type) + { + var result = new RawResult(type, ReadOnlySequence.Empty, RawResult.ResultFlags.None); + Assert.Equal(type, result.Resp3Type); + Assert.True(result.HasValue); + Assert.True(result.IsNull); + + var value = result.AsRedisValue(); + + Assert.True(value.IsNull); + string? s = value; + Assert.Null(s); + + byte[]? arr = (byte[]?)value; + Assert.Null(arr); + } + + [Fact] + public void DefaultWorks() + { + var result = RawResult.Nil; + Assert.Equal(ResultType.None, result.Resp3Type); + Assert.False(result.HasValue); + Assert.True(result.IsNull); + + var value = result.AsRedisValue(); + + Assert.True(value.IsNull); + var s = (string?)value; + Assert.Null(s); + + var arr = (byte[]?)value; + Assert.Null(arr); + } + + [Fact] + public void NilWorks() + { + var result = RawResult.Nil; + Assert.Equal(ResultType.None, result.Resp3Type); + Assert.True(result.IsNull); + + var value = result.AsRedisValue(); + + Assert.True(value.IsNull); + var s = (string?)value; + Assert.Null(s); + + var arr = (byte[]?)value; + Assert.Null(arr); + } +} diff --git a/tests/StackExchange.Redis.Tests/RealWorldTests.cs b/tests/StackExchange.Redis.Tests/RealWorldTests.cs new file mode 100644 index 000000000..ba9605b4f --- /dev/null +++ b/tests/StackExchange.Redis.Tests/RealWorldTests.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class RealWorldTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task WhyDoesThisNotWork() + { + Log("first:"); + var config = ConfigurationOptions.Parse("localhost:6379,localhost:6380,name=Core (Q&A),tiebreaker=:RedisPrimary,abortConnect=False"); + Assert.Equal(2, config.EndPoints.Count); + Log("Endpoint 0: {0} (AddressFamily: {1})", config.EndPoints[0], config.EndPoints[0].AddressFamily); + Log("Endpoint 1: {0} (AddressFamily: {1})", config.EndPoints[1], config.EndPoints[1].AddressFamily); + + await using (var conn = ConnectionMultiplexer.Connect("localhost:6379,localhost:6380,name=Core (Q&A),tiebreaker=:RedisPrimary,abortConnect=False", Writer)) + { + Log(""); + Log("pausing..."); + await Task.Delay(200).ForAwait(); + Log("second:"); + + bool result = conn.Configure(Writer); + Log("Returned: {0}", result); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/RedisFeaturesTests.cs b/tests/StackExchange.Redis.Tests/RedisFeaturesTests.cs new file mode 100644 index 000000000..2cabb90b4 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/RedisFeaturesTests.cs @@ -0,0 +1,29 @@ +using System; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class RedisFeaturesTests +{ + [Fact] + public void ExecAbort() // a random one because it is fun + { + var features = new RedisFeatures(new Version(2, 9)); + var s = features.ToString(); + Assert.True(features.ExecAbort); + Assert.StartsWith("Features in 2.9" + Environment.NewLine, s); + Assert.Contains("ExecAbort: True" + Environment.NewLine, s); + + features = new RedisFeatures(new Version(2, 9, 5)); + s = features.ToString(); + Assert.False(features.ExecAbort); + Assert.StartsWith("Features in 2.9.5" + Environment.NewLine, s); + Assert.Contains("ExecAbort: False" + Environment.NewLine, s); + + features = new RedisFeatures(new Version(3, 0)); + s = features.ToString(); + Assert.True(features.ExecAbort); + Assert.StartsWith("Features in 3.0" + Environment.NewLine, s); + Assert.Contains("ExecAbort: True" + Environment.NewLine, s); + } +} diff --git a/tests/StackExchange.Redis.Tests/RedisResultTests.cs b/tests/StackExchange.Redis.Tests/RedisResultTests.cs new file mode 100644 index 000000000..e63e00dc8 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/RedisResultTests.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using Xunit; + +namespace StackExchange.Redis.Tests; + +/// +/// Tests for . +/// +public sealed class RedisResultTests +{ + /// + /// Tests the basic functionality of . + /// + [Fact] + public void ToDictionaryWorks() + { + var redisArrayResult = RedisResult.Create( + ["one", 1, "two", 2, "three", 3, "four", 4]); + + var dict = redisArrayResult.ToDictionary(); + + Assert.Equal(4, dict.Count); + Assert.Equal(1, (RedisValue)dict["one"]); + Assert.Equal(2, (RedisValue)dict["two"]); + Assert.Equal(3, (RedisValue)dict["three"]); + Assert.Equal(4, (RedisValue)dict["four"]); + } + + /// + /// Tests the basic functionality of + /// when the results contain a nested results array, which is common for lua script results. + /// + [Fact] + public void ToDictionaryWorksWhenNested() + { + var redisArrayResult = RedisResult.Create( + [ + RedisResult.Create((RedisValue)"one"), + RedisResult.Create(["two", 2, "three", 3]), + + RedisResult.Create((RedisValue)"four"), + RedisResult.Create(["five", 5, "six", 6]), + ]); + + var dict = redisArrayResult.ToDictionary(); + var nestedDict = dict["one"].ToDictionary(); + + Assert.Equal(2, dict.Count); + Assert.Equal(2, nestedDict.Count); + Assert.Equal(2, (RedisValue)nestedDict["two"]); + Assert.Equal(3, (RedisValue)nestedDict["three"]); + } + + /// + /// Tests that fails when a duplicate key is encountered. + /// This also tests that the default comparator is case-insensitive. + /// + [Fact] + public void ToDictionaryFailsWithDuplicateKeys() + { + var redisArrayResult = RedisResult.Create( + ["banana", 1, "BANANA", 2, "orange", 3, "apple", 4]); + + Assert.Throws(() => redisArrayResult.ToDictionary(/* Use default comparer, causes collision of banana */)); + } + + /// + /// Tests that correctly uses the provided comparator. + /// + [Fact] + public void ToDictionaryWorksWithCustomComparator() + { + var redisArrayResult = RedisResult.Create( + ["banana", 1, "BANANA", 2, "orange", 3, "apple", 4]); + + var dict = redisArrayResult.ToDictionary(StringComparer.Ordinal); + + Assert.Equal(4, dict.Count); + Assert.Equal(1, (RedisValue)dict["banana"]); + Assert.Equal(2, (RedisValue)dict["BANANA"]); + } + + /// + /// Tests that fails when the redis results array contains an odd number + /// of elements. In other words, it's not actually a Key,Value,Key,Value... etc. array. + /// + [Fact] + public void ToDictionaryFailsOnMishapenResults() + { + var redisArrayResult = RedisResult.Create( + ["one", 1, "two", 2, "three", 3, "four" /* missing 4 */]); + + Assert.Throws(() => redisArrayResult.ToDictionary(StringComparer.Ordinal)); + } + + [Fact] + public void SingleResultConvertibleViaTo() + { + var value = RedisResult.Create(123); + Assert.StrictEqual((int)123, Convert.ToInt32(value)); + Assert.StrictEqual((uint)123U, Convert.ToUInt32(value)); + Assert.StrictEqual(123L, Convert.ToInt64(value)); + Assert.StrictEqual(123UL, Convert.ToUInt64(value)); + Assert.StrictEqual((byte)123, Convert.ToByte(value)); + Assert.StrictEqual((sbyte)123, Convert.ToSByte(value)); + Assert.StrictEqual((short)123, Convert.ToInt16(value)); + Assert.StrictEqual((ushort)123, Convert.ToUInt16(value)); + Assert.Equal("123", Convert.ToString(value)); + Assert.StrictEqual(123M, Convert.ToDecimal(value)); + Assert.StrictEqual((char)123, Convert.ToChar(value)); + Assert.StrictEqual(123f, Convert.ToSingle(value)); + Assert.StrictEqual(123d, Convert.ToDouble(value)); + } + + [Fact] + public void SingleResultConvertibleDirectViaChangeType_Type() + { + var value = RedisResult.Create(123); + Assert.StrictEqual((int)123, Convert.ChangeType(value, typeof(int))); + Assert.StrictEqual((uint)123U, Convert.ChangeType(value, typeof(uint))); + Assert.StrictEqual(123L, Convert.ChangeType(value, typeof(long))); + Assert.StrictEqual(123UL, Convert.ChangeType(value, typeof(ulong))); + Assert.StrictEqual((byte)123, Convert.ChangeType(value, typeof(byte))); + Assert.StrictEqual((sbyte)123, Convert.ChangeType(value, typeof(sbyte))); + Assert.StrictEqual((short)123, Convert.ChangeType(value, typeof(short))); + Assert.StrictEqual((ushort)123, Convert.ChangeType(value, typeof(ushort))); + Assert.Equal("123", Convert.ChangeType(value, typeof(string))); + Assert.StrictEqual(123M, Convert.ChangeType(value, typeof(decimal))); + Assert.StrictEqual((char)123, Convert.ChangeType(value, typeof(char))); + Assert.StrictEqual(123f, Convert.ChangeType(value, typeof(float))); + Assert.StrictEqual(123d, Convert.ChangeType(value, typeof(double))); + } + + [Fact] + public void SingleResultConvertibleDirectViaChangeType_TypeCode() + { + var value = RedisResult.Create(123); + Assert.StrictEqual((int)123, Convert.ChangeType(value, TypeCode.Int32)); + Assert.StrictEqual((uint)123U, Convert.ChangeType(value, TypeCode.UInt32)); + Assert.StrictEqual(123L, Convert.ChangeType(value, TypeCode.Int64)); + Assert.StrictEqual(123UL, Convert.ChangeType(value, TypeCode.UInt64)); + Assert.StrictEqual((byte)123, Convert.ChangeType(value, TypeCode.Byte)); + Assert.StrictEqual((sbyte)123, Convert.ChangeType(value, TypeCode.SByte)); + Assert.StrictEqual((short)123, Convert.ChangeType(value, TypeCode.Int16)); + Assert.StrictEqual((ushort)123, Convert.ChangeType(value, TypeCode.UInt16)); + Assert.Equal("123", Convert.ChangeType(value, TypeCode.String)); + Assert.StrictEqual(123M, Convert.ChangeType(value, TypeCode.Decimal)); + Assert.StrictEqual((char)123, Convert.ChangeType(value, TypeCode.Char)); + Assert.StrictEqual(123f, Convert.ChangeType(value, TypeCode.Single)); + Assert.StrictEqual(123d, Convert.ChangeType(value, TypeCode.Double)); + } + + [Theory] + [InlineData(ResultType.Double)] + [InlineData(ResultType.BulkString)] + [InlineData(ResultType.SimpleString)] + public void RedisResultParseNaN(ResultType resultType) + { + // https://github.com/redis/NRedisStack/issues/439 + var value = RedisResult.Create("NaN", resultType); + Assert.True(double.IsNaN(value.AsDouble())); + } + + [Theory] + [InlineData(ResultType.Double)] + [InlineData(ResultType.BulkString)] + [InlineData(ResultType.SimpleString)] + public void RedisResultParseInf(ResultType resultType) + { + // https://github.com/redis/NRedisStack/issues/439 + var value = RedisResult.Create("inf", resultType); + Assert.True(double.IsPositiveInfinity(value.AsDouble())); + } + + [Theory] + [InlineData(ResultType.Double)] + [InlineData(ResultType.BulkString)] + [InlineData(ResultType.SimpleString)] + public void RedisResultParsePlusInf(ResultType resultType) + { + // https://github.com/redis/NRedisStack/issues/439 + var value = RedisResult.Create("+inf", resultType); + Assert.True(double.IsPositiveInfinity(value.AsDouble())); + } + + [Theory] + [InlineData(ResultType.Double)] + [InlineData(ResultType.BulkString)] + [InlineData(ResultType.SimpleString)] + public void RedisResultParseMinusInf(ResultType resultType) + { + // https://github.com/redis/NRedisStack/issues/439 + var value = RedisResult.Create("-inf", resultType); + Assert.True(double.IsNegativeInfinity(value.AsDouble())); + } +} diff --git a/tests/StackExchange.Redis.Tests/RedisTestConfig.json b/tests/StackExchange.Redis.Tests/RedisTestConfig.json new file mode 100644 index 000000000..c652a4583 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/RedisTestConfig.json @@ -0,0 +1,6 @@ +{ + //"LogToConsole": false, + //"PrimaryServer": "[::1]", + //"ReplicaServer": "[::1]", + //"SecureServer": "[::1]" +} \ No newline at end of file diff --git a/tests/StackExchange.Redis.Tests/RedisValueEquivalencyTests.cs b/tests/StackExchange.Redis.Tests/RedisValueEquivalencyTests.cs new file mode 100644 index 000000000..7f6ad1561 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/RedisValueEquivalencyTests.cs @@ -0,0 +1,449 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class RedisValueEquivalency +{ + // internal storage types: null, integer, double, string, raw + // public perceived types: int, long, double, bool, memory / byte[] + [Fact] + public void Int32_Matrix() + { + static void Check(RedisValue known, RedisValue test) + { + KeyAndValueTests.CheckSame(known, test); + if (known.IsNull) + { + Assert.True(test.IsNull); + Assert.False(((int?)test).HasValue); + } + else + { + Assert.False(test.IsNull); + Assert.Equal((int)known, ((int?)test)!.Value); + Assert.Equal((int)known, (int)test); + } + Assert.Equal((int)known, (int)test); + } + Check(42, 42); + Check(42, 42.0); + Check(42, "42"); + Check(42, "42.0"); + Check(42, Bytes("42")); + Check(42, Bytes("42.0")); + CheckString(42, "42"); + + Check(-42, -42); + Check(-42, -42.0); + Check(-42, "-42"); + Check(-42, "-42.0"); + Check(-42, Bytes("-42")); + Check(-42, Bytes("-42.0")); + CheckString(-42, "-42"); + + Check(1, true); + Check(0, false); + } + + [Fact] + public void Int64_Matrix() + { + static void Check(RedisValue known, RedisValue test) + { + KeyAndValueTests.CheckSame(known, test); + if (known.IsNull) + { + Assert.True(test.IsNull); + Assert.False(((long?)test).HasValue); + } + else + { + Assert.False(test.IsNull); + Assert.Equal((long)known, ((long?)test!).Value); + Assert.Equal((long)known, (long)test); + } + Assert.Equal((long)known, (long)test); + } + Check(1099511627848, 1099511627848); + Check(1099511627848, 1099511627848.0); + Check(1099511627848, "1099511627848"); + Check(1099511627848, "1099511627848.0"); + Check(1099511627848, Bytes("1099511627848")); + Check(1099511627848, Bytes("1099511627848.0")); + CheckString(1099511627848, "1099511627848"); + + Check(-1099511627848, -1099511627848); + Check(-1099511627848, -1099511627848); + Check(-1099511627848, "-1099511627848"); + Check(-1099511627848, "-1099511627848.0"); + Check(-1099511627848, Bytes("-1099511627848")); + Check(-1099511627848, Bytes("-1099511627848.0")); + CheckString(-1099511627848, "-1099511627848"); + + Check(1L, true); + Check(0L, false); + } + + [Fact] + public void Double_Matrix() + { + static void Check(RedisValue known, RedisValue test) + { + KeyAndValueTests.CheckSame(known, test); + if (known.IsNull) + { + Assert.True(test.IsNull); + Assert.False(((double?)test).HasValue); + } + else + { + Assert.False(test.IsNull); + Assert.Equal((double)known, ((double?)test)!.Value); + Assert.Equal((double)known, (double)test); + } + Assert.Equal((double)known, (double)test); + } + Check(1099511627848.0, 1099511627848); + Check(1099511627848.0, 1099511627848.0); + Check(1099511627848.0, "1099511627848"); + Check(1099511627848.0, "1099511627848.0"); + Check(1099511627848.0, Bytes("1099511627848")); + Check(1099511627848.0, Bytes("1099511627848.0")); + CheckString(1099511627848.0, "1099511627848"); + + Check(-1099511627848.0, -1099511627848); + Check(-1099511627848.0, -1099511627848); + Check(-1099511627848.0, "-1099511627848"); + Check(-1099511627848.0, "-1099511627848.0"); + Check(-1099511627848.0, Bytes("-1099511627848")); + Check(-1099511627848.0, Bytes("-1099511627848.0")); + CheckString(-1099511627848.0, "-1099511627848"); + + Check(1.0, true); + Check(0.0, false); + + Check(1099511627848.6001, 1099511627848.6001); + Check(1099511627848.6001, "1099511627848.6001"); + Check(1099511627848.6001, Bytes("1099511627848.6001")); + CheckString(1099511627848.6001, "1099511627848.6001"); + + Check(-1099511627848.6001, -1099511627848.6001); + Check(-1099511627848.6001, "-1099511627848.6001"); + Check(-1099511627848.6001, Bytes("-1099511627848.6001")); + CheckString(-1099511627848.6001, "-1099511627848.6001"); + + Check(double.NegativeInfinity, double.NegativeInfinity); + CheckString(double.NegativeInfinity, "-inf"); + + Check(double.PositiveInfinity, double.PositiveInfinity); + CheckString(double.PositiveInfinity, "+inf"); + + Check(double.NaN, double.NaN); + CheckString(double.NaN, "NaN"); + } + + [Theory] + [InlineData("na")] + [InlineData("nan")] + [InlineData("nans")] + [InlineData("in")] + [InlineData("inf")] + [InlineData("info")] + public void SpecialCaseEqualityRules_String(string value) + { + RedisValue x = value, y = value; + Assert.Equal(x, y); + + Assert.True(x.Equals(y)); + Assert.True(y.Equals(x)); + Assert.True(x == y); + Assert.True(y == x); + Assert.False(x != y); + Assert.False(y != x); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } + + [Theory] + [InlineData("na")] + [InlineData("nan")] + [InlineData("nans")] + [InlineData("in")] + [InlineData("inf")] + [InlineData("info")] + public void SpecialCaseEqualityRules_Bytes(string value) + { + byte[] bytes0 = Encoding.UTF8.GetBytes(value), + bytes1 = Encoding.UTF8.GetBytes(value); + Assert.NotSame(bytes0, bytes1); + RedisValue x = bytes0, y = bytes1; + + Assert.True(x.Equals(y)); + Assert.True(y.Equals(x)); + Assert.True(x == y); + Assert.True(y == x); + Assert.False(x != y); + Assert.False(y != x); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } + + [Theory] + [InlineData("na")] + [InlineData("nan")] + [InlineData("nans")] + [InlineData("in")] + [InlineData("inf")] + [InlineData("info")] + public void SpecialCaseEqualityRules_Hybrid(string value) + { + byte[] bytes = Encoding.UTF8.GetBytes(value); + RedisValue x = bytes, y = value; + + Assert.True(x.Equals(y)); + Assert.True(y.Equals(x)); + Assert.True(x == y); + Assert.True(y == x); + Assert.False(x != y); + Assert.False(y != x); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } + + [Theory] + [InlineData("na", "NA")] + [InlineData("nan", "NAN")] + [InlineData("nans", "NANS")] + [InlineData("in", "IN")] + [InlineData("inf", "INF")] + [InlineData("info", "INFO")] + public void SpecialCaseNonEqualityRules_String(string s, string t) + { + RedisValue x = s, y = t; + Assert.False(x.Equals(y)); + Assert.False(y.Equals(x)); + Assert.False(x == y); + Assert.False(y == x); + Assert.True(x != y); + Assert.True(y != x); + } + + [Theory] + [InlineData("na", "NA")] + [InlineData("nan", "NAN")] + [InlineData("nans", "NANS")] + [InlineData("in", "IN")] + [InlineData("inf", "INF")] + [InlineData("info", "INFO")] + public void SpecialCaseNonEqualityRules_Bytes(string s, string t) + { + RedisValue x = Encoding.UTF8.GetBytes(s), y = Encoding.UTF8.GetBytes(t); + Assert.False(x.Equals(y)); + Assert.False(y.Equals(x)); + Assert.False(x == y); + Assert.False(y == x); + Assert.True(x != y); + Assert.True(y != x); + } + + [Theory] + [InlineData("na", "NA")] + [InlineData("nan", "NAN")] + [InlineData("nans", "NANS")] + [InlineData("in", "IN")] + [InlineData("inf", "INF")] + [InlineData("info", "INFO")] + public void SpecialCaseNonEqualityRules_Hybrid(string s, string t) + { + RedisValue x = s, y = Encoding.UTF8.GetBytes(t); + Assert.False(x.Equals(y)); + Assert.False(y.Equals(x)); + Assert.False(x == y); + Assert.False(y == x); + Assert.True(x != y); + Assert.True(y != x); + } + + private static void CheckString(RedisValue value, string expected) + { + var s = value.ToString(); + Assert.True(s == expected, $"'{s}' vs '{expected}'"); + } + + private static byte[]? Bytes(string? s) => s == null ? null : Encoding.UTF8.GetBytes(s); + + private static string LineNumber([CallerLineNumber] int lineNumber = 0) => lineNumber.ToString(); + + [Fact] + public void RedisValueStartsWith() + { + // test strings + RedisValue x = "abc"; + Assert.True(x.StartsWith("a"), LineNumber()); + Assert.True(x.StartsWith("ab"), LineNumber()); + Assert.True(x.StartsWith("abc"), LineNumber()); + Assert.False(x.StartsWith("abd"), LineNumber()); + Assert.False(x.StartsWith("abcd"), LineNumber()); + Assert.False(x.StartsWith(123), LineNumber()); + Assert.False(x.StartsWith(false), LineNumber()); + + // test binary + x = Encoding.ASCII.GetBytes("abc"); + Assert.True(x.StartsWith("a"), LineNumber()); + Assert.True(x.StartsWith("ab"), LineNumber()); + Assert.True(x.StartsWith("abc"), LineNumber()); + Assert.False(x.StartsWith("abd"), LineNumber()); + Assert.False(x.StartsWith("abcd"), LineNumber()); + Assert.False(x.StartsWith(123), LineNumber()); + Assert.False(x.StartsWith(false), LineNumber()); + + Assert.True(x.StartsWith(Encoding.ASCII.GetBytes("a")), LineNumber()); + Assert.True(x.StartsWith(Encoding.ASCII.GetBytes("ab")), LineNumber()); + Assert.True(x.StartsWith(Encoding.ASCII.GetBytes("abc")), LineNumber()); + Assert.False(x.StartsWith(Encoding.ASCII.GetBytes("abd")), LineNumber()); + Assert.False(x.StartsWith(Encoding.ASCII.GetBytes("abcd")), LineNumber()); + + x = 10; // integers are effectively strings in this context + Assert.True(x.StartsWith(1), LineNumber()); + Assert.True(x.StartsWith(10), LineNumber()); + Assert.False(x.StartsWith(100), LineNumber()); + } + + [Fact] + public void TryParseInt64() + { + Assert.True(((RedisValue)123).TryParse(out long l)); + Assert.Equal(123, l); + + Assert.True(((RedisValue)123.0).TryParse(out l)); + Assert.Equal(123, l); + + Assert.True(((RedisValue)(int.MaxValue + 123L)).TryParse(out l)); + Assert.Equal(int.MaxValue + 123L, l); + + Assert.True(((RedisValue)"123").TryParse(out l)); + Assert.Equal(123, l); + + Assert.True(((RedisValue)(-123)).TryParse(out l)); + Assert.Equal(-123, l); + + Assert.True(default(RedisValue).TryParse(out l)); + Assert.Equal(0, l); + + Assert.True(((RedisValue)123.0).TryParse(out l)); + Assert.Equal(123, l); + + Assert.False(((RedisValue)"abc").TryParse(out long _)); + Assert.False(((RedisValue)"123.1").TryParse(out long _)); + Assert.False(((RedisValue)123.1).TryParse(out long _)); + } + + [Fact] + public void TryParseInt32() + { + Assert.True(((RedisValue)123).TryParse(out int i)); + Assert.Equal(123, i); + + Assert.True(((RedisValue)123.0).TryParse(out i)); + Assert.Equal(123, i); + + Assert.False(((RedisValue)(int.MaxValue + 123L)).TryParse(out int _)); + + Assert.True(((RedisValue)"123").TryParse(out i)); + Assert.Equal(123, i); + + Assert.True(((RedisValue)(-123)).TryParse(out i)); + Assert.Equal(-123, i); + + Assert.True(default(RedisValue).TryParse(out i)); + Assert.Equal(0, i); + + Assert.True(((RedisValue)123.0).TryParse(out i)); + Assert.Equal(123, i); + + Assert.False(((RedisValue)"abc").TryParse(out int _)); + Assert.False(((RedisValue)"123.1").TryParse(out int _)); + Assert.False(((RedisValue)123.1).TryParse(out int _)); + } + + [Fact] + public void TryParseDouble() + { + Assert.True(((RedisValue)123).TryParse(out double d)); + Assert.Equal(123, d); + + Assert.True(((RedisValue)123.0).TryParse(out d)); + Assert.Equal(123.0, d); + + Assert.True(((RedisValue)123.1).TryParse(out d)); + Assert.Equal(123.1, d); + + Assert.True(((RedisValue)(int.MaxValue + 123L)).TryParse(out d)); + Assert.Equal(int.MaxValue + 123L, d); + + Assert.True(((RedisValue)"123").TryParse(out d)); + Assert.Equal(123.0, d); + + Assert.True(((RedisValue)(-123)).TryParse(out d)); + Assert.Equal(-123.0, d); + + Assert.True(default(RedisValue).TryParse(out d)); + Assert.Equal(0.0, d); + + Assert.True(((RedisValue)123.0).TryParse(out d)); + Assert.Equal(123.0, d); + + Assert.True(((RedisValue)"123.1").TryParse(out d)); + Assert.Equal(123.1, d); + + Assert.False(((RedisValue)"abc").TryParse(out double _)); + } + + [Fact] + public void RedisValueLengthString() + { + RedisValue value = "abc"; + Assert.Equal(RedisValue.StorageType.String, value.Type); + Assert.Equal(3, value.Length()); + } + + [Fact] + public void RedisValueLengthDouble() + { + RedisValue value = Math.PI; + Assert.Equal(RedisValue.StorageType.Double, value.Type); + Assert.Equal(18, value.Length()); + } + + [Fact] + public void RedisValueLengthInt64() + { + RedisValue value = 123; + Assert.Equal(RedisValue.StorageType.Int64, value.Type); + Assert.Equal(3, value.Length()); + } + + [Fact] + public void RedisValueLengthUInt64() + { + RedisValue value = ulong.MaxValue - 5; + Assert.Equal(RedisValue.StorageType.UInt64, value.Type); + Assert.Equal(20, value.Length()); + } + + [Fact] + public void RedisValueLengthRaw() + { + RedisValue value = new byte[] { 0, 1, 2 }; + Assert.Equal(RedisValue.StorageType.Raw, value.Type); + Assert.Equal(3, value.Length()); + } + + [Fact] + public void RedisValueLengthNull() + { + RedisValue value = RedisValue.Null; + Assert.Equal(RedisValue.StorageType.Null, value.Type); + Assert.Equal(0, value.Length()); + } +} diff --git a/tests/StackExchange.Redis.Tests/RespProtocolTests.cs b/tests/StackExchange.Redis.Tests/RespProtocolTests.cs new file mode 100644 index 000000000..855ec96d1 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/RespProtocolTests.cs @@ -0,0 +1,435 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public sealed class RespProtocolTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + [RunPerProtocol] + public async Task ConnectWithTiming() + { + await using var conn = Create(shared: false, log: Writer); + await conn.GetDatabase().PingAsync(); + } + + [Theory] + // specify nothing + [InlineData("someserver", false)] + // specify *just* the protocol; sure, we'll believe you + [InlineData("someserver,protocol=resp3", true)] + [InlineData("someserver,protocol=resp3,$HELLO=", false)] + [InlineData("someserver,protocol=resp3,$HELLO=BONJOUR", true)] + [InlineData("someserver,protocol=3", true, "resp3")] + [InlineData("someserver,protocol=3,$HELLO=", false, "resp3")] + [InlineData("someserver,protocol=3,$HELLO=BONJOUR", true, "resp3")] + [InlineData("someserver,protocol=2", false, "resp2")] + [InlineData("someserver,protocol=2,$HELLO=", false, "resp2")] + [InlineData("someserver,protocol=2,$HELLO=BONJOUR", false, "resp2")] + // specify a pre-6 version - only used if protocol specified + [InlineData("someserver,version=5.9", false)] + [InlineData("someserver,version=5.9,$HELLO=", false)] + [InlineData("someserver,version=5.9,$HELLO=BONJOUR", false)] + [InlineData("someserver,version=5.9,protocol=resp3", true)] + [InlineData("someserver,version=5.9,protocol=resp3,$HELLO=", false)] + [InlineData("someserver,version=5.9,protocol=resp3,$HELLO=BONJOUR", true)] + [InlineData("someserver,version=5.9,protocol=3", true, "resp3")] + [InlineData("someserver,version=5.9,protocol=3,$HELLO=", false, "resp3")] + [InlineData("someserver,version=5.9,protocol=3,$HELLO=BONJOUR", true, "resp3")] + [InlineData("someserver,version=5.9,protocol=2", false, "resp2")] + [InlineData("someserver,version=5.9,protocol=2,$HELLO=", false, "resp2")] + [InlineData("someserver,version=5.9,protocol=2,$HELLO=BONJOUR", false, "resp2")] + // specify a post-6 version; attempt by default + [InlineData("someserver,version=6.0", false)] + [InlineData("someserver,version=6.0,$HELLO=", false)] + [InlineData("someserver,version=6.0,$HELLO=BONJOUR", false)] + [InlineData("someserver,version=6.0,protocol=resp3", true)] + [InlineData("someserver,version=6.0,protocol=resp3,$HELLO=", false)] + [InlineData("someserver,version=6.0,protocol=resp3,$HELLO=BONJOUR", true)] + [InlineData("someserver,version=6.0,protocol=3", true, "resp3")] + [InlineData("someserver,version=6.0,protocol=3,$HELLO=", false, "resp3")] + [InlineData("someserver,version=6.0,protocol=3,$HELLO=BONJOUR", true, "resp3")] + [InlineData("someserver,version=6.0,protocol=2", false, "resp2")] + [InlineData("someserver,version=6.0,protocol=2,$HELLO=", false, "resp2")] + [InlineData("someserver,version=6.0,protocol=2,$HELLO=BONJOUR", false, "resp2")] + [InlineData("someserver,version=7.2", false)] + [InlineData("someserver,version=7.2,$HELLO=", false)] + [InlineData("someserver,version=7.2,$HELLO=BONJOUR", false)] + public void ParseFormatConfigOptions(string configurationString, bool tryResp3, string? formatProtocol = null) + { + var config = ConfigurationOptions.Parse(configurationString); + + string expectedConfigurationString = formatProtocol is null ? configurationString : Regex.Replace(configurationString, "(?<=protocol=)[^,]+", formatProtocol); + + Assert.Equal(expectedConfigurationString, config.ToString(true)); // check round-trip + Assert.Equal(expectedConfigurationString, config.Clone().ToString(true)); // check clone + Assert.Equal(tryResp3, config.TryResp3()); + } + + [Fact] + [RunPerProtocol] + public async Task TryConnect() + { + var muxer = Create(shared: false); + await muxer.GetDatabase().PingAsync(); + + var server = muxer.GetServerEndPoint(muxer.GetEndPoints().Single()); + if (TestContext.Current.IsResp3() && !server.GetFeatures().Resp3) + { + Assert.Skip("server does not support RESP3"); + } + if (TestContext.Current.IsResp3()) + { + Assert.Equal(RedisProtocol.Resp3, server.Protocol); + } + else + { + Assert.Equal(RedisProtocol.Resp2, server.Protocol); + } + var cid = server.GetBridge(RedisCommand.GET)?.ConnectionId; + if (server.GetFeatures().ClientId) + { + Assert.NotNull(cid); + } + else + { + Assert.Null(cid); + } + } + + [Theory] + [InlineData("HELLO", true)] + [InlineData("BONJOUR", false)] + public async Task ConnectWithBrokenHello(string command, bool isResp3) + { + var config = ConfigurationOptions.Parse(TestConfig.Current.SecureServerAndPort); + config.Password = TestConfig.Current.SecurePassword; + config.Protocol = RedisProtocol.Resp3; + config.CommandMap = CommandMap.Create(new() { ["hello"] = command }); + + await using var muxer = await ConnectionMultiplexer.ConnectAsync(config, Writer); + await muxer.GetDatabase().PingAsync(); // is connected + var ep = muxer.GetServerEndPoint(muxer.GetEndPoints()[0]); + if (!ep.GetFeatures().Resp3) // this is just a v6 check + { + isResp3 = false; // then, no: it won't be + } + Assert.Equal(isResp3 ? RedisProtocol.Resp3 : RedisProtocol.Resp2, ep.Protocol); + var result = await muxer.GetDatabase().ExecuteAsync("latency", "doctor"); + Assert.Equal(isResp3 ? ResultType.VerbatimString : ResultType.BulkString, result.Resp3Type); + } + + [Theory] + [InlineData("return 42", RedisProtocol.Resp2, ResultType.Integer, ResultType.Integer, 42)] + [InlineData("return 'abc'", RedisProtocol.Resp2, ResultType.BulkString, ResultType.BulkString, "abc")] + [InlineData(@"return {1,2,3}", RedisProtocol.Resp2, ResultType.Array, ResultType.Array, ARR_123)] + [InlineData("return nil", RedisProtocol.Resp2, ResultType.BulkString, ResultType.Null, null)] + [InlineData(@"return redis.pcall('hgetall', '{key}')", RedisProtocol.Resp2, ResultType.Array, ResultType.Array, MAP_ABC)] + [InlineData(@"redis.setresp(3) return redis.pcall('hgetall', '{key}')", RedisProtocol.Resp2, ResultType.Array, ResultType.Array, MAP_ABC)] + [InlineData("return true", RedisProtocol.Resp2, ResultType.Integer, ResultType.Integer, 1)] + [InlineData("return false", RedisProtocol.Resp2, ResultType.BulkString, ResultType.Null, null)] + [InlineData("redis.setresp(3) return true", RedisProtocol.Resp2, ResultType.Integer, ResultType.Integer, 1)] + [InlineData("redis.setresp(3) return false", RedisProtocol.Resp2, ResultType.Integer, ResultType.Integer, 0)] + + [InlineData("return { map = { a = 1, b = 2, c = 3 } }", RedisProtocol.Resp2, ResultType.Array, ResultType.Array, MAP_ABC, 6)] + [InlineData("return { set = { a = 1, b = 2, c = 3 } }", RedisProtocol.Resp2, ResultType.Array, ResultType.Array, SET_ABC, 6)] + [InlineData("return { double = 42 }", RedisProtocol.Resp2, ResultType.BulkString, ResultType.BulkString, 42.0, 6)] + + [InlineData("return 42", RedisProtocol.Resp3, ResultType.Integer, ResultType.Integer, 42)] + [InlineData("return 'abc'", RedisProtocol.Resp3, ResultType.BulkString, ResultType.BulkString, "abc")] + [InlineData("return {1,2,3}", RedisProtocol.Resp3, ResultType.Array, ResultType.Array, ARR_123)] + [InlineData("return nil", RedisProtocol.Resp3, ResultType.BulkString, ResultType.Null, null)] + [InlineData(@"return redis.pcall('hgetall', '{key}')", RedisProtocol.Resp3, ResultType.Array, ResultType.Array, MAP_ABC)] + [InlineData(@"redis.setresp(3) return redis.pcall('hgetall', '{key}')", RedisProtocol.Resp3, ResultType.Array, ResultType.Map, MAP_ABC)] + [InlineData("return true", RedisProtocol.Resp3, ResultType.Integer, ResultType.Integer, 1)] + [InlineData("return false", RedisProtocol.Resp3, ResultType.BulkString, ResultType.Null, null)] + [InlineData("redis.setresp(3) return true", RedisProtocol.Resp3, ResultType.Integer, ResultType.Boolean, true)] + [InlineData("redis.setresp(3) return false", RedisProtocol.Resp3, ResultType.Integer, ResultType.Boolean, false)] + + [InlineData("return { map = { a = 1, b = 2, c = 3 } }", RedisProtocol.Resp3, ResultType.Array, ResultType.Map, MAP_ABC, 6)] + [InlineData("return { set = { a = 1, b = 2, c = 3 } }", RedisProtocol.Resp3, ResultType.Array, ResultType.Set, SET_ABC, 6)] + [InlineData("return { double = 42 }", RedisProtocol.Resp3, ResultType.SimpleString, ResultType.Double, 42.0, 6)] + public async Task CheckLuaResult(string script, RedisProtocol protocol, ResultType resp2, ResultType resp3, object? expected, int? serverMin = 1) + { + // note Lua does not appear to return RESP3 types in any scenarios + var muxer = Create(protocol: protocol); + var ep = muxer.GetServerEndPoint(muxer.GetEndPoints().Single()); + if (serverMin > ep.Version.Major) + { + Assert.Skip($"applies to v{serverMin} onwards - detected v{ep.Version.Major}"); + } + if (script.Contains("redis.setresp(3)") && !ep.GetFeatures().Resp3) /* v6 check */ + { + Assert.Skip("debug protocol not available"); + } + if (ep.Protocol is null) throw new InvalidOperationException($"No protocol! {ep.InteractiveConnectionState}"); + Assert.Equal(protocol, ep.Protocol); + var key = Me(); + script = script.Replace("{key}", key); + + var db = muxer.GetDatabase(); + if (expected is MAP_ABC) + { + db.KeyDelete(key); + db.HashSet(key, "a", 1); + db.HashSet(key, "b", 2); + db.HashSet(key, "c", 3); + } + var result = await db.ScriptEvaluateAsync(script: script, flags: CommandFlags.NoScriptCache); + Assert.Equal(resp2, result.Resp2Type); + Assert.Equal(resp3, result.Resp3Type); + + switch (expected) + { + case null: + Assert.True(result.IsNull); + break; + case ARR_123: + Assert.Equal(3, result.Length); + for (int i = 0; i < result.Length; i++) + { + Assert.Equal(i + 1, result[i].AsInt32()); + } + break; + case MAP_ABC: + var map = result.ToDictionary(); + Assert.Equal(3, map.Count); + Assert.True(map.TryGetValue("a", out var value)); + Assert.Equal(1, value.AsInt32()); + Assert.True(map.TryGetValue("b", out value)); + Assert.Equal(2, value.AsInt32()); + Assert.True(map.TryGetValue("c", out value)); + Assert.Equal(3, value.AsInt32()); + break; + case SET_ABC: + Assert.Equal(3, result.Length); + var arr = result.AsStringArray()!; + Assert.Contains("a", arr); + Assert.Contains("b", arr); + Assert.Contains("c", arr); + break; + case string s: + Assert.Equal(s, result.AsString()); + break; + case double d: + Assert.Equal(d, result.AsDouble()); + break; + case int i: + Assert.Equal(i, result.AsInt32()); + break; + case bool b: + Assert.Equal(b, result.AsBoolean()); + break; + } + } + + [Theory] + // [InlineData("return 42", false, ResultType.Integer, ResultType.Integer, 42)] + // [InlineData("return 'abc'", false, ResultType.BulkString, ResultType.BulkString, "abc")] + // [InlineData(@"return {1,2,3}", false, ResultType.Array, ResultType.Array, ARR_123)] + // [InlineData("return nil", false, ResultType.BulkString, ResultType.Null, null)] + // [InlineData(@"return redis.pcall('hgetall', 'key')", false, ResultType.Array, ResultType.Array, MAP_ABC)] + // [InlineData("return true", false, ResultType.Integer, ResultType.Integer, 1)] + + // [InlineData("return 42", true, ResultType.Integer, ResultType.Integer, 42)] + // [InlineData("return 'abc'", true, ResultType.BulkString, ResultType.BulkString, "abc")] + // [InlineData("return {1,2,3}", true, ResultType.Array, ResultType.Array, ARR_123)] + // [InlineData("return nil", true, ResultType.BulkString, ResultType.Null, null)] + // [InlineData(@"return redis.pcall('hgetall', 'key')", true, ResultType.Array, ResultType.Array, MAP_ABC)] + // [InlineData("return true", true, ResultType.Integer, ResultType.Integer, 1)] + [InlineData("incrby", RedisProtocol.Resp2, ResultType.Integer, ResultType.Integer, 42, "ikey", 2)] + [InlineData("incrby", RedisProtocol.Resp3, ResultType.Integer, ResultType.Integer, 42, "ikey", 2)] + [InlineData("incrby", RedisProtocol.Resp2, ResultType.Integer, ResultType.Integer, 2, "nkey", 2)] + [InlineData("incrby", RedisProtocol.Resp3, ResultType.Integer, ResultType.Integer, 2, "nkey", 2)] + + [InlineData("get", RedisProtocol.Resp2, ResultType.BulkString, ResultType.BulkString, "40", "ikey")] + [InlineData("get", RedisProtocol.Resp3, ResultType.BulkString, ResultType.BulkString, "40", "ikey")] + [InlineData("get", RedisProtocol.Resp2, ResultType.BulkString, ResultType.Null, null, "nkey")] + [InlineData("get", RedisProtocol.Resp3, ResultType.BulkString, ResultType.Null, null, "nkey")] + + [InlineData("smembers", RedisProtocol.Resp2, ResultType.Array, ResultType.Array, SET_ABC, "skey")] + [InlineData("smembers", RedisProtocol.Resp3, ResultType.Array, ResultType.Set, SET_ABC, "skey")] + [InlineData("smembers", RedisProtocol.Resp2, ResultType.Array, ResultType.Array, EMPTY_ARR, "nkey")] + [InlineData("smembers", RedisProtocol.Resp3, ResultType.Array, ResultType.Set, EMPTY_ARR, "nkey")] + + [InlineData("hgetall", RedisProtocol.Resp2, ResultType.Array, ResultType.Array, MAP_ABC, "hkey")] + [InlineData("hgetall", RedisProtocol.Resp3, ResultType.Array, ResultType.Map, MAP_ABC, "hkey")] + [InlineData("hgetall", RedisProtocol.Resp2, ResultType.Array, ResultType.Array, EMPTY_ARR, "nkey")] + [InlineData("hgetall", RedisProtocol.Resp3, ResultType.Array, ResultType.Map, EMPTY_ARR, "nkey")] + + [InlineData("sismember", RedisProtocol.Resp2, ResultType.Integer, ResultType.Integer, true, "skey", "b")] + [InlineData("sismember", RedisProtocol.Resp3, ResultType.Integer, ResultType.Integer, true, "skey", "b")] + [InlineData("sismember", RedisProtocol.Resp2, ResultType.Integer, ResultType.Integer, false, "nkey", "b")] + [InlineData("sismember", RedisProtocol.Resp3, ResultType.Integer, ResultType.Integer, false, "nkey", "b")] + [InlineData("sismember", RedisProtocol.Resp2, ResultType.Integer, ResultType.Integer, false, "skey", "d")] + [InlineData("sismember", RedisProtocol.Resp3, ResultType.Integer, ResultType.Integer, false, "skey", "d")] + + [InlineData("latency", RedisProtocol.Resp2, ResultType.BulkString, ResultType.BulkString, STR_DAVE, "doctor")] + [InlineData("latency", RedisProtocol.Resp3, ResultType.BulkString, ResultType.VerbatimString, STR_DAVE, "doctor")] + + [InlineData("incrbyfloat", RedisProtocol.Resp2, ResultType.BulkString, ResultType.BulkString, 41.5, "ikey", 1.5)] + [InlineData("incrbyfloat", RedisProtocol.Resp3, ResultType.BulkString, ResultType.BulkString, 41.5, "ikey", 1.5)] + + /* DEBUG PROTOCOL + * Reply with a test value of the specified type. can be: string, + * integer, double, bignum, null, array, set, map, attrib, push, verbatim, + * true, false., + * + * NOTE: "debug protocol" may be disabled in later default server configs; if this starts + * failing when we upgrade the test server: update the config to re-enable the command + */ + [InlineData("debug", RedisProtocol.Resp2, ResultType.BulkString, ResultType.BulkString, ANY, "protocol", "string")] + [InlineData("debug", RedisProtocol.Resp3, ResultType.BulkString, ResultType.BulkString, ANY, "protocol", "string")] + + [InlineData("debug", RedisProtocol.Resp2, ResultType.BulkString, ResultType.BulkString, ANY, "protocol", "double")] + [InlineData("debug", RedisProtocol.Resp3, ResultType.SimpleString, ResultType.Double, ANY, "protocol", "double")] + + [InlineData("debug", RedisProtocol.Resp2, ResultType.BulkString, ResultType.BulkString, ANY, "protocol", "bignum")] + [InlineData("debug", RedisProtocol.Resp3, ResultType.SimpleString, ResultType.BigInteger, ANY, "protocol", "bignum")] + + [InlineData("debug", RedisProtocol.Resp2, ResultType.BulkString, ResultType.Null, null, "protocol", "null")] + [InlineData("debug", RedisProtocol.Resp3, ResultType.BulkString, ResultType.Null, null, "protocol", "null")] + + [InlineData("debug", RedisProtocol.Resp2, ResultType.Array, ResultType.Array, ANY, "protocol", "array")] + [InlineData("debug", RedisProtocol.Resp3, ResultType.Array, ResultType.Array, ANY, "protocol", "array")] + + [InlineData("debug", RedisProtocol.Resp2, ResultType.Array, ResultType.Array, ANY, "protocol", "set")] + [InlineData("debug", RedisProtocol.Resp3, ResultType.Array, ResultType.Set, ANY, "protocol", "set")] + + [InlineData("debug", RedisProtocol.Resp2, ResultType.Array, ResultType.Array, ANY, "protocol", "map")] + [InlineData("debug", RedisProtocol.Resp3, ResultType.Array, ResultType.Map, ANY, "protocol", "map")] + + [InlineData("debug", RedisProtocol.Resp2, ResultType.BulkString, ResultType.BulkString, ANY, "protocol", "verbatim")] + [InlineData("debug", RedisProtocol.Resp3, ResultType.BulkString, ResultType.VerbatimString, ANY, "protocol", "verbatim")] + + [InlineData("debug", RedisProtocol.Resp2, ResultType.Integer, ResultType.Integer, true, "protocol", "true")] + [InlineData("debug", RedisProtocol.Resp3, ResultType.Integer, ResultType.Boolean, true, "protocol", "true")] + + [InlineData("debug", RedisProtocol.Resp2, ResultType.Integer, ResultType.Integer, false, "protocol", "false")] + [InlineData("debug", RedisProtocol.Resp3, ResultType.Integer, ResultType.Boolean, false, "protocol", "false")] + + public async Task CheckCommandResult(string command, RedisProtocol protocol, ResultType resp2, ResultType resp3, object? expected, params object[] args) + { + var muxer = Create(protocol: protocol); + var ep = muxer.GetServerEndPoint(muxer.GetEndPoints().Single()); + if (command == "debug" && args.Length > 0 && args[0] is "protocol" && !ep.GetFeatures().Resp3 /* v6 check */) + { + Assert.Skip("debug protocol not available"); + } + Assert.Equal(protocol, ep.Protocol); + + var db = muxer.GetDatabase(); + if (args.Length > 0) + { + var origKey = (string)args[0]; + switch (origKey) + { + case "ikey": + case "skey": + case "hkey": + case "nkey": + var newKey = Me() + "_" + origKey; // disambiguate + args[0] = newKey; + await db.KeyDeleteAsync(newKey); // remove + switch (origKey) // initialize + { + case "ikey": + await db.StringSetAsync(newKey, "40"); + break; + case "skey": + await db.SetAddAsync(newKey, ["a", "b", "c"]); + break; + case "hkey": + await db.HashSetAsync(newKey, [new("a", 1), new("b", 2), new("c", 3)]); + break; + } + break; + } + } + var result = await db.ExecuteAsync(command, args); + Assert.Equal(resp2, result.Resp2Type); + Assert.Equal(resp3, result.Resp3Type); + + switch (expected) + { + case null: + Assert.True(result.IsNull); + break; + case ANY: + // not checked beyond type + break; + case EMPTY_ARR: + Assert.Equal(0, result.Length); + break; + case ARR_123: + Assert.Equal(3, result.Length); + for (int i = 0; i < result.Length; i++) + { + Assert.Equal(i + 1, result[i].AsInt32()); + } + break; + case STR_DAVE: + var scontent = result.ToString(); + Log(scontent); + Assert.NotNull(scontent); + var isExpectedContent = scontent.StartsWith("Dave, ") || scontent.StartsWith("I'm sorry, Dave"); + Assert.True(isExpectedContent); + Log(scontent); + + scontent = result.ToString(out var type); + Assert.NotNull(scontent); + isExpectedContent = scontent.StartsWith("Dave, ") || scontent.StartsWith("I'm sorry, Dave"); + Assert.True(isExpectedContent); + Log(scontent); + if (protocol == RedisProtocol.Resp3) + { + Assert.Equal("txt", type); + } + else + { + Assert.Null(type); + } + break; + case SET_ABC: + Assert.Equal(3, result.Length); + var arr = result.AsStringArray()!; + Assert.Contains("a", arr); + Assert.Contains("b", arr); + Assert.Contains("c", arr); + break; + case MAP_ABC: + var map = result.ToDictionary(); + Assert.Equal(3, map.Count); + Assert.True(map.TryGetValue("a", out var value)); + Assert.Equal(1, value.AsInt32()); + Assert.True(map.TryGetValue("b", out value)); + Assert.Equal(2, value.AsInt32()); + Assert.True(map.TryGetValue("c", out value)); + Assert.Equal(3, value.AsInt32()); + break; + case string s: + Assert.Equal(s, result.AsString()); + break; + case int i: + Assert.Equal(i, result.AsInt32()); + break; + case bool b: + Assert.Equal(b, result.AsBoolean()); + Assert.Equal(b ? 1 : 0, result.AsInt32()); + Assert.Equal(b ? 1 : 0, result.AsInt64()); + break; + } + } + +#pragma warning disable SA1310 // Field names should not contain underscore + private const string SET_ABC = nameof(SET_ABC); + private const string ARR_123 = nameof(ARR_123); + private const string MAP_ABC = nameof(MAP_ABC); + private const string EMPTY_ARR = nameof(EMPTY_ARR); + private const string STR_DAVE = nameof(STR_DAVE); + private const string ANY = nameof(ANY); +#pragma warning restore SA1310 // Field names should not contain underscore +} diff --git a/tests/StackExchange.Redis.Tests/ResultBoxTests.cs b/tests/StackExchange.Redis.Tests/ResultBoxTests.cs new file mode 100644 index 000000000..adb1b309f --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ResultBoxTests.cs @@ -0,0 +1,95 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ResultBoxTests +{ + [Fact] + public void SyncResultBox() + { + var msg = Message.Create(-1, CommandFlags.None, RedisCommand.PING); + var box = SimpleResultBox.Get(); + Assert.False(box.IsAsync); + + int activated = 0; + lock (box) + { + Task.Run(() => + { + lock (box) + { + // release the worker to start work + Monitor.PulseAll(box); + + // wait for the completion signal + if (Monitor.Wait(box, TimeSpan.FromSeconds(10))) + { + Interlocked.Increment(ref activated); + } + } + }); + Assert.True(Monitor.Wait(box, TimeSpan.FromSeconds(10)), "failed to handover lock to worker"); + } + + // check that continuation was not already signalled + Thread.Sleep(100); + Assert.Equal(0, Volatile.Read(ref activated)); + + msg.SetSource(ResultProcessor.DemandOK, box); + Assert.True(msg.TrySetResult("abc")); + + // check that TrySetResult did not signal continuation + Thread.Sleep(100); + Assert.Equal(0, Volatile.Read(ref activated)); + + // check that complete signals continuation + msg.Complete(); + Thread.Sleep(100); + Assert.Equal(1, Volatile.Read(ref activated)); + + var s = box.GetResult(out var ex); + Assert.Null(ex); + Assert.NotNull(s); + Assert.Equal("abc", s); + } + + [Fact] + public void TaskResultBox() + { + // TaskResultBox currently uses a stating field for values before activations are + // signalled; High Integrity Mode *demands* this behaviour, so: validate that it + // works correctly + var msg = Message.Create(-1, CommandFlags.None, RedisCommand.PING); + var box = TaskResultBox.Create(out var tcs, null); + Assert.True(box.IsAsync); + + msg.SetSource(ResultProcessor.DemandOK, box); + Assert.True(msg.TrySetResult("abc")); + + // check that continuation was not already signalled + Thread.Sleep(100); + Assert.False(tcs.Task.IsCompleted); + + msg.SetSource(ResultProcessor.DemandOK, box); + Assert.True(msg.TrySetResult("abc")); + + // check that TrySetResult did not signal continuation + Thread.Sleep(100); + Assert.False(tcs.Task.IsCompleted); + + // check that complete signals continuation + msg.Complete(); + Thread.Sleep(100); + Assert.True(tcs.Task.IsCompleted); + + var s = box.GetResult(out var ex); + Assert.Null(ex); + Assert.NotNull(s); + Assert.Equal("abc", s); + + Assert.Equal("abc", tcs.Task.Result); // we already checked IsCompleted + } +} diff --git a/tests/StackExchange.Redis.Tests/RoleTests.cs b/tests/StackExchange.Redis.Tests/RoleTests.cs new file mode 100644 index 000000000..198ae6da7 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/RoleTests.cs @@ -0,0 +1,81 @@ +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class Roles(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + protected override string GetConfiguration() => TestConfig.Current.PrimaryServerAndPort + "," + TestConfig.Current.ReplicaServerAndPort; + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task PrimaryRole(bool allowAdmin) // should work with or without admin now + { + await using var conn = Create(allowAdmin: allowAdmin); + var servers = conn.GetServers(); + Log("Server list:"); + foreach (var s in servers) + { + Log($" Server: {s.EndPoint} (isConnected: {s.IsConnected}, isReplica: {s.IsReplica})"); + } + var server = servers.First(conn => !conn.IsReplica); + var role = server.Role(); + Log($"Chosen primary: {server.EndPoint} (role: {role})"); + if (allowAdmin) + { + Log($"Info (Replication) dump for {server.EndPoint}:"); + Log(server.InfoRaw("Replication")); + Log(""); + + foreach (var s in servers) + { + if (s.IsReplica) + { + Log($"Info (Replication) dump for {s.EndPoint}:"); + Log(s.InfoRaw("Replication")); + Log(""); + } + } + } + Assert.NotNull(role); + Assert.Equal(role.Value, RedisLiterals.master); + var primary = role as Role.Master; + Assert.NotNull(primary); + Assert.NotNull(primary.Replicas); + + // Only do this check for Redis > 4 (to exclude Redis 3.x on Windows). + // Unrelated to this test, the replica isn't connecting and we'll revisit swapping the server out. + // TODO: MemuraiDeveloper check + if (server.Version > RedisFeatures.v4_0_0) + { + Log($"Searching for: {TestConfig.Current.ReplicaServer}:{TestConfig.Current.ReplicaPort}"); + Log($"Replica count: {primary.Replicas.Count}"); + + Assert.NotEmpty(primary.Replicas); + foreach (var replica in primary.Replicas) + { + Log($" Replica: {replica.Ip}:{replica.Port} (offset: {replica.ReplicationOffset})"); + Log(replica.ToString()); + } + Assert.Contains(primary.Replicas, r => + r.Ip == TestConfig.Current.ReplicaServer && + r.Port == TestConfig.Current.ReplicaPort); + } + } + + [Fact] + public async Task ReplicaRole() + { + await using var conn = await ConnectionMultiplexer.ConnectAsync($"{TestConfig.Current.ReplicaServerAndPort},allowAdmin=true"); + var server = conn.GetServers().First(conn => conn.IsReplica); + + var role = server.Role(); + Assert.NotNull(role); + var replica = role as Role.Replica; + Assert.NotNull(replica); + Assert.Equal(replica.MasterIp, TestConfig.Current.PrimaryServer); + Assert.Equal(replica.MasterPort, TestConfig.Current.PrimaryPort); + } +} diff --git a/tests/StackExchange.Redis.Tests/SSDBTests.cs b/tests/StackExchange.Redis.Tests/SSDBTests.cs new file mode 100644 index 000000000..a1f2f3d5e --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SSDBTests.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class SSDBTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task ConnectToSSDB() + { + Skip.IfNoConfig(nameof(TestConfig.Config.SSDBServer), TestConfig.Current.SSDBServer); + + await using var conn = await ConnectionMultiplexer.ConnectAsync(new ConfigurationOptions + { + EndPoints = { { TestConfig.Current.SSDBServer, TestConfig.Current.SSDBPort } }, + CommandMap = CommandMap.SSDB, + }); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + Assert.True(db.StringGet(key).IsNull); + db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); + Assert.Equal("abc", db.StringGet(key)); + } +} diff --git a/tests/StackExchange.Redis.Tests/SSLTests.cs b/tests/StackExchange.Redis.Tests/SSLTests.cs new file mode 100644 index 000000000..0dafe3f9b --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SSLTests.cs @@ -0,0 +1,567 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Reflection; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using StackExchange.Redis.Tests.Helpers; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class SSLTests(ITestOutputHelper output, SSLTests.SSLServerFixture fixture) : TestBase(output), IClassFixture +{ + private SSLServerFixture Fixture { get; } = fixture; + + [Theory] // (note the 6379 port is closed) + [InlineData(null, true)] // auto-infer port (but specify 6380) + [InlineData(6380, true)] // all explicit + public async Task ConnectToAzure(int? port, bool ssl) + { + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer); + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCachePassword), TestConfig.Current.AzureCachePassword); + + var options = new ConfigurationOptions(); + options.CertificateValidation += ShowCertFailures(Writer); + if (port == null) + { + options.EndPoints.Add(TestConfig.Current.AzureCacheServer); + } + else + { + options.EndPoints.Add(TestConfig.Current.AzureCacheServer, port.Value); + } + options.Ssl = ssl; + options.Password = TestConfig.Current.AzureCachePassword; + Log(options.ToString()); + using (var connection = ConnectionMultiplexer.Connect(options)) + { + var ttl = await connection.GetDatabase().PingAsync(); + Log(ttl.ToString()); + } + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public async Task ConnectToSSLServer(bool useSsl, bool specifyHost) + { + Fixture.SkipIfNoServer(); + + var server = TestConfig.Current.SslServer; + int? port = TestConfig.Current.SslPort; + string? password = ""; + bool isAzure = false; + if (string.IsNullOrWhiteSpace(server) && useSsl) + { + // we can bounce it past azure instead? + server = TestConfig.Current.AzureCacheServer; + password = TestConfig.Current.AzureCachePassword; + port = null; + isAzure = true; + } + Skip.IfNoConfig(nameof(TestConfig.Config.SslServer), server); + + var config = new ConfigurationOptions + { + AllowAdmin = true, + SyncTimeout = Debugger.IsAttached ? int.MaxValue : 2000, + Password = password, + }; + var map = new Dictionary + { + ["config"] = null, // don't rely on config working + }; + if (!isAzure) map["cluster"] = null; + config.CommandMap = CommandMap.Create(map); + if (port != null) config.EndPoints.Add(server, port.Value); + else config.EndPoints.Add(server); + + if (useSsl) + { + config.Ssl = useSsl; + if (specifyHost) + { + config.SslHost = server; + } + config.CertificateValidation += (sender, cert, chain, errors) => + { + Log("errors: " + errors); + Log("cert issued to: " + cert?.Subject); + return true; // fingers in ears, pretend we don't know this is wrong + }; + } + + var configString = config.ToString(); + Log("config: " + configString); + var clone = ConfigurationOptions.Parse(configString); + Assert.Equal(configString, clone.ToString()); + + var log = new StringBuilder(); + Writer.EchoTo(log); + + if (useSsl) + { + await using var conn = await ConnectionMultiplexer.ConnectAsync(config, Writer); + + Log("Connect log:"); + lock (log) + { + Log(log.ToString()); + } + Log("===="); + conn.ConnectionFailed += OnConnectionFailed; + conn.InternalError += OnInternalError; + var db = conn.GetDatabase(); + await db.PingAsync().ForAwait(); + using (var file = File.Create("ssl-" + useSsl + "-" + specifyHost + ".zip")) + { + conn.ExportConfiguration(file); + } + RedisKey key = "SE.Redis"; + + const int AsyncLoop = 2000; + // perf; async + await db.KeyDeleteAsync(key).ForAwait(); + var watch = Stopwatch.StartNew(); + for (int i = 0; i < AsyncLoop; i++) + { + try + { + await db.StringIncrementAsync(key, flags: CommandFlags.FireAndForget).ForAwait(); + } + catch (Exception ex) + { + Log($"Failure on i={i}: {ex.Message}"); + throw; + } + } + // need to do this inside the timer to measure the TTLB + long value = (long)await db.StringGetAsync(key).ForAwait(); + watch.Stop(); + Assert.Equal(AsyncLoop, value); + Log($"F&F: {AsyncLoop} INCR, {watch.ElapsedMilliseconds:###,##0}ms, {(long)(AsyncLoop / watch.Elapsed.TotalSeconds)} ops/s; final value: {value}"); + + // perf: sync/multi-threaded + // TestConcurrent(db, key, 30, 10); + // TestConcurrent(db, key, 30, 20); + // TestConcurrent(db, key, 30, 30); + // TestConcurrent(db, key, 30, 40); + // TestConcurrent(db, key, 30, 50); + } + else + { + Assert.Throws(() => ConnectionMultiplexer.Connect(config, Writer)); + } + } + +#if NETCOREAPP3_1_OR_GREATER +#pragma warning disable CS0618 // Type or member is obsolete + // Docker configured with only TLS_AES_256_GCM_SHA384 for testing + [Theory] + [InlineData(SslProtocols.None, true, TlsCipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TlsCipherSuite.TLS_AES_256_GCM_SHA384)] + [InlineData(SslProtocols.Tls12, true, TlsCipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TlsCipherSuite.TLS_AES_256_GCM_SHA384)] + [InlineData(SslProtocols.Tls13, true, TlsCipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TlsCipherSuite.TLS_AES_256_GCM_SHA384)] + [InlineData(SslProtocols.Tls12, false, TlsCipherSuite.TLS_AES_128_CCM_8_SHA256)] + [InlineData(SslProtocols.Tls12, true)] + [InlineData(SslProtocols.Tls13, true)] + [InlineData(SslProtocols.Ssl2, false)] + [InlineData(SslProtocols.Ssl3, false)] + [InlineData(SslProtocols.Tls12 | SslProtocols.Tls13, true)] + [InlineData(SslProtocols.Ssl3 | SslProtocols.Tls12 | SslProtocols.Tls13, true)] + [InlineData(SslProtocols.Ssl2, false, TlsCipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TlsCipherSuite.TLS_AES_256_GCM_SHA384)] +#pragma warning restore CS0618 // Type or member is obsolete + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Yes, we know.")] + public async Task ConnectSslClientAuthenticationOptions(SslProtocols protocols, bool expectSuccess, params TlsCipherSuite[] tlsCipherSuites) + { + Fixture.SkipIfNoServer(); + + try + { + var config = new ConfigurationOptions() + { + EndPoints = { TestConfig.Current.SslServerAndPort }, + AllowAdmin = true, + ConnectRetry = 1, + SyncTimeout = Debugger.IsAttached ? int.MaxValue : 5000, + Ssl = true, + SslClientAuthenticationOptions = host => new SslClientAuthenticationOptions() + { + TargetHost = host, + CertificateRevocationCheckMode = X509RevocationMode.NoCheck, + EnabledSslProtocols = protocols, + CipherSuitesPolicy = tlsCipherSuites?.Length > 0 ? new CipherSuitesPolicy(tlsCipherSuites) : null, + RemoteCertificateValidationCallback = (sender, cert, chain, errors) => + { + Log(" Errors: " + errors); + Log(" Cert issued to: " + cert?.Subject); + return true; + }, + }, + }; + + if (expectSuccess) + { + await using var conn = await ConnectionMultiplexer.ConnectAsync(config, Writer); + + var db = conn.GetDatabase(); + Log("Pinging..."); + var time = await db.PingAsync().ForAwait(); + Log($"Ping time: {time}"); + } + else + { + var ex = await Assert.ThrowsAsync(() => ConnectionMultiplexer.ConnectAsync(config, Writer)); + Log("(Expected) Failure connecting: " + ex.Message); + if (ex.InnerException is PlatformNotSupportedException pnse) + { + Assert.Skip("Expected failure, but also test not supported on this platform: " + pnse.Message); + } + } + } + catch (RedisException ex) when (ex.InnerException is PlatformNotSupportedException pnse) + { + Assert.Skip("Test not supported on this platform: " + pnse.Message); + } + } +#endif + + [Fact] + public async Task RedisLabsSSL() + { + Skip.IfNoConfig(nameof(TestConfig.Config.RedisLabsSslServer), TestConfig.Current.RedisLabsSslServer); + Skip.IfNoConfig(nameof(TestConfig.Config.RedisLabsPfxPath), TestConfig.Current.RedisLabsPfxPath); + + var cert = new X509Certificate2(TestConfig.Current.RedisLabsPfxPath, ""); + Assert.NotNull(cert); + Log("Thumbprint: " + cert.Thumbprint); + + int timeout = 5000; + if (Debugger.IsAttached) timeout *= 100; + var options = new ConfigurationOptions + { + EndPoints = { { TestConfig.Current.RedisLabsSslServer, TestConfig.Current.RedisLabsSslPort } }, + ConnectTimeout = timeout, + AllowAdmin = true, + CommandMap = CommandMap.Create( + new HashSet + { + "subscribe", + "unsubscribe", + "cluster", + }, + false), + }; + + options.TrustIssuer("redislabs_ca.pem"); + + if (!Directory.Exists(Me())) Directory.CreateDirectory(Me()); +#if LOGOUTPUT + ConnectionMultiplexer.EchoPath = Me(); +#endif + options.Ssl = true; + options.CertificateSelection += (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => cert; + + await using var conn = ConnectionMultiplexer.Connect(options); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + string? s = db.StringGet(key); + Assert.Null(s); + db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); + s = db.StringGet(key); + Assert.Equal("abc", s); + + var latency = await db.PingAsync(); + Log("RedisLabs latency: {0:###,##0.##}ms", latency.TotalMilliseconds); + + using (var file = File.Create("RedisLabs.zip")) + { + conn.ExportConfiguration(file); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task RedisLabsEnvironmentVariableClientCertificate(bool setEnv) + { + try + { + Skip.IfNoConfig(nameof(TestConfig.Config.RedisLabsSslServer), TestConfig.Current.RedisLabsSslServer); + Skip.IfNoConfig(nameof(TestConfig.Config.RedisLabsPfxPath), TestConfig.Current.RedisLabsPfxPath); + + if (setEnv) + { + Environment.SetEnvironmentVariable("SERedis_ClientCertPfxPath", TestConfig.Current.RedisLabsPfxPath); + Environment.SetEnvironmentVariable("SERedis_IssuerCertPath", "redislabs_ca.pem"); + // check env worked + Assert.Equal(TestConfig.Current.RedisLabsPfxPath, Environment.GetEnvironmentVariable("SERedis_ClientCertPfxPath")); + Assert.Equal("redislabs_ca.pem", Environment.GetEnvironmentVariable("SERedis_IssuerCertPath")); + } + int timeout = 5000; + if (Debugger.IsAttached) timeout *= 100; + var options = new ConfigurationOptions + { + EndPoints = { { TestConfig.Current.RedisLabsSslServer, TestConfig.Current.RedisLabsSslPort } }, + ConnectTimeout = timeout, + AllowAdmin = true, + CommandMap = CommandMap.Create( + new HashSet + { + "subscribe", + "unsubscribe", + "cluster", + }, + false), + }; + + if (!Directory.Exists(Me())) Directory.CreateDirectory(Me()); +#if LOGOUTPUT + ConnectionMultiplexer.EchoPath = Me(); +#endif + options.Ssl = true; + + await using var conn = ConnectionMultiplexer.Connect(options); + + RedisKey key = Me(); + if (!setEnv) Assert.Fail("Could not set environment"); + + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + string? s = db.StringGet(key); + Assert.Null(s); + db.StringSet(key, "abc"); + s = db.StringGet(key); + Assert.Equal("abc", s); + + var latency = await db.PingAsync(); + Log("RedisLabs latency: {0:###,##0.##}ms", latency.TotalMilliseconds); + + using (var file = File.Create("RedisLabs.zip")) + { + conn.ExportConfiguration(file); + } + } + catch (RedisConnectionException ex) when (!setEnv && ex.FailureType == ConnectionFailureType.UnableToConnect) + { + } + finally + { + Environment.SetEnvironmentVariable("SERedis_ClientCertPfxPath", null); + } + } + + [Fact] + public void SSLHostInferredFromEndpoints() + { + var options = new ConfigurationOptions + { + EndPoints = + { + { "mycache.rediscache.windows.net", 15000 }, + { "mycache.rediscache.windows.net", 15001 }, + { "mycache.rediscache.windows.net", 15002 }, + }, + Ssl = true, + }; + Assert.Equal("mycache.rediscache.windows.net", options.SslHost); + options = new ConfigurationOptions() + { + EndPoints = { { "121.23.23.45", 15000 } }, + }; + Assert.Null(options.SslHost); + } + + private void Check(string name, object? x, object? y) + { + Log($"{name}: {(x == null ? "(null)" : x.ToString())} vs {(y == null ? "(null)" : y.ToString())}"); + Assert.Equal(x, y); + } + + [Fact] + public void Issue883_Exhaustive() + { + var old = CultureInfo.CurrentCulture; + try + { + var fields = typeof(ConfigurationOptions).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var all = CultureInfo.GetCultures(CultureTypes.AllCultures); + Log($"Checking {all.Length} cultures..."); + foreach (var ci in all) + { + Log("Testing: " + ci.Name); + CultureInfo.CurrentCulture = ci; + + var a = ConfigurationOptions.Parse("myDNS:883,password=mypassword,connectRetry=3,connectTimeout=5000,syncTimeout=5000,defaultDatabase=0,ssl=true,abortConnect=false"); + var b = new ConfigurationOptions + { + EndPoints = { { "myDNS", 883 } }, + Password = "mypassword", + ConnectRetry = 3, + ConnectTimeout = 5000, + SyncTimeout = 5000, + DefaultDatabase = 0, + Ssl = true, + AbortOnConnectFail = false, + }; + Log($"computed: {b.ToString(true)}"); + + Log("Checking endpoints..."); + var c = a.EndPoints.Cast().Single(); + var d = b.EndPoints.Cast().Single(); + Check(nameof(c.Host), c.Host, d.Host); + Check(nameof(c.Port), c.Port, d.Port); + Check(nameof(c.AddressFamily), c.AddressFamily, d.AddressFamily); + + Log($"Comparing {fields.Length} fields..."); + Array.Sort(fields, (x, y) => string.CompareOrdinal(x.Name, y.Name)); + foreach (var field in fields) + { + Check(field.Name, field.GetValue(a), field.GetValue(b)); + } + } + } + finally + { + CultureInfo.CurrentCulture = old; + } + } + + [Fact] + public async Task SSLParseViaConfig_Issue883_ConfigObject() + { + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer); + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCachePassword), TestConfig.Current.AzureCachePassword); + + var options = new ConfigurationOptions + { + AbortOnConnectFail = false, + Ssl = true, + ConnectRetry = 3, + ConnectTimeout = 5000, + SyncTimeout = 5000, + DefaultDatabase = 0, + EndPoints = { { TestConfig.Current.AzureCacheServer, 6380 } }, + Password = TestConfig.Current.AzureCachePassword, + }; + options.CertificateValidation += ShowCertFailures(Writer); + + await using var conn = ConnectionMultiplexer.Connect(options); + + await conn.GetDatabase().PingAsync(); + } + + public static RemoteCertificateValidationCallback? ShowCertFailures(TextWriterOutputHelper output) + { + if (output == null) + { + return null; + } + + return (sender, certificate, chain, sslPolicyErrors) => + { + void WriteStatus(X509ChainStatus[] status) + { + if (status != null) + { + for (int i = 0; i < status.Length; i++) + { + var item = status[i]; + Log(output, $"\tstatus {i}: {item.Status}, {item.StatusInformation}"); + } + } + } + lock (output) + { + if (certificate != null) + { + Log(output, $"Subject: {certificate.Subject}"); + } + Log(output, $"Policy errors: {sslPolicyErrors}"); + if (chain != null) + { + WriteStatus(chain.ChainStatus); + + var elements = chain.ChainElements; + if (elements != null) + { + int index = 0; + foreach (var item in elements) + { + Log(output, $"{index++}: {item.Certificate.Subject}; {item.Information}"); + WriteStatus(item.ChainElementStatus); + } + } + } + } + return sslPolicyErrors == SslPolicyErrors.None; + }; + } + + [Fact] + public async Task SSLParseViaConfig_Issue883_ConfigString() + { + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer); + Skip.IfNoConfig(nameof(TestConfig.Config.AzureCachePassword), TestConfig.Current.AzureCachePassword); + + var configString = $"{TestConfig.Current.AzureCacheServer}:6380,password={TestConfig.Current.AzureCachePassword},connectRetry=3,connectTimeout=5000,syncTimeout=5000,defaultDatabase=0,ssl=true,abortConnect=false"; + var options = ConfigurationOptions.Parse(configString); + options.CertificateValidation += ShowCertFailures(Writer); + + await using var conn = ConnectionMultiplexer.Connect(options); + + await conn.GetDatabase().PingAsync(); + } + + [Fact] + public void ConfigObject_Issue1407_ToStringIncludesSslProtocols() + { + const SslProtocols sslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13; + var sourceOptions = new ConfigurationOptions + { + AbortOnConnectFail = false, + Ssl = true, + SslProtocols = sslProtocols, + ConnectRetry = 3, + ConnectTimeout = 5000, + SyncTimeout = 5000, + DefaultDatabase = 0, + EndPoints = { { "endpoint.test", 6380 } }, + Password = "123456", + }; + + var targetOptions = ConfigurationOptions.Parse(sourceOptions.ToString()); + Assert.Equal(sourceOptions.SslProtocols, targetOptions.SslProtocols); + } + + public class SSLServerFixture : IDisposable + { + public bool ServerRunning { get; } + + public SSLServerFixture() + { + ServerRunning = TestConfig.IsServerRunning(TestConfig.Current.SslServer, TestConfig.Current.SslPort); + } + + public void SkipIfNoServer() + { + Skip.IfNoConfig(nameof(TestConfig.Config.SslServer), TestConfig.Current.SslServer); + if (!ServerRunning) + { + Assert.Skip($"SSL/TLS Server was not running at {TestConfig.Current.SslServer}:{TestConfig.Current.SslPort}"); + } + } + + public void Dispose() { } + } +} diff --git a/tests/StackExchange.Redis.Tests/SanityCheckTests.cs b/tests/StackExchange.Redis.Tests/SanityCheckTests.cs new file mode 100644 index 000000000..353098fd5 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SanityCheckTests.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public sealed class SanityChecks +{ + /// + /// Ensure we don't reference System.ValueTuple as it causes issues with .NET Full Framework. + /// + /// + /// Modified from . + /// Thanks Lucas Trzesniewski!. + /// + [Fact] + public void ValueTupleNotReferenced() + { + using var fileStream = File.OpenRead(typeof(RedisValue).Assembly.Location); + using var peReader = new PEReader(fileStream); + var metadataReader = peReader.GetMetadataReader(); + + foreach (var typeRefHandle in metadataReader.TypeReferences) + { + var typeRef = metadataReader.GetTypeReference(typeRefHandle); + if (metadataReader.GetString(typeRef.Namespace) == typeof(ValueTuple).Namespace) + { + var typeName = metadataReader.GetString(typeRef.Name); + Assert.DoesNotContain(nameof(ValueTuple), typeName); + } + } + } +} diff --git a/tests/StackExchange.Redis.Tests/ScanTests.cs b/tests/StackExchange.Redis.Tests/ScanTests.cs new file mode 100644 index 000000000..fe03cbf86 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ScanTests.cs @@ -0,0 +1,464 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class ScanTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task KeysScan(bool supported) + { + string[]? disabledCommands = supported ? null : ["scan"]; + await using var conn = Create(disabledCommands: disabledCommands, allowAdmin: true); + + var dbId = TestConfig.GetDedicatedDB(conn); + var db = conn.GetDatabase(dbId); + var prefix = Me() + ":"; + var server = GetServer(conn); + Assert.Equal(TestContext.Current.GetProtocol(), server.Protocol); + server.FlushDatabase(dbId); + for (int i = 0; i < 100; i++) + { + db.StringSet(prefix + i, Guid.NewGuid().ToString(), flags: CommandFlags.FireAndForget); + } + var seq = server.Keys(dbId, pageSize: 50); + var cur = seq as IScanningCursor; + Assert.NotNull(cur); + Log($"Cursor: {cur.Cursor}, PageOffset: {cur.PageOffset}, PageSize: {cur.PageSize}"); + Assert.Equal(0, cur.PageOffset); + Assert.Equal(0, cur.Cursor); + if (supported) + { + Assert.Equal(50, cur.PageSize); + } + else + { + Assert.Equal(int.MaxValue, cur.PageSize); + } + Assert.Equal(100, seq.Distinct().Count()); + Assert.Equal(100, seq.Distinct().Count()); + Assert.Equal(100, server.Keys(dbId, prefix + "*").Distinct().Count()); + // 7, 70, 71, ..., 79 + Assert.Equal(11, server.Keys(dbId, prefix + "7*").Distinct().Count()); + } + + [Fact] + public async Task ScansIScanning() + { + await using var conn = Create(allowAdmin: true); + + var prefix = Me() + Guid.NewGuid(); + var dbId = TestConfig.GetDedicatedDB(conn); + var db = conn.GetDatabase(dbId); + var server = GetServer(conn); + server.FlushDatabase(dbId); + for (int i = 0; i < 100; i++) + { + db.StringSet(prefix + i, Guid.NewGuid().ToString(), flags: CommandFlags.FireAndForget); + } + var seq = server.Keys(dbId, prefix + "*", pageSize: 15); + using (var iter = seq.GetEnumerator()) + { + IScanningCursor s0 = (IScanningCursor)seq, s1 = (IScanningCursor)iter; + + Assert.Equal(15, s0.PageSize); + Assert.Equal(15, s1.PageSize); + + // start at zero + Assert.Equal(0, s0.Cursor); + Assert.Equal(s0.Cursor, s1.Cursor); + + for (int i = 0; i < 47; i++) + { + Assert.True(iter.MoveNext()); + } + + // non-zero in the middle + Assert.NotEqual(0, s0.Cursor); + Assert.Equal(s0.Cursor, s1.Cursor); + + for (int i = 0; i < 53; i++) + { + Assert.True(iter.MoveNext()); + } + + // zero "next" at the end + Assert.False(iter.MoveNext()); + Assert.NotEqual(0, s0.Cursor); + Assert.NotEqual(0, s1.Cursor); + } + } + + [Fact] + public async Task ScanResume() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_8_0); + + var dbId = TestConfig.GetDedicatedDB(conn); + var db = conn.GetDatabase(dbId); + var prefix = Me(); + var server = GetServer(conn); + server.FlushDatabase(dbId); + int i; + for (i = 0; i < 100; i++) + { + db.StringSet(prefix + ":" + i, Guid.NewGuid().ToString()); + } + + var expected = new HashSet(); + long snapCursor = 0; + int snapOffset = 0, snapPageSize = 0; + + i = 0; + var seq = server.Keys(dbId, prefix + ":*", pageSize: 15); + foreach (var key in seq) + { + if (i == 57) + { + snapCursor = ((IScanningCursor)seq).Cursor; + snapOffset = ((IScanningCursor)seq).PageOffset; + snapPageSize = ((IScanningCursor)seq).PageSize; + Log($"i: {i}, Cursor: {snapCursor}, Offset: {snapOffset}, PageSize: {snapPageSize}"); + } + if (i >= 57) + { + expected.Add(key); + } + i++; + } + Log($"Expected: 43, Actual: {expected.Count}, Cursor: {snapCursor}, Offset: {snapOffset}, PageSize: {snapPageSize}"); + Assert.Equal(43, expected.Count); + Assert.NotEqual(0, snapCursor); + Assert.Equal(15, snapPageSize); + + // note: you might think that we can say "hmmm, 57 when using page-size 15 on an empty (flushed) db (so: no skipped keys); that'll be + // offset 12 in the 4th page; you'd be wrong, though; page size doesn't *actually* mean page size; it is a rough analogue for + // page size, with zero guarantees; in this particular test, the first page actually has 19 elements, for example. So: we cannot + // make the following assertion: + // Assert.Equal(12, snapOffset); + seq = server.Keys(dbId, prefix + ":*", pageSize: 15, cursor: snapCursor, pageOffset: snapOffset); + var seqCur = (IScanningCursor)seq; + Assert.Equal(snapCursor, seqCur.Cursor); + Assert.Equal(snapPageSize, seqCur.PageSize); + Assert.Equal(snapOffset, seqCur.PageOffset); + using (var iter = seq.GetEnumerator()) + { + var iterCur = (IScanningCursor)iter; + Assert.Equal(snapCursor, iterCur.Cursor); + Assert.Equal(snapOffset, iterCur.PageOffset); + Assert.Equal(snapCursor, seqCur.Cursor); + Assert.Equal(snapOffset, seqCur.PageOffset); + + Assert.True(iter.MoveNext()); + Assert.Equal(snapCursor, iterCur.Cursor); + Assert.Equal(snapOffset, iterCur.PageOffset); + Assert.Equal(snapCursor, seqCur.Cursor); + Assert.Equal(snapOffset, seqCur.PageOffset); + + Assert.True(iter.MoveNext()); + Assert.Equal(snapCursor, iterCur.Cursor); + Assert.Equal(snapOffset + 1, iterCur.PageOffset); + Assert.Equal(snapCursor, seqCur.Cursor); + Assert.Equal(snapOffset + 1, seqCur.PageOffset); + } + + int count = 0; + foreach (var key in seq) + { + expected.Remove(key); + count++; + } + Assert.Empty(expected); + Assert.Equal(43, count); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SetScan(bool supported) + { + string[]? disabledCommands = supported ? null : ["sscan"]; + + await using var conn = Create(disabledCommands: disabledCommands); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.SetAdd(key, "a", CommandFlags.FireAndForget); + db.SetAdd(key, "b", CommandFlags.FireAndForget); + db.SetAdd(key, "c", CommandFlags.FireAndForget); + var arr = db.SetScan(key).ToArray(); + Assert.Equal(3, arr.Length); + Assert.Contains((RedisValue)"a", arr); + Assert.Contains((RedisValue)"b", arr); + Assert.Contains((RedisValue)"c", arr); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SortedSetScan(bool supported) + { + string[]? disabledCommands = supported ? null : ["zscan"]; + + await using var conn = Create(disabledCommands: disabledCommands); + + RedisKey key = Me() + supported; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.SortedSetAdd(key, "a", 1, CommandFlags.FireAndForget); + db.SortedSetAdd(key, "b", 2, CommandFlags.FireAndForget); + db.SortedSetAdd(key, "c", 3, CommandFlags.FireAndForget); + + var arr = db.SortedSetScan(key).ToArray(); + Assert.Equal(3, arr.Length); + Assert.True(arr.Any(x => x.Element == "a" && x.Score == 1), "a"); + Assert.True(arr.Any(x => x.Element == "b" && x.Score == 2), "b"); + Assert.True(arr.Any(x => x.Element == "c" && x.Score == 3), "c"); + + var dictionary = arr.ToDictionary(); + Assert.Equal(1, dictionary["a"]); + Assert.Equal(2, dictionary["b"]); + Assert.Equal(3, dictionary["c"]); + + var sDictionary = arr.ToStringDictionary(); + Assert.Equal(1, sDictionary["a"]); + Assert.Equal(2, sDictionary["b"]); + Assert.Equal(3, sDictionary["c"]); + + var basic = db.SortedSetRangeByRankWithScores(key, order: Order.Ascending).ToDictionary(); + Assert.Equal(3, basic.Count); + Assert.Equal(1, basic["a"]); + Assert.Equal(2, basic["b"]); + Assert.Equal(3, basic["c"]); + + basic = db.SortedSetRangeByRankWithScores(key, order: Order.Descending).ToDictionary(); + Assert.Equal(3, basic.Count); + Assert.Equal(1, basic["a"]); + Assert.Equal(2, basic["b"]); + Assert.Equal(3, basic["c"]); + + var basicArr = db.SortedSetRangeByScoreWithScores(key, order: Order.Ascending); + Assert.Equal(3, basicArr.Length); + Assert.Equal(1, basicArr[0].Score); + Assert.Equal(2, basicArr[1].Score); + Assert.Equal(3, basicArr[2].Score); + basic = basicArr.ToDictionary(); + Assert.Equal(3, basic.Count); // asc + Assert.Equal(1, basic["a"]); + Assert.Equal(2, basic["b"]); + Assert.Equal(3, basic["c"]); + + basicArr = db.SortedSetRangeByScoreWithScores(key, order: Order.Descending); + Assert.Equal(3, basicArr.Length); + Assert.Equal(3, basicArr[0].Score); + Assert.Equal(2, basicArr[1].Score); + Assert.Equal(1, basicArr[2].Score); + basic = basicArr.ToDictionary(); + Assert.Equal(3, basic.Count); // desc + Assert.Equal(1, basic["a"]); + Assert.Equal(2, basic["b"]); + Assert.Equal(3, basic["c"]); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task HashScan(bool supported) + { + string[]? disabledCommands = supported ? null : ["hscan"]; + + await using var conn = Create(disabledCommands: disabledCommands); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.HashSet(key, "a", "1", flags: CommandFlags.FireAndForget); + db.HashSet(key, "b", "2", flags: CommandFlags.FireAndForget); + db.HashSet(key, "c", "3", flags: CommandFlags.FireAndForget); + + var arr = db.HashScan(key).ToArray(); + Assert.Equal(3, arr.Length); + Assert.True(arr.Any(x => x.Name == "a" && x.Value == "1"), "a"); + Assert.True(arr.Any(x => x.Name == "b" && x.Value == "2"), "b"); + Assert.True(arr.Any(x => x.Name == "c" && x.Value == "3"), "c"); + + var dictionary = arr.ToDictionary(); + Assert.Equal(1, (long)dictionary["a"]); + Assert.Equal(2, (long)dictionary["b"]); + Assert.Equal(3, (long)dictionary["c"]); + + var sDictionary = arr.ToStringDictionary(); + Assert.Equal("1", sDictionary["a"]); + Assert.Equal("2", sDictionary["b"]); + Assert.Equal("3", sDictionary["c"]); + + var basic = db.HashGetAll(key).ToDictionary(); + Assert.Equal(3, basic.Count); + Assert.Equal(1, (long)basic["a"]); + Assert.Equal(2, (long)basic["b"]); + Assert.Equal(3, (long)basic["c"]); + } + + [Theory] + [InlineData(10)] + [InlineData(100)] + [InlineData(1000)] + [InlineData(10000)] + public async Task HashScanLarge(int pageSize) + { + await using var conn = Create(); + + RedisKey key = Me() + pageSize; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + for (int i = 0; i < 2000; i++) + db.HashSet(key, "k" + i, "v" + i, flags: CommandFlags.FireAndForget); + + int count = db.HashScan(key, pageSize: pageSize).Count(); + Assert.Equal(2000, count); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task HashScanNoValues(bool supported) + { + string[]? disabledCommands = supported ? null : ["hscan"]; + + await using var conn = Create(require: RedisFeatures.v7_4_0_rc1, disabledCommands: disabledCommands); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.HashSet(key, "a", "1", flags: CommandFlags.FireAndForget); + db.HashSet(key, "b", "2", flags: CommandFlags.FireAndForget); + db.HashSet(key, "c", "3", flags: CommandFlags.FireAndForget); + + var arr = db.HashScanNoValues(key).ToArray(); + Assert.Equal(3, arr.Length); + Assert.True(arr.Any(x => x == "a"), "a"); + Assert.True(arr.Any(x => x == "b"), "b"); + Assert.True(arr.Any(x => x == "c"), "c"); + + var basic = db.HashGetAll(key).ToDictionary(); + Assert.Equal(3, basic.Count); + Assert.Equal(1, (long)basic["a"]); + Assert.Equal(2, (long)basic["b"]); + Assert.Equal(3, (long)basic["c"]); + } + + [Theory] + [InlineData(10)] + [InlineData(100)] + [InlineData(1000)] + [InlineData(10000)] + public async Task HashScanNoValuesLarge(int pageSize) + { + await using var conn = Create(require: RedisFeatures.v7_4_0_rc1); + + RedisKey key = Me() + pageSize; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + for (int i = 0; i < 2000; i++) + { + db.HashSet(key, "k" + i, "v" + i, flags: CommandFlags.FireAndForget); + } + + int count = db.HashScanNoValues(key, pageSize: pageSize).Count(); + Assert.Equal(2000, count); + } + + /// + /// See . + /// + [Fact] + public async Task HashScanThresholds() + { + await using var conn = Create(allowAdmin: true); + + var config = conn.GetServer(conn.GetEndPoints(true)[0]).ConfigGet("hash-max-ziplist-entries").First(); + var threshold = int.Parse(config.Value); + + RedisKey key = Me(); + Assert.False(GotCursors(conn, key, threshold - 1)); + Assert.True(GotCursors(conn, key, threshold + 1)); + } + + private static bool GotCursors(IConnectionMultiplexer conn, RedisKey key, int count) + { + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var entries = new HashEntry[count]; + for (var i = 0; i < count; i++) + { + entries[i] = new HashEntry("Item:" + i, i); + } + db.HashSet(key, entries, CommandFlags.FireAndForget); + + var found = false; + var response = db.HashScan(key); + var cursor = (IScanningCursor)response; + foreach (var _ in response) + { + if (cursor.Cursor > 0) + { + found = true; + } + } + return found; + } + + [Theory] + [InlineData(10)] + [InlineData(100)] + [InlineData(1000)] + [InlineData(10000)] + public async Task SetScanLarge(int pageSize) + { + await using var conn = Create(); + + RedisKey key = Me() + pageSize; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + for (int i = 0; i < 2000; i++) + db.SetAdd(key, "s" + i, flags: CommandFlags.FireAndForget); + + int count = db.SetScan(key, pageSize: pageSize).Count(); + Assert.Equal(2000, count); + } + + [Theory] + [InlineData(10)] + [InlineData(100)] + [InlineData(1000)] + [InlineData(10000)] + public async Task SortedSetScanLarge(int pageSize) + { + await using var conn = Create(); + + RedisKey key = Me() + pageSize; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + for (int i = 0; i < 2000; i++) + db.SortedSetAdd(key, "z" + i, i, flags: CommandFlags.FireAndForget); + + int count = db.SortedSetScan(key, pageSize: pageSize).Count(); + Assert.Equal(2000, count); + } +} diff --git a/tests/StackExchange.Redis.Tests/ScriptingTests.cs b/tests/StackExchange.Redis.Tests/ScriptingTests.cs new file mode 100644 index 000000000..15ea6adb1 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ScriptingTests.cs @@ -0,0 +1,1156 @@ +#if NET // Since we're flushing and reloading scripts, only run this in once suite +using System; +using System.Diagnostics; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using StackExchange.Redis.KeyspaceIsolation; +using Xunit; + +// ReSharper disable UseAwaitUsing # for consistency with existing tests +// ReSharper disable MethodHasAsyncOverload # grandfathered existing usage +// ReSharper disable StringLiteralTypo # because of Lua scripts +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class ScriptingTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + private IConnectionMultiplexer GetScriptConn(bool allowAdmin = false) + { + int syncTimeout = 5000; + if (Debugger.IsAttached) syncTimeout = 500000; + return Create(allowAdmin: allowAdmin, syncTimeout: syncTimeout, require: RedisFeatures.v2_6_0); + } + + [Fact] + public async Task ClientScripting() + { + await using var conn = GetScriptConn(); + _ = conn.GetDatabase().ScriptEvaluate(script: "return redis.call('info','server')", keys: null, values: null); + } + + [Fact] + public async Task BasicScripting() + { + await using var conn = GetScriptConn(); + + var db = conn.GetDatabase(); + var noCache = db.ScriptEvaluateAsync( + script: "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", + keys: ["key1", "key2"], + values: ["first", "second"]); + var cache = db.ScriptEvaluateAsync( + script: "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", + keys: ["key1", "key2"], + values: ["first", "second"]); + var results = (string[]?)(await noCache)!; + Assert.NotNull(results); + Assert.Equal(4, results.Length); + Assert.Equal("key1", results[0]); + Assert.Equal("key2", results[1]); + Assert.Equal("first", results[2]); + Assert.Equal("second", results[3]); + + results = (string[]?)(await cache)!; + Assert.NotNull(results); + Assert.Equal(4, results.Length); + Assert.Equal("key1", results[0]); + Assert.Equal("key2", results[1]); + Assert.Equal("first", results[2]); + Assert.Equal("second", results[3]); + } + + [Fact] + public async Task KeysScripting() + { + await using var conn = GetScriptConn(); + + var db = conn.GetDatabase(); + var key = Me(); + db.StringSet(key, "bar", flags: CommandFlags.FireAndForget); + var result = (string?)db.ScriptEvaluate(script: "return redis.call('get', KEYS[1])", keys: [key], values: null); + Assert.Equal("bar", result); + } + + [Fact] + public async Task TestRandomThingFromForum() + { + const string Script = """ + local currentVal = tonumber(redis.call('GET', KEYS[1])); + if (currentVal <= 0 ) then return 1 elseif (currentVal - (tonumber(ARGV[1])) < 0 ) then return 0 end; + return redis.call('INCRBY', KEYS[1], -tonumber(ARGV[1])); + """; + + await using var conn = GetScriptConn(); + + var prefix = Me(); + var db = conn.GetDatabase(); + db.StringSet(prefix + "A", "0", flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "B", "5", flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "C", "10", flags: CommandFlags.FireAndForget); + + var a = db.ScriptEvaluateAsync(script: Script, keys: [prefix + "A"], values: [6]).ForAwait(); + var b = db.ScriptEvaluateAsync(script: Script, keys: [prefix + "B"], values: [6]).ForAwait(); + var c = db.ScriptEvaluateAsync(script: Script, keys: [prefix + "C"], values: [6]).ForAwait(); + + var values = await db.StringGetAsync([prefix + "A", prefix + "B", prefix + "C"]).ForAwait(); + + Assert.Equal(1, (long)await a); // exit code when current val is non-positive + Assert.Equal(0, (long)await b); // exit code when result would be negative + Assert.Equal(4, (long)await c); // 10 - 6 = 4 + Assert.Equal("0", values[0]); + Assert.Equal("5", values[1]); + Assert.Equal("4", values[2]); + } + + [Fact] + public async Task MultiIncrWithoutReplies() + { + await using var conn = GetScriptConn(); + + var db = conn.GetDatabase(); + var prefix = Me(); + // prime some initial values + db.KeyDelete([prefix + "a", prefix + "b", prefix + "c"], CommandFlags.FireAndForget); + db.StringIncrement(prefix + "b", flags: CommandFlags.FireAndForget); + db.StringIncrement(prefix + "c", flags: CommandFlags.FireAndForget); + db.StringIncrement(prefix + "c", flags: CommandFlags.FireAndForget); + + // run the script, passing "a", "b", "c", "c" to + // increment a & b by 1, c twice + var result = db.ScriptEvaluateAsync( + script: "for i,key in ipairs(KEYS) do redis.call('incr', key) end", + keys: [prefix + "a", prefix + "b", prefix + "c", prefix + "c"], // <== aka "KEYS" in the script + values: null).ForAwait(); // <== aka "ARGV" in the script + + // check the incremented values + var a = db.StringGetAsync(prefix + "a").ForAwait(); + var b = db.StringGetAsync(prefix + "b").ForAwait(); + var c = db.StringGetAsync(prefix + "c").ForAwait(); + + var r = await result; + Assert.NotNull(r); + Assert.True(r.IsNull, "result"); + Assert.Equal(1, (long)await a); + Assert.Equal(2, (long)await b); + Assert.Equal(4, (long)await c); + } + + [Fact] + public async Task MultiIncrByWithoutReplies() + { + await using var conn = GetScriptConn(); + + var db = conn.GetDatabase(); + var prefix = Me(); + // prime some initial values + db.KeyDelete([prefix + "a", prefix + "b", prefix + "c"], CommandFlags.FireAndForget); + db.StringIncrement(prefix + "b", flags: CommandFlags.FireAndForget); + db.StringIncrement(prefix + "c", flags: CommandFlags.FireAndForget); + db.StringIncrement(prefix + "c", flags: CommandFlags.FireAndForget); + + // run the script, passing "a", "b", "c" and 1,2,3 + // increment a & b by 1, c twice + var result = db.ScriptEvaluateAsync( + script: "for i,key in ipairs(KEYS) do redis.call('incrby', key, ARGV[i]) end", + keys: [prefix + "a", prefix + "b", prefix + "c"], // <== aka "KEYS" in the script + values: [1, 1, 2]).ForAwait(); // <== aka "ARGV" in the script + + // check the incremented values + var a = db.StringGetAsync(prefix + "a").ForAwait(); + var b = db.StringGetAsync(prefix + "b").ForAwait(); + var c = db.StringGetAsync(prefix + "c").ForAwait(); + + Assert.True((await result).IsNull, "result"); + Assert.Equal(1, (long)await a); + Assert.Equal(2, (long)await b); + Assert.Equal(4, (long)await c); + } + + [Fact] + public async Task DisableStringInference() + { + await using var conn = GetScriptConn(); + + var db = conn.GetDatabase(); + var key = Me(); + db.StringSet(key, "bar", flags: CommandFlags.FireAndForget); + var result = (byte[]?)db.ScriptEvaluate(script: "return redis.call('get', KEYS[1])", keys: [key]); + Assert.NotNull(result); + Assert.Equal("bar", Encoding.UTF8.GetString(result)); + } + + [Fact] + public async Task FlushDetection() + { + // we don't expect this to handle everything; we just expect it to be predictable + await using var conn = GetScriptConn(allowAdmin: true); + + var db = conn.GetDatabase(); + var key = Me(); + db.StringSet(key, "bar", flags: CommandFlags.FireAndForget); + var result = (string?)db.ScriptEvaluate(script: "return redis.call('get', KEYS[1])", keys: [key], values: null); + Assert.Equal("bar", result); + + // now cause all kinds of problems + GetServer(conn).ScriptFlush(); + + // expect this one to fail just work fine (self-fix) + db.ScriptEvaluate(script: "return redis.call('get', KEYS[1])", keys: [key], values: null); + + result = (string?)db.ScriptEvaluate(script: "return redis.call('get', KEYS[1])", keys: [key], values: null); + Assert.Equal("bar", result); + } + + [Fact] + public async Task PrepareScript() + { + string[] scripts = ["return redis.call('get', KEYS[1])", "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"]; + await using (var conn = GetScriptConn(allowAdmin: true)) + { + var server = GetServer(conn); + server.ScriptFlush(); + + // when vanilla + server.ScriptLoad(scripts[0]); + server.ScriptLoad(scripts[1]); + + // when known to exist + server.ScriptLoad(scripts[0]); + server.ScriptLoad(scripts[1]); + } + await using (var conn = GetScriptConn()) + { + var server = GetServer(conn); + + // when vanilla + server.ScriptLoad(scripts[0]); + server.ScriptLoad(scripts[1]); + + // when known to exist + server.ScriptLoad(scripts[0]); + server.ScriptLoad(scripts[1]); + + // when known to exist + server.ScriptLoad(scripts[0]); + server.ScriptLoad(scripts[1]); + } + } + + [Fact] + public async Task NonAsciiScripts() + { + await using var conn = GetScriptConn(); + + const string Evil = "return '僕'"; + var db = conn.GetDatabase(); + GetServer(conn).ScriptLoad(Evil); + + var result = (string?)db.ScriptEvaluate(script: Evil, keys: null, values: null); + Assert.Equal("僕", result); + } + + [Fact] + public async Task ScriptThrowsError() + { + await using var conn = GetScriptConn(); + await Assert.ThrowsAsync(async () => + { + var db = conn.GetDatabase(); + try + { + await db.ScriptEvaluateAsync(script: "return redis.error_reply('oops')", keys: null, values: null).ForAwait(); + } + catch (AggregateException ex) + { + throw ex.InnerExceptions[0]; + } + }).ForAwait(); + } + + [Fact] + public async Task ScriptThrowsErrorInsideTransaction() + { + await using var conn = GetScriptConn(); + + var key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + var beforeTran = (string?)db.StringGet(key); + Assert.Null(beforeTran); + var tran = db.CreateTransaction(); + { + var a = tran.StringIncrementAsync(key); + var b = tran.ScriptEvaluateAsync(script: "return redis.error_reply('oops')", keys: null, values: null); + var c = tran.StringIncrementAsync(key); + var complete = tran.ExecuteAsync(); + + Assert.True(conn.Wait(complete)); + Assert.True(QuickWait(a).IsCompleted, a.Status.ToString()); + Assert.True(QuickWait(c).IsCompleted, "State: " + c.Status); + Assert.Equal(1L, a.Result); + Assert.Equal(2L, c.Result); + + Assert.True(QuickWait(b).IsFaulted, "should be faulted"); + Assert.NotNull(b.Exception); + Assert.Single(b.Exception.InnerExceptions); + var ex = b.Exception.InnerExceptions.Single(); + Assert.IsType(ex); + // 7.0 slightly changes the error format, accept either. + Assert.Contains(ex.Message, new[] { "ERR oops", "oops" }); + } + var afterTran = db.StringGetAsync(key); + Assert.Equal(2L, (long)db.Wait(afterTran)); + } + private static Task QuickWait(Task task) + { + if (!task.IsCompleted) + { + try { task.Wait(200); } catch { /* But don't error */ } + } + return task; + } + + [Fact] + public async Task ChangeDbInScript() + { + await using var conn = GetScriptConn(); + + var key = Me(); + conn.GetDatabase(1).StringSet(key, "db 1", flags: CommandFlags.FireAndForget); + conn.GetDatabase(2).StringSet(key, "db 2", flags: CommandFlags.FireAndForget); + + Log("Key: " + key); + var db = conn.GetDatabase(2); + var evalResult = db.ScriptEvaluateAsync( + script: @"redis.call('select', 1) + return redis.call('get','" + key + "')", + keys: null, + values: null); + var getResult = db.StringGetAsync(key); + + Assert.Equal("db 1", (string?)await evalResult); + // now, our connection thought it was in db 2, but the script changed to db 1 + Assert.Equal("db 2", await getResult); + } + + [Fact] + public async Task ChangeDbInTranScript() + { + await using var conn = GetScriptConn(); + + var key = Me(); + conn.GetDatabase(1).StringSet(key, "db 1", flags: CommandFlags.FireAndForget); + conn.GetDatabase(2).StringSet(key, "db 2", flags: CommandFlags.FireAndForget); + + var db = conn.GetDatabase(2); + var tran = db.CreateTransaction(); + var evalResult = tran.ScriptEvaluateAsync( + script: @"redis.call('select', 1) + return redis.call('get','" + key + "')", + keys: null, + values: null); + var getResult = tran.StringGetAsync(key); + Assert.True(tran.Execute()); + + Assert.Equal("db 1", (string?)await evalResult); + // now, our connection thought it was in db 2, but the script changed to db 1 + Assert.Equal("db 2", await getResult); + } + + [Fact] + public async Task TestBasicScripting() + { + await using var conn = Create(require: RedisFeatures.v2_6_0); + + RedisValue newId = Guid.NewGuid().ToString(); + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.HashSet(key, "id", 123, flags: CommandFlags.FireAndForget); + + var wasSet = (bool)db.ScriptEvaluate( + script: "if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end", + keys: [key], + values: [newId]); + + Assert.True(wasSet); + + wasSet = (bool)db.ScriptEvaluate( + script: "if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end", + keys: [key], + values: [newId]); + Assert.False(wasSet); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CheckLoads(bool async) + { + await using var conn0 = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + await using var conn1 = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + // note that these are on different connections (so we wouldn't expect + // the flush to drop the local cache - assume it is a surprise!) + var server = conn0.GetServer(TestConfig.Current.PrimaryServerAndPort); + var db = conn1.GetDatabase(); + var key = Me(); + var Script = $"return '{key}';"; + + // start empty + server.ScriptFlush(); + Assert.False(server.ScriptExists(Script)); + + // run once, causes to be cached + Assert.Equal(key, await EvaluateScript()); + + Assert.True(server.ScriptExists(Script)); + + // can run again + Assert.Equal(key, await EvaluateScript()); + + // ditch the scripts; should no longer exist + await db.PingAsync(); + server.ScriptFlush(); + Assert.False(server.ScriptExists(Script)); + await db.PingAsync(); + + // just works; magic + Assert.Equal(key, await EvaluateScript()); + + // but gets marked as unloaded, so we can use it again... + Assert.Equal(key, await EvaluateScript()); + + // which will cause it to be cached + Assert.True(server.ScriptExists(Script)); + + async Task EvaluateScript() + { + return async ? + (string?)await db.ScriptEvaluateAsync(script: Script) : + (string?)db.ScriptEvaluate(script: Script); + } + } + + [Fact] + public async Task CompareScriptToDirect() + { + Skip.UnlessLongRunning(); + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "return redis.call('incr', KEYS[1])"; + var server = conn.GetServer(TestConfig.Current.PrimaryServerAndPort); + server.ScriptFlush(); + + server.ScriptLoad(Script); + var db = conn.GetDatabase(); + await db.PingAsync(); // k, we're all up to date now; clean db, minimal script cache + + // we're using a pipeline here, so send 1000 messages, but for timing: only care about the last + const int Loop = 5000; + RedisKey key = Me(); + RedisKey[] keys = [key]; // script takes an array + + // run via script + db.KeyDelete(key, CommandFlags.FireAndForget); + var watch = Stopwatch.StartNew(); + for (int i = 1; i < Loop; i++) // the i=1 is to do all-but-one + { + db.ScriptEvaluate(script: Script, keys: keys, flags: CommandFlags.FireAndForget); + } + var scriptResult = db.ScriptEvaluate(script: Script, keys: keys); // last one we wait for (no F+F) + watch.Stop(); + TimeSpan scriptTime = watch.Elapsed; + + // run via raw op + db.KeyDelete(key, CommandFlags.FireAndForget); + watch = Stopwatch.StartNew(); + for (int i = 1; i < Loop; i++) // the i=1 is to do all-but-one + { + db.StringIncrement(key, flags: CommandFlags.FireAndForget); + } + var directResult = db.StringIncrement(key); // last one we wait for (no F+F) + watch.Stop(); + TimeSpan directTime = watch.Elapsed; + + Assert.Equal(Loop, (long)scriptResult); + Assert.Equal(Loop, directResult); + + Log("script: {0}ms; direct: {1}ms", scriptTime.TotalMilliseconds, directTime.TotalMilliseconds); + } + + [Fact] + public async Task TestCallByHash() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "return redis.call('incr', KEYS[1])"; + var server = conn.GetServer(TestConfig.Current.PrimaryServerAndPort); + server.ScriptFlush(); + + byte[] hash = server.ScriptLoad(Script); + Assert.NotNull(hash); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + RedisKey[] keys = [key]; + + string hexHash = string.Concat(hash.Select(x => x.ToString("X2"))); + Assert.Equal("2BAB3B661081DB58BD2341920E0BA7CF5DC77B25", hexHash); + + db.ScriptEvaluate(script: hexHash, keys: keys, flags: CommandFlags.FireAndForget); + db.ScriptEvaluate(hash, keys, flags: CommandFlags.FireAndForget); + + var count = (int)db.StringGet(keys)[0]; + Assert.Equal(2, count); + } + + [Fact] + public async Task SimpleLuaScript() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "return @ident"; + var server = conn.GetServer(TestConfig.Current.PrimaryServerAndPort); + server.ScriptFlush(); + + var prepared = LuaScript.Prepare(Script); + + var db = conn.GetDatabase(); + + // Scopes for repeated use + { + var val = prepared.Evaluate(db, new { ident = "hello" }); + Assert.Equal("hello", (string?)val); + } + + { + var val = prepared.Evaluate(db, new { ident = 123 }); + Assert.Equal(123, (int)val); + } + + { + var val = prepared.Evaluate(db, new { ident = 123L }); + Assert.Equal(123L, (long)val); + } + + { + var val = prepared.Evaluate(db, new { ident = 1.1 }); + Assert.Equal(1.1, (double)val); + } + + { + var val = prepared.Evaluate(db, new { ident = true }); + Assert.True((bool)val); + } + + { + var val = prepared.Evaluate(db, new { ident = new byte[] { 4, 5, 6 } }); + var valArray = (byte[]?)val; + Assert.NotNull(valArray); + Assert.True(new byte[] { 4, 5, 6 }.SequenceEqual(valArray)); + } + + { + var val = prepared.Evaluate(db, new { ident = new ReadOnlyMemory([4, 5, 6]) }); + var valArray = (byte[]?)val; + Assert.NotNull(valArray); + Assert.True(new byte[] { 4, 5, 6 }.SequenceEqual(valArray)); + } + } + + [Fact] + public async Task SimpleRawScriptEvaluate() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "return ARGV[1]"; + var server = conn.GetServer(TestConfig.Current.PrimaryServerAndPort); + server.ScriptFlush(); + + var db = conn.GetDatabase(); + + // Scopes for repeated use + { + var val = db.ScriptEvaluate(script: Script, values: ["hello"]); + Assert.Equal("hello", (string?)val); + } + + { + var val = db.ScriptEvaluate(script: Script, values: [123]); + Assert.Equal(123, (int)val); + } + + { + var val = db.ScriptEvaluate(script: Script, values: [123L]); + Assert.Equal(123L, (long)val); + } + + { + var val = db.ScriptEvaluate(script: Script, values: [1.1]); + Assert.Equal(1.1, (double)val); + } + + { + var val = db.ScriptEvaluate(script: Script, values: [true]); + Assert.True((bool)val); + } + + { + var val = db.ScriptEvaluate(script: Script, values: [new byte[] { 4, 5, 6 }]); + var valArray = (byte[]?)val; + Assert.NotNull(valArray); + Assert.True(new byte[] { 4, 5, 6 }.SequenceEqual(valArray)); + } + + { + var val = db.ScriptEvaluate(script: Script, values: [new ReadOnlyMemory([4, 5, 6])]); + var valArray = (byte[]?)val; + Assert.NotNull(valArray); + Assert.True(new byte[] { 4, 5, 6 }.SequenceEqual(valArray)); + } + } + + [Fact] + public async Task LuaScriptWithKeys() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "redis.call('set', @key, @value)"; + var server = conn.GetServer(TestConfig.Current.PrimaryServerAndPort); + server.ScriptFlush(); + + var script = LuaScript.Prepare(Script); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var p = new { key = (RedisKey)key, value = 123 }; + + script.Evaluate(db, p); + var val = db.StringGet(key); + Assert.Equal(123, (int)val); + + // no super clean way to extract this; so just abuse InternalsVisibleTo + script.ExtractParameters(p, null, out RedisKey[]? keys, out _); + Assert.NotNull(keys); + Assert.Single(keys); + Assert.Equal(key, keys[0]); + } + + [Fact] + public async Task NoInlineReplacement() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "redis.call('set', @key, 'hello@example')"; + var server = conn.GetServer(TestConfig.Current.PrimaryServerAndPort); + server.ScriptFlush(); + + var script = LuaScript.Prepare(Script); + + Assert.Equal("redis.call('set', ARGV[1], 'hello@example')", script.ExecutableScript); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var p = new { key }; + + script.Evaluate(db, p, flags: CommandFlags.FireAndForget); + var val = db.StringGet(key); + Assert.Equal("hello@example", val); + } + + [Fact] + public void EscapeReplacement() + { + const string Script = "redis.call('set', @key, @@escapeMe)"; + var script = LuaScript.Prepare(Script); + + Assert.Equal("redis.call('set', ARGV[1], @escapeMe)", script.ExecutableScript); + } + + [Fact] + public async Task SimpleLoadedLuaScript() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "return @ident"; + var server = conn.GetServer(TestConfig.Current.PrimaryServerAndPort); + server.ScriptFlush(); + + var prepared = LuaScript.Prepare(Script); + var loaded = prepared.Load(server); + + var db = conn.GetDatabase(); + + // Scopes for repeated use + { + var val = loaded.Evaluate(db, new { ident = "hello" }); + Assert.Equal("hello", (string?)val); + } + + { + var val = loaded.Evaluate(db, new { ident = 123 }); + Assert.Equal(123, (int)val); + } + + { + var val = loaded.Evaluate(db, new { ident = 123L }); + Assert.Equal(123L, (long)val); + } + + { + var val = loaded.Evaluate(db, new { ident = 1.1 }); + Assert.Equal(1.1, (double)val); + } + + { + var val = loaded.Evaluate(db, new { ident = true }); + Assert.True((bool)val); + } + + { + var val = loaded.Evaluate(db, new { ident = new byte[] { 4, 5, 6 } }); + var valArray = (byte[]?)val; + Assert.NotNull(valArray); + Assert.True(new byte[] { 4, 5, 6 }.SequenceEqual(valArray)); + } + + { + var val = loaded.Evaluate(db, new { ident = new ReadOnlyMemory([4, 5, 6]) }); + var valArray = (byte[]?)val; + Assert.NotNull(valArray); + Assert.True(new byte[] { 4, 5, 6 }.SequenceEqual(valArray)); + } + } + + [Fact] + public async Task LoadedLuaScriptWithKeys() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "redis.call('set', @key, @value)"; + var server = conn.GetServer(TestConfig.Current.PrimaryServerAndPort); + server.ScriptFlush(); + + var script = LuaScript.Prepare(Script); + var prepared = script.Load(server); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var p = new { key = (RedisKey)key, value = 123 }; + + prepared.Evaluate(db, p, flags: CommandFlags.FireAndForget); + var val = db.StringGet(key); + Assert.Equal(123, (int)val); + + // no super clean way to extract this; so just abuse InternalsVisibleTo + prepared.Original.ExtractParameters(p, null, out RedisKey[]? keys, out _); + Assert.NotNull(keys); + Assert.Single(keys); + Assert.Equal(key, keys[0]); + } + + [Fact] + public void PurgeLuaScriptCache() + { + const string Script = "redis.call('set', @PurgeLuaScriptCacheKey, @PurgeLuaScriptCacheValue)"; + var first = LuaScript.Prepare(Script); + var fromCache = LuaScript.Prepare(Script); + + Assert.True(ReferenceEquals(first, fromCache)); + + LuaScript.PurgeCache(); + var shouldBeNew = LuaScript.Prepare(Script); + + Assert.False(ReferenceEquals(first, shouldBeNew)); + } + + private static void PurgeLuaScriptOnFinalizeImpl(string script) + { + var first = LuaScript.Prepare(script); + var fromCache = LuaScript.Prepare(script); + Assert.True(ReferenceEquals(first, fromCache)); + Assert.Equal(1, LuaScript.GetCachedScriptCount()); + } + + [Fact] + public void PurgeLuaScriptOnFinalize() + { + Skip.UnlessLongRunning(); + const string Script = "redis.call('set', @PurgeLuaScriptOnFinalizeKey, @PurgeLuaScriptOnFinalizeValue)"; + LuaScript.PurgeCache(); + Assert.Equal(0, LuaScript.GetCachedScriptCount()); + + // This has to be a separate method to guarantee that the created LuaScript objects go out of scope, + // and are thus available to be garbage collected. + PurgeLuaScriptOnFinalizeImpl(Script); + CollectGarbage(); + + Assert.Equal(0, LuaScript.GetCachedScriptCount()); + + LuaScript.Prepare(Script); + Assert.Equal(1, LuaScript.GetCachedScriptCount()); + } + + [Fact] + public async Task DatabaseLuaScriptConvenienceMethods() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "redis.call('set', @key, @value)"; + var script = LuaScript.Prepare(Script); + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.ScriptEvaluate(script, new { key = (RedisKey)key, value = "value" }); + var val = db.StringGet(key); + Assert.Equal("value", val); + + var prepared = script.Load(conn.GetServer(conn.GetEndPoints()[0])); + + db.ScriptEvaluate(prepared, new { key = (RedisKey)(key + "2"), value = "value2" }); + var val2 = db.StringGet(key + "2"); + Assert.Equal("value2", val2); + } + + [Fact] + public async Task ServerLuaScriptConvenienceMethods() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "redis.call('set', @key, @value)"; + var script = LuaScript.Prepare(Script); + var server = conn.GetServer(conn.GetEndPoints()[0]); + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var prepared = server.ScriptLoad(script); + + db.ScriptEvaluate(prepared, new { key = (RedisKey)key, value = "value3" }); + var val = db.StringGet(key); + Assert.Equal("value3", val); + } + + [Fact] + public void LuaScriptPrefixedKeys() + { + const string Script = "redis.call('set', @key, @value)"; + var prepared = LuaScript.Prepare(Script); + var key = Me(); + var p = new { key = (RedisKey)key, value = "hello" }; + + // no super clean way to extract this; so just abuse InternalsVisibleTo + prepared.ExtractParameters(p, "prefix-", out RedisKey[]? keys, out RedisValue[]? args); + Assert.NotNull(keys); + Assert.Single(keys); + Assert.Equal("prefix-" + key, keys[0]); + Assert.NotNull(args); + Assert.Equal(2, args.Length); + Assert.Equal("prefix-" + key, args[0]); + Assert.Equal("hello", args[1]); + } + + [Fact] + public async Task LuaScriptWithWrappedDatabase() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "redis.call('set', @key, @value)"; + var db = conn.GetDatabase(); + var wrappedDb = db.WithKeyPrefix("prefix-"); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var prepared = LuaScript.Prepare(Script); + wrappedDb.ScriptEvaluate(prepared, new { key = (RedisKey)key, value = 123 }); + var val1 = wrappedDb.StringGet(key); + Assert.Equal(123, (int)val1); + + var val2 = db.StringGet("prefix-" + key); + Assert.Equal(123, (int)val2); + + var val3 = db.StringGet(key); + Assert.True(val3.IsNull); + } + + [Fact] + public async Task AsyncLuaScriptWithWrappedDatabase() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "redis.call('set', @key, @value)"; + var db = conn.GetDatabase(); + var wrappedDb = db.WithKeyPrefix("prefix-"); + var key = Me(); + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var prepared = LuaScript.Prepare(Script); + await wrappedDb.ScriptEvaluateAsync(prepared, new { key = (RedisKey)key, value = 123 }); + var val1 = await wrappedDb.StringGetAsync(key); + Assert.Equal(123, (int)val1); + + var val2 = await db.StringGetAsync("prefix-" + key); + Assert.Equal(123, (int)val2); + + var val3 = await db.StringGetAsync(key); + Assert.True(val3.IsNull); + } + + [Fact] + public async Task LoadedLuaScriptWithWrappedDatabase() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "redis.call('set', @key, @value)"; + var db = conn.GetDatabase(); + var wrappedDb = db.WithKeyPrefix("prefix2-"); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + var prepared = LuaScript.Prepare(Script).Load(server); + wrappedDb.ScriptEvaluate(prepared, new { key = (RedisKey)key, value = 123 }, flags: CommandFlags.FireAndForget); + var val1 = wrappedDb.StringGet(key); + Assert.Equal(123, (int)val1); + + var val2 = db.StringGet("prefix2-" + key); + Assert.Equal(123, (int)val2); + + var val3 = db.StringGet(key); + Assert.True(val3.IsNull); + } + + [Fact] + public async Task AsyncLoadedLuaScriptWithWrappedDatabase() + { + await using var conn = Create(allowAdmin: true, require: RedisFeatures.v2_6_0); + + const string Script = "redis.call('set', @key, @value)"; + var db = conn.GetDatabase(); + var wrappedDb = db.WithKeyPrefix("prefix2-"); + var key = Me(); + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var server = conn.GetServer(conn.GetEndPoints()[0]); + var prepared = await LuaScript.Prepare(Script).LoadAsync(server); + await wrappedDb.ScriptEvaluateAsync(prepared, new { key = (RedisKey)key, value = 123 }, flags: CommandFlags.FireAndForget); + var val1 = await wrappedDb.StringGetAsync(key); + Assert.Equal(123, (int)val1); + + var val2 = await db.StringGetAsync("prefix2-" + key); + Assert.Equal(123, (int)val2); + + var val3 = await db.StringGetAsync(key); + Assert.True(val3.IsNull); + } + + [Fact] + public async Task ScriptWithKeyPrefixViaTokens() + { + await using var conn = Create(); + + var p = conn.GetDatabase().WithKeyPrefix("prefix/"); + + var args = new { x = "abc", y = (RedisKey)"def", z = 123 }; + var script = LuaScript.Prepare(@" +local arr = {}; +arr[1] = @x; +arr[2] = @y; +arr[3] = @z; +return arr; +"); + var result = (RedisValue[]?)p.ScriptEvaluate(script, args); + Assert.NotNull(result); + Assert.Equal("abc", result[0]); + Assert.Equal("prefix/def", result[1]); + Assert.Equal("123", result[2]); + } + + [Fact] + public async Task ScriptWithKeyPrefixViaArrays() + { + await using var conn = Create(); + + var p = conn.GetDatabase().WithKeyPrefix("prefix/"); + + const string Script = @" +local arr = {}; +arr[1] = ARGV[1]; +arr[2] = KEYS[1]; +arr[3] = ARGV[2]; +return arr; +"; + var result = (RedisValue[]?)p.ScriptEvaluate(script: Script, keys: ["def"], values: ["abc", 123]); + Assert.NotNull(result); + Assert.Equal("abc", result[0]); + Assert.Equal("prefix/def", result[1]); + Assert.Equal("123", result[2]); + } + + [Fact] + public async Task ScriptWithKeyPrefixCompare() + { + await using var conn = Create(); + + var p = conn.GetDatabase().WithKeyPrefix("prefix/"); + var args = new { k = (RedisKey)"key", s = "str", v = 123 }; + LuaScript lua = LuaScript.Prepare("return {@k, @s, @v}"); + var viaArgs = (RedisValue[]?)p.ScriptEvaluate(lua, args); + + var viaArr = (RedisValue[]?)p.ScriptEvaluate(script: "return {KEYS[1], ARGV[1], ARGV[2]}", keys: [args.k], values: [args.s, args.v]); + Assert.NotNull(viaArr); + Assert.NotNull(viaArgs); + Assert.Equal(string.Join(",", viaArr), string.Join(",", viaArgs)); + } + + [Fact] + public void RedisResultUnderstandsNullArrayArray() => TestNullArray(RedisResult.NullArray); + + [Fact] + public void RedisResultUnderstandsNullArrayNull() => TestNullArray(null); + + [Theory] + [InlineData(null, false)] + [InlineData("", false)] + [InlineData("829c3804401b0727f70f73d4415e162400cbe57b", true)] + [InlineData("$29c3804401b0727f70f73d4415e162400cbe57b", false)] + [InlineData("829c3804401b0727f70f73d4415e162400cbe57", false)] + [InlineData("829c3804401b0727f70f73d4415e162400cbe57bb", false)] + public void Sha1Detection(string? candidate, bool isSha) + { + Assert.Equal(isSha, ResultProcessor.ScriptLoadProcessor.IsSHA1(candidate)); + } + + private static void TestNullArray(RedisResult? value) + { + Assert.True(value == null || value.IsNull); + + Assert.Null((RedisValue[]?)value); + Assert.Null((RedisKey[]?)value); + Assert.Null((bool[]?)value); + Assert.Null((long[]?)value); + Assert.Null((ulong[]?)value); + Assert.Null((string[]?)value!); + Assert.Null((int[]?)value); + Assert.Null((double[]?)value); + Assert.Null((byte[][]?)value!); + Assert.Null((RedisResult[]?)value); + } + + [Fact] + public void RedisResultUnderstandsNullNull() => TestNullValue(null); + [Fact] + public void RedisResultUnderstandsNullValue() => TestNullValue(RedisResult.Create(RedisValue.Null, ResultType.None)); + + [Fact] + public async Task TestEvalReadonly() + { + await using var conn = GetScriptConn(); + var db = conn.GetDatabase(); + + string script = "return KEYS[1]"; + RedisKey[] keys = ["key1"]; + RedisValue[] values = ["first"]; + + var result = db.ScriptEvaluateReadOnly(script, keys, values); + Assert.Equal("key1", result.ToString()); + } + + [Fact] + public async Task TestEvalReadonlyAsync() + { + await using var conn = GetScriptConn(); + var db = conn.GetDatabase(); + + string script = "return KEYS[1]"; + RedisKey[] keys = ["key1"]; + RedisValue[] values = ["first"]; + + var result = await db.ScriptEvaluateReadOnlyAsync(script, keys, values); + Assert.Equal("key1", result.ToString()); + } + + [Fact] + public async Task TestEvalShaReadOnly() + { + await using var conn = GetScriptConn(); + var db = conn.GetDatabase(); + var key = Me(); + var script = $"return redis.call('get','{key}')"; + db.StringSet(key, "bar"); + db.ScriptEvaluate(script: script); + + SHA1 sha1Hash = SHA1.Create(); + byte[] hash = sha1Hash.ComputeHash(Encoding.UTF8.GetBytes(script)); + Log("Hash: " + Convert.ToBase64String(hash)); + var result = db.ScriptEvaluateReadOnly(hash); + + Assert.Equal("bar", result.ToString()); + } + + [Fact] + public async Task TestEvalShaReadOnlyAsync() + { + await using var conn = GetScriptConn(); + var db = conn.GetDatabase(); + var key = Me(); + var script = $"return redis.call('get','{key}')"; + db.StringSet(key, "bar"); + db.ScriptEvaluate(script: script); + + SHA1 sha1Hash = SHA1.Create(); + byte[] hash = sha1Hash.ComputeHash(Encoding.UTF8.GetBytes(script)); + Log("Hash: " + Convert.ToBase64String(hash)); + var result = await db.ScriptEvaluateReadOnlyAsync(hash); + + Assert.Equal("bar", result.ToString()); + } + + [Fact, TestCulture("en-US")] + public void LuaScriptEnglishParameters() => LuaScriptParameterShared(); + + [Fact, TestCulture("tr-TR")] + public void LuaScriptTurkishParameters() => LuaScriptParameterShared(); + + private void LuaScriptParameterShared() + { + const string Script = "redis.call('set', @key, @testIId)"; + var prepared = LuaScript.Prepare(Script); + var key = Me(); + var p = new { key = (RedisKey)key, testIId = "hello" }; + + prepared.ExtractParameters(p, null, out RedisKey[]? keys, out RedisValue[]? args); + Assert.NotNull(keys); + Assert.Single(keys); + Assert.Equal(key, keys[0]); + Assert.NotNull(args); + Assert.Equal(2, args.Length); + Assert.Equal(key, args[0]); + Assert.Equal("hello", args[1]); + } + + private static void TestNullValue(RedisResult? value) + { + Assert.True(value == null || value.IsNull); + + Assert.True(((RedisValue)value).IsNull); + Assert.True(((RedisKey)value).IsNull); + Assert.Null((bool?)value); + Assert.Null((long?)value); + Assert.Null((ulong?)value); + Assert.Null((string?)value); + Assert.Null((double?)value); + Assert.Null((byte[]?)value); + } +} +#endif diff --git a/tests/StackExchange.Redis.Tests/SecureTests.cs b/tests/StackExchange.Redis.Tests/SecureTests.cs new file mode 100644 index 000000000..8f90e04ba --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SecureTests.cs @@ -0,0 +1,89 @@ +using System.Diagnostics; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class SecureTests(ITestOutputHelper output) : TestBase(output) +{ + protected override string GetConfiguration() => + TestConfig.Current.SecureServerAndPort + ",password=" + TestConfig.Current.SecurePassword + ",name=MyClient"; + + [Fact] + public async Task MassiveBulkOpsFireAndForgetSecure() + { + await using var conn = Create(); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + await db.PingAsync(); + + var watch = Stopwatch.StartNew(); + + for (int i = 0; i <= AsyncOpsQty; i++) + { + db.StringSet(key, i, flags: CommandFlags.FireAndForget); + } + int val = (int)db.StringGet(key); + Assert.Equal(AsyncOpsQty, val); + watch.Stop(); + Log("{2}: Time for {0} ops: {1}ms (any order); ops/s: {3}", AsyncOpsQty, watch.ElapsedMilliseconds, Me(), AsyncOpsQty / watch.Elapsed.TotalSeconds); + } + + [Fact] + public void CheckConfig() + { + var config = ConfigurationOptions.Parse(GetConfiguration()); + foreach (var ep in config.EndPoints) + { + Log(ep.ToString()); + } + Assert.Single(config.EndPoints); + Assert.Equal("changeme", config.Password); + } + + [Fact] + public async Task Connect() + { + await using var conn = Create(); + + await conn.GetDatabase().PingAsync(); + } + + [Theory] + [InlineData("wrong", "WRONGPASS invalid username-password pair or user is disabled.")] + [InlineData("", "NOAUTH Returned - connection has not yet authenticated")] + public async Task ConnectWithWrongPassword(string password, string exepctedMessage) + { + await using var checkConn = Create(); + var checkServer = GetServer(checkConn); + + var config = ConfigurationOptions.Parse(GetConfiguration()); + config.Password = password; + config.ConnectRetry = 0; // we don't want to retry on closed sockets in this case. + config.BacklogPolicy = BacklogPolicy.FailFast; + + var ex = await Assert.ThrowsAsync(async () => + { + SetExpectedAmbientFailureCount(-1); + + await using var conn = await ConnectionMultiplexer.ConnectAsync(config, Writer).ConfigureAwait(false); + + await conn.GetDatabase().PingAsync(); + }).ConfigureAwait(false); + Log($"Exception ({ex.FailureType}): {ex.Message}"); + Assert.Equal(ConnectionFailureType.AuthenticationFailure, ex.FailureType); + Assert.StartsWith("It was not possible to connect to the redis server(s). There was an authentication failure; check that passwords (or client certificates) are configured correctly: (RedisServerException) ", ex.Message); + + // This changed in some version...not sure which. For our purposes, splitting on v3 vs v6+ + if (checkServer.Version.IsAtLeast(RedisFeatures.v6_0_0)) + { + Assert.EndsWith(exepctedMessage, ex.Message); + } + else + { + Assert.EndsWith("NOAUTH Returned - connection has not yet authenticated", ex.Message); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/SentinelBase.cs b/tests/StackExchange.Redis.Tests/SentinelBase.cs new file mode 100644 index 000000000..826b9c613 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SentinelBase.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class SentinelBase : TestBase, IAsyncLifetime +{ + protected static string ServiceName => TestConfig.Current.SentinelSeviceName; + protected static ConfigurationOptions ServiceOptions => new ConfigurationOptions { ServiceName = ServiceName, AllowAdmin = true }; + + protected ConnectionMultiplexer Conn { get; set; } + protected IServer SentinelServerA { get; set; } + protected IServer SentinelServerB { get; set; } + protected IServer SentinelServerC { get; set; } + public IServer[] SentinelsServers { get; set; } + +#nullable disable + public SentinelBase(ITestOutputHelper output) : base(output) + { + Skip.IfNoConfig(nameof(TestConfig.Config.SentinelServer), TestConfig.Current.SentinelServer); + Skip.IfNoConfig(nameof(TestConfig.Config.SentinelSeviceName), TestConfig.Current.SentinelSeviceName); + } +#nullable enable + + public ValueTask DisposeAsync() => default; + + public async ValueTask InitializeAsync() + { + var options = ServiceOptions.Clone(); + options.EndPoints.Add(TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortA); + options.EndPoints.Add(TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortB); + options.EndPoints.Add(TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortC); + Conn = ConnectionMultiplexer.SentinelConnect(options, Writer); + + for (var i = 0; i < 150; i++) + { + await Task.Delay(100).ForAwait(); + if (Conn.IsConnected) + { + await using var checkConn = Conn.GetSentinelMasterConnection(options, Writer); + if (checkConn.IsConnected) + { + break; + } + } + } + Assert.True(Conn.IsConnected); + SentinelServerA = Conn.GetServer(TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortA)!; + SentinelServerB = Conn.GetServer(TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortB)!; + SentinelServerC = Conn.GetServer(TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortC)!; + SentinelsServers = [SentinelServerA, SentinelServerB, SentinelServerC]; + + SentinelServerA.AllowReplicaWrites = true; + // Wait until we are in a state of a single primary and replica + await WaitForReadyAsync(); + } + + // Sometimes it's global, sometimes it's local + // Depends what mood Redis is in but they're equal and not the point of our tests + protected static readonly IpComparer _ipComparer = new IpComparer(); + protected class IpComparer : IEqualityComparer + { + public bool Equals(string? x, string? y) => x == y || x?.Replace("0.0.0.0", "127.0.0.1") == y?.Replace("0.0.0.0", "127.0.0.1"); + public int GetHashCode(string? obj) => obj?.GetHashCode() ?? 0; + } + + protected async Task WaitForReadyAsync(EndPoint? expectedPrimary = null, bool waitForReplication = false, TimeSpan? duration = null) + { + duration ??= TimeSpan.FromSeconds(30); + + var sw = Stopwatch.StartNew(); + + // wait until we have 1 primary and 1 replica and have verified their roles + var primary = SentinelServerA.SentinelGetMasterAddressByName(ServiceName); + if (expectedPrimary != null && expectedPrimary.ToString() != primary?.ToString()) + { + while (sw.Elapsed < duration.Value) + { + await Task.Delay(1000).ForAwait(); + try + { + primary = SentinelServerA.SentinelGetMasterAddressByName(ServiceName); + if (expectedPrimary.ToString() == primary?.ToString()) + break; + } + catch (Exception) + { + // ignore + } + } + } + if (expectedPrimary != null && expectedPrimary.ToString() != primary?.ToString()) + throw new RedisException($"Primary was expected to be {expectedPrimary}"); + Log($"Primary is {primary}"); + + await using var checkConn = Conn.GetSentinelMasterConnection(ServiceOptions); + + await WaitForRoleAsync(checkConn.GetServer(primary), "master", duration.Value.Subtract(sw.Elapsed)).ForAwait(); + + var replicas = SentinelServerA.SentinelGetReplicaAddresses(ServiceName); + if (replicas?.Length > 0) + { + await Task.Delay(100).ForAwait(); + replicas = SentinelServerA.SentinelGetReplicaAddresses(ServiceName); + await WaitForRoleAsync(checkConn.GetServer(replicas[0]), "slave", duration.Value.Subtract(sw.Elapsed)).ForAwait(); + } + + if (waitForReplication) + { + await WaitForReplicationAsync(checkConn.GetServer(primary), duration.Value.Subtract(sw.Elapsed)).ForAwait(); + } + } + + protected async Task WaitForRoleAsync(IServer server, string role, TimeSpan? duration = null) + { + duration ??= TimeSpan.FromSeconds(30); + + Log($"Waiting for server ({server.EndPoint}) role to be \"{role}\"..."); + var sw = Stopwatch.StartNew(); + while (sw.Elapsed < duration.Value) + { + try + { + if (server.Role()?.Value == role) + { + Log($"Done waiting for server ({server.EndPoint}) role to be \"{role}\""); + return; + } + } + catch (Exception) + { + // ignore + } + + await Task.Delay(100).ForAwait(); + } + + throw new RedisException($"Timeout waiting for server ({server.EndPoint}) to have expected role (\"{role}\") assigned"); + } + + protected async Task WaitForReplicationAsync(IServer primary, TimeSpan? duration = null) + { + duration ??= TimeSpan.FromSeconds(10); + + static void LogEndpoints(IServer primary, Action log) + { + if (primary.Multiplexer is ConnectionMultiplexer muxer) + { + var serverEndpoints = muxer.GetServerSnapshot(); + log("Endpoints:"); + foreach (var serverEndpoint in serverEndpoints) + { + log($" {serverEndpoint}:"); + var server = primary.Multiplexer.GetServer(serverEndpoint.EndPoint); + log($" Server: (Connected={server.IsConnected}, Type={server.ServerType}, IsReplica={server.IsReplica}, Unselectable={serverEndpoint.GetUnselectableFlags()})"); + } + } + } + + Log("Waiting for primary/replica replication to be in sync..."); + var sw = Stopwatch.StartNew(); + while (sw.Elapsed < duration.Value) + { + var info = primary.Info("replication"); + var replicationInfo = info.FirstOrDefault(f => f.Key == "Replication")?.ToArray().ToDictionary(); + var replicaInfo = replicationInfo?.FirstOrDefault(i => i.Key.StartsWith("slave")).Value?.Split(',').ToDictionary(i => i.Split('=').First(), i => i.Split('=').Last()); + var replicaOffset = replicaInfo?["offset"]; + var primaryOffset = replicationInfo?["master_repl_offset"]; + + if (replicaOffset == primaryOffset) + { + Log($"Done waiting for primary ({primaryOffset}) / replica ({replicaOffset}) replication to be in sync"); + LogEndpoints(primary, m => Log(m)); + return; + } + + Log($"Waiting for primary ({primaryOffset}) / replica ({replicaOffset}) replication to be in sync..."); + + await Task.Delay(250).ForAwait(); + } + + throw new RedisException("Timeout waiting for test servers primary/replica replication to be in sync."); + } +} diff --git a/tests/StackExchange.Redis.Tests/SentinelFailoverTests.cs b/tests/StackExchange.Redis.Tests/SentinelFailoverTests.cs new file mode 100644 index 000000000..358722839 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SentinelFailoverTests.cs @@ -0,0 +1,104 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[Collection(NonParallelCollection.Name)] +public class SentinelFailoverTests(ITestOutputHelper output) : SentinelBase(output) +{ + [Fact] + public async Task ManagedPrimaryConnectionEndToEndWithFailoverTest() + { + Skip.UnlessLongRunning(); + var connectionString = $"{TestConfig.Current.SentinelServer}:{TestConfig.Current.SentinelPortA},serviceName={ServiceOptions.ServiceName},allowAdmin=true"; + await using var conn = await ConnectionMultiplexer.ConnectAsync(connectionString); + + conn.ConfigurationChanged += (s, e) => Log($"Configuration changed: {e.EndPoint}"); + + var sub = conn.GetSubscriber(); +#pragma warning disable CS0618 + sub.Subscribe("*", (channel, message) => Log($"Sub: {channel}, message:{message}")); +#pragma warning restore CS0618 + + var db = conn.GetDatabase(); + await db.PingAsync(); + + var endpoints = conn.GetEndPoints(); + Assert.Equal(2, endpoints.Length); + + var servers = endpoints.Select(e => conn.GetServer(e)).ToArray(); + Assert.Equal(2, servers.Length); + + var primary = servers.FirstOrDefault(s => !s.IsReplica); + Assert.NotNull(primary); + var replica = servers.FirstOrDefault(s => s.IsReplica); + Assert.NotNull(replica); + Assert.NotEqual(primary.EndPoint.ToString(), replica.EndPoint.ToString()); + + // Set string value on current primary + var expected = DateTime.Now.Ticks.ToString(); + Log("Tick Key: " + expected); + var key = Me(); + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + await db.StringSetAsync(key, expected); + + var value = await db.StringGetAsync(key); + Assert.Equal(expected, value); + + Log("Waiting for first replication check..."); + // force read from replica, replication has some lag + await WaitForReplicationAsync(servers[0]).ForAwait(); + value = await db.StringGetAsync(key, CommandFlags.DemandReplica); + Assert.Equal(expected, value); + + Log("Waiting for ready pre-failover..."); + await WaitForReadyAsync(); + + // capture current replica + var replicas = SentinelServerA.SentinelGetReplicaAddresses(ServiceName); + + Log("Starting failover..."); + var sw = Stopwatch.StartNew(); + SentinelServerA.SentinelFailover(ServiceName); + + // There's no point in doing much for 10 seconds - this is a built-in delay of how Sentinel works. + // The actual completion invoking the replication of the former primary is handled via + // https://github.com/redis/redis/blob/f233c4c59d24828c77eb1118f837eaee14695f7f/src/sentinel.c#L4799-L4808 + // ...which is invoked by INFO polls every 10 seconds (https://github.com/redis/redis/blob/f233c4c59d24828c77eb1118f837eaee14695f7f/src/sentinel.c#L81) + // ...which is calling https://github.com/redis/redis/blob/f233c4c59d24828c77eb1118f837eaee14695f7f/src/sentinel.c#L2666 + // However, the quicker iteration on INFO during an o_down does not apply here: https://github.com/redis/redis/blob/f233c4c59d24828c77eb1118f837eaee14695f7f/src/sentinel.c#L3089-L3104 + // So...we're waiting 10 seconds, no matter what. Might as well just idle to be more stable. + await Task.Delay(TimeSpan.FromSeconds(10)); + + // wait until the replica becomes the primary + Log("Waiting for ready post-failover..."); + await WaitForReadyAsync(expectedPrimary: replicas[0]); + Log($"Time to failover: {sw.Elapsed}"); + + endpoints = conn.GetEndPoints(); + Assert.Equal(2, endpoints.Length); + + servers = endpoints.Select(e => conn.GetServer(e)).ToArray(); + Assert.Equal(2, servers.Length); + + var newPrimary = servers.FirstOrDefault(s => !s.IsReplica); + Assert.NotNull(newPrimary); + Assert.Equal(replica.EndPoint.ToString(), newPrimary.EndPoint.ToString()); + var newReplica = servers.FirstOrDefault(s => s.IsReplica); + Assert.NotNull(newReplica); + Assert.Equal(primary.EndPoint.ToString(), newReplica.EndPoint.ToString()); + Assert.NotEqual(primary.EndPoint.ToString(), replica.EndPoint.ToString()); + + value = await db.StringGetAsync(key); + Assert.Equal(expected, value); + + Log("Waiting for second replication check..."); + // force read from replica, replication has some lag + await WaitForReplicationAsync(newPrimary).ForAwait(); + value = await db.StringGetAsync(key, CommandFlags.DemandReplica); + Assert.Equal(expected, value); + } +} diff --git a/tests/StackExchange.Redis.Tests/SentinelTests.cs b/tests/StackExchange.Redis.Tests/SentinelTests.cs new file mode 100644 index 000000000..e58f530fd --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SentinelTests.cs @@ -0,0 +1,475 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class SentinelTests(ITestOutputHelper output) : SentinelBase(output) +{ + [Fact] + public async Task PrimaryConnectTest() + { + var connectionString = $"{TestConfig.Current.SentinelServer},serviceName={ServiceOptions.ServiceName},allowAdmin=true"; + + var conn = ConnectionMultiplexer.Connect(connectionString); + + var db = conn.GetDatabase(); + await db.PingAsync(); + + var endpoints = conn.GetEndPoints(); + Assert.Equal(2, endpoints.Length); + + var servers = endpoints.Select(e => conn.GetServer(e)).ToArray(); + Assert.Equal(2, servers.Length); + + var primary = servers.FirstOrDefault(s => !s.IsReplica); + Assert.NotNull(primary); + var replica = servers.FirstOrDefault(s => s.IsReplica); + Assert.NotNull(replica); + Assert.NotEqual(primary.EndPoint.ToString(), replica.EndPoint.ToString()); + + var expected = DateTime.Now.Ticks.ToString(); + Log("Tick Key: " + expected); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.StringSet(key, expected); + + var value = db.StringGet(key); + Assert.Equal(expected, value); + + // force read from replica, replication has some lag + await WaitForReplicationAsync(servers[0], TimeSpan.FromSeconds(10)).ForAwait(); + value = db.StringGet(key, CommandFlags.DemandReplica); + Assert.Equal(expected, value); + } + + [Fact] + public async Task PrimaryConnectAsyncTest() + { + var connectionString = $"{TestConfig.Current.SentinelServer},serviceName={ServiceOptions.ServiceName},allowAdmin=true"; + var conn = await ConnectionMultiplexer.ConnectAsync(connectionString); + + var db = conn.GetDatabase(); + await db.PingAsync(); + + var endpoints = conn.GetEndPoints(); + Assert.Equal(2, endpoints.Length); + + var servers = endpoints.Select(e => conn.GetServer(e)).ToArray(); + Assert.Equal(2, servers.Length); + + var primary = servers.FirstOrDefault(s => !s.IsReplica); + Assert.NotNull(primary); + var replica = servers.FirstOrDefault(s => s.IsReplica); + Assert.NotNull(replica); + Assert.NotEqual(primary.EndPoint.ToString(), replica.EndPoint.ToString()); + + var expected = DateTime.Now.Ticks.ToString(); + Log("Tick Key: " + expected); + var key = Me(); + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + await db.StringSetAsync(key, expected); + + var value = await db.StringGetAsync(key); + Assert.Equal(expected, value); + + // force read from replica, replication has some lag + await WaitForReplicationAsync(servers[0], TimeSpan.FromSeconds(10)).ForAwait(); + value = await db.StringGetAsync(key, CommandFlags.DemandReplica); + Assert.Equal(expected, value); + } + + [Fact] + [RunPerProtocol] + public async Task SentinelConnectTest() + { + var options = ServiceOptions.Clone(); + options.EndPoints.Add(TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortA); + await using var conn = ConnectionMultiplexer.SentinelConnect(options); + + var db = conn.GetDatabase(); + var test = await db.PingAsync(); + Log("ping to sentinel {0}:{1} took {2} ms", TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortA, test.TotalMilliseconds); + } + + [Fact] + public async Task SentinelRepeatConnectTest() + { + var options = ConfigurationOptions.Parse($"{TestConfig.Current.SentinelServer}:{TestConfig.Current.SentinelPortA}"); + options.ServiceName = ServiceName; + options.AllowAdmin = true; + + Log("Service Name: " + options.ServiceName); + foreach (var ep in options.EndPoints) + { + Log(" Endpoint: " + ep); + } + + await using var conn = await ConnectionMultiplexer.ConnectAsync(options); + + var db = conn.GetDatabase(); + var test = await db.PingAsync(); + Log("ping to 1st sentinel {0}:{1} took {2} ms", TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortA, test.TotalMilliseconds); + + Log("Service Name: " + options.ServiceName); + foreach (var ep in options.EndPoints) + { + Log(" Endpoint: " + ep); + } + + await using var conn2 = ConnectionMultiplexer.Connect(options); + + var db2 = conn2.GetDatabase(); + var test2 = await db2.PingAsync(); + Log("ping to 2nd sentinel {0}:{1} took {2} ms", TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortA, test2.TotalMilliseconds); + } + + [Fact] + public async Task SentinelConnectAsyncTest() + { + var options = ServiceOptions.Clone(); + options.EndPoints.Add(TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortA); + var conn = await ConnectionMultiplexer.SentinelConnectAsync(options); + + var db = conn.GetDatabase(); + var test = await db.PingAsync(); + Log("ping to sentinel {0}:{1} took {2} ms", TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortA, test.TotalMilliseconds); + } + + [Fact] + public void SentinelRole() + { + foreach (var server in SentinelsServers) + { + var role = server.Role(); + Assert.NotNull(role); + Assert.Equal(role.Value, RedisLiterals.sentinel); + var sentinel = role as Role.Sentinel; + Assert.NotNull(sentinel); + } + } + + [Fact] + public async Task PingTest() + { + var test = await SentinelServerA.PingAsync(); + Log("ping to sentinel {0}:{1} took {2} ms", TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortA, test.TotalMilliseconds); + test = await SentinelServerB.PingAsync(); + Log("ping to sentinel {0}:{1} took {1} ms", TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortB, test.TotalMilliseconds); + test = await SentinelServerC.PingAsync(); + Log("ping to sentinel {0}:{1} took {1} ms", TestConfig.Current.SentinelServer, TestConfig.Current.SentinelPortC, test.TotalMilliseconds); + } + + [Fact] + public void SentinelGetPrimaryAddressByNameTest() + { + foreach (var server in SentinelsServers) + { + var primary = server.SentinelMaster(ServiceName); + var endpoint = server.SentinelGetMasterAddressByName(ServiceName); + Assert.NotNull(endpoint); + var ipEndPoint = endpoint as IPEndPoint; + Assert.NotNull(ipEndPoint); + Assert.Equal(primary.ToDictionary()["ip"], ipEndPoint.Address.ToString()); + Assert.Equal(primary.ToDictionary()["port"], ipEndPoint.Port.ToString()); + Log("{0}:{1}", ipEndPoint.Address, ipEndPoint.Port); + } + } + + [Fact] + public async Task SentinelGetPrimaryAddressByNameAsyncTest() + { + foreach (var server in SentinelsServers) + { + var primary = server.SentinelMaster(ServiceName); + var endpoint = await server.SentinelGetMasterAddressByNameAsync(ServiceName).ForAwait(); + Assert.NotNull(endpoint); + var ipEndPoint = endpoint as IPEndPoint; + Assert.NotNull(ipEndPoint); + Assert.Equal(primary.ToDictionary()["ip"], ipEndPoint.Address.ToString()); + Assert.Equal(primary.ToDictionary()["port"], ipEndPoint.Port.ToString()); + Log("{0}:{1}", ipEndPoint.Address, ipEndPoint.Port); + } + } + + [Fact] + public void SentinelGetMasterAddressByNameNegativeTest() + { + foreach (var server in SentinelsServers) + { + var endpoint = server.SentinelGetMasterAddressByName("FakeServiceName"); + Assert.Null(endpoint); + } + } + + [Fact] + public async Task SentinelGetMasterAddressByNameAsyncNegativeTest() + { + foreach (var server in SentinelsServers) + { + var endpoint = await server.SentinelGetMasterAddressByNameAsync("FakeServiceName").ForAwait(); + Assert.Null(endpoint); + } + } + + [Fact] + public void SentinelPrimaryTest() + { + foreach (var server in SentinelsServers) + { + var dict = server.SentinelMaster(ServiceName).ToDictionary(); + Assert.Equal(ServiceName, dict["name"]); + Assert.StartsWith("master", dict["flags"]); + foreach (var kvp in dict) + { + Log("{0}:{1}", kvp.Key, kvp.Value); + } + } + } + + [Fact] + public async Task SentinelPrimaryAsyncTest() + { + foreach (var server in SentinelsServers) + { + var results = await server.SentinelMasterAsync(ServiceName).ForAwait(); + Assert.Equal(ServiceName, results.ToDictionary()["name"]); + Assert.StartsWith("master", results.ToDictionary()["flags"]); + foreach (var kvp in results) + { + Log("{0}:{1}", kvp.Key, kvp.Value); + } + } + } + + [Fact] + public void SentinelSentinelsTest() + { + var sentinels = SentinelServerA.SentinelSentinels(ServiceName); + + var expected = new List + { + SentinelServerB.EndPoint.ToString(), + SentinelServerC.EndPoint.ToString(), + }; + + var actual = new List(); + foreach (var kv in sentinels) + { + var data = kv.ToDictionary(); + actual.Add(data["ip"] + ":" + data["port"]); + } + + Assert.All(expected, ep => Assert.NotEqual(ep, SentinelServerA.EndPoint.ToString())); + Assert.Equal(2, sentinels.Length); + Assert.All(expected, ep => Assert.Contains(ep, actual, _ipComparer)); + + sentinels = SentinelServerB.SentinelSentinels(ServiceName); + foreach (var kv in sentinels) + { + var data = kv.ToDictionary(); + actual.Add(data["ip"] + ":" + data["port"]); + } + + expected = + [ + SentinelServerA.EndPoint.ToString(), + SentinelServerC.EndPoint.ToString(), + ]; + + Assert.All(expected, ep => Assert.NotEqual(ep, SentinelServerB.EndPoint.ToString())); + Assert.Equal(2, sentinels.Length); + Assert.All(expected, ep => Assert.Contains(ep, actual, _ipComparer)); + + sentinels = SentinelServerC.SentinelSentinels(ServiceName); + foreach (var kv in sentinels) + { + var data = kv.ToDictionary(); + actual.Add(data["ip"] + ":" + data["port"]); + } + + expected = + [ + SentinelServerA.EndPoint.ToString(), + SentinelServerB.EndPoint.ToString(), + ]; + + Assert.All(expected, ep => Assert.NotEqual(ep, SentinelServerC.EndPoint.ToString())); + Assert.Equal(2, sentinels.Length); + Assert.All(expected, ep => Assert.Contains(ep, actual, _ipComparer)); + } + + [Fact] + public async Task SentinelSentinelsAsyncTest() + { + var sentinels = await SentinelServerA.SentinelSentinelsAsync(ServiceName).ForAwait(); + var expected = new List + { + SentinelServerB.EndPoint.ToString(), + SentinelServerC.EndPoint.ToString(), + }; + + var actual = new List(); + foreach (var kv in sentinels) + { + var data = kv.ToDictionary(); + actual.Add(data["ip"] + ":" + data["port"]); + } + + Assert.All(expected, ep => Assert.NotEqual(ep, SentinelServerA.EndPoint.ToString())); + Assert.Equal(2, sentinels.Length); + Assert.All(expected, ep => Assert.Contains(ep, actual, _ipComparer)); + + sentinels = await SentinelServerB.SentinelSentinelsAsync(ServiceName).ForAwait(); + + expected = + [ + SentinelServerA.EndPoint.ToString(), + SentinelServerC.EndPoint.ToString(), + ]; + + actual = []; + foreach (var kv in sentinels) + { + var data = kv.ToDictionary(); + actual.Add(data["ip"] + ":" + data["port"]); + } + + Assert.All(expected, ep => Assert.NotEqual(ep, SentinelServerB.EndPoint.ToString())); + Assert.Equal(2, sentinels.Length); + Assert.All(expected, ep => Assert.Contains(ep, actual, _ipComparer)); + + sentinels = await SentinelServerC.SentinelSentinelsAsync(ServiceName).ForAwait(); + expected = + [ + SentinelServerA.EndPoint.ToString(), + SentinelServerB.EndPoint.ToString(), + ]; + actual = []; + foreach (var kv in sentinels) + { + var data = kv.ToDictionary(); + actual.Add(data["ip"] + ":" + data["port"]); + } + + Assert.All(expected, ep => Assert.NotEqual(ep, SentinelServerC.EndPoint.ToString())); + Assert.Equal(2, sentinels.Length); + Assert.All(expected, ep => Assert.Contains(ep, actual, _ipComparer)); + } + + [Fact] + public void SentinelPrimariesTest() + { + var primaryConfigs = SentinelServerA.SentinelMasters(); + Assert.Single(primaryConfigs); + Assert.True(primaryConfigs[0].ToDictionary().ContainsKey("name"), "replicaConfigs contains 'name'"); + Assert.Equal(ServiceName, primaryConfigs[0].ToDictionary()["name"]); + Assert.StartsWith("master", primaryConfigs[0].ToDictionary()["flags"]); + foreach (var config in primaryConfigs) + { + foreach (var kvp in config) + { + Log("{0}:{1}", kvp.Key, kvp.Value); + } + } + } + + [Fact] + public async Task SentinelPrimariesAsyncTest() + { + var primaryConfigs = await SentinelServerA.SentinelMastersAsync().ForAwait(); + Assert.Single(primaryConfigs); + Assert.True(primaryConfigs[0].ToDictionary().ContainsKey("name"), "replicaConfigs contains 'name'"); + Assert.Equal(ServiceName, primaryConfigs[0].ToDictionary()["name"]); + Assert.StartsWith("master", primaryConfigs[0].ToDictionary()["flags"]); + foreach (var config in primaryConfigs) + { + foreach (var kvp in config) + { + Log("{0}:{1}", kvp.Key, kvp.Value); + } + } + } + + [Fact] + public async Task SentinelReplicasTest() + { + // Give previous test run a moment to reset when multi-framework failover is in play. + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => SentinelServerA.SentinelReplicas(ServiceName).Length > 0); + + var replicaConfigs = SentinelServerA.SentinelReplicas(ServiceName); + Assert.True(replicaConfigs.Length > 0, "Has replicaConfigs"); + Assert.True(replicaConfigs[0].ToDictionary().ContainsKey("name"), "replicaConfigs contains 'name'"); + Assert.StartsWith("slave", replicaConfigs[0].ToDictionary()["flags"]); + + foreach (var config in replicaConfigs) + { + foreach (var kvp in config) + { + Log("{0}:{1}", kvp.Key, kvp.Value); + } + } + } + + [Fact] + public async Task SentinelReplicasAsyncTest() + { + // Give previous test run a moment to reset when multi-framework failover is in play. + await UntilConditionAsync(TimeSpan.FromSeconds(5), () => SentinelServerA.SentinelReplicas(ServiceName).Length > 0); + + var replicaConfigs = await SentinelServerA.SentinelReplicasAsync(ServiceName).ForAwait(); + Assert.True(replicaConfigs.Length > 0, "Has replicaConfigs"); + Assert.True(replicaConfigs[0].ToDictionary().ContainsKey("name"), "replicaConfigs contains 'name'"); + Assert.StartsWith("slave", replicaConfigs[0].ToDictionary()["flags"]); + foreach (var config in replicaConfigs) + { + foreach (var kvp in config) + { + Log("{0}:{1}", kvp.Key, kvp.Value); + } + } + } + + [Fact] + public async Task SentinelGetSentinelAddressesTest() + { + var addresses = await SentinelServerA.SentinelGetSentinelAddressesAsync(ServiceName).ForAwait(); + Assert.Contains(SentinelServerB.EndPoint, addresses); + Assert.Contains(SentinelServerC.EndPoint, addresses); + + addresses = await SentinelServerB.SentinelGetSentinelAddressesAsync(ServiceName).ForAwait(); + Assert.Contains(SentinelServerA.EndPoint, addresses); + Assert.Contains(SentinelServerC.EndPoint, addresses); + + addresses = await SentinelServerC.SentinelGetSentinelAddressesAsync(ServiceName).ForAwait(); + Assert.Contains(SentinelServerA.EndPoint, addresses); + Assert.Contains(SentinelServerB.EndPoint, addresses); + } + + [Fact] + public async Task ReadOnlyConnectionReplicasTest() + { + var replicas = SentinelServerA.SentinelGetReplicaAddresses(ServiceName); + if (replicas.Length == 0) + { + Assert.Skip("Sentinel race: 0 replicas to test against."); + } + + var config = new ConfigurationOptions(); + foreach (var replica in replicas) + { + config.EndPoints.Add(replica); + } + + var readonlyConn = await ConnectionMultiplexer.ConnectAsync(config); + + await UntilConditionAsync(TimeSpan.FromSeconds(2), () => readonlyConn.IsConnected); + Assert.True(readonlyConn.IsConnected); + var db = readonlyConn.GetDatabase(); + var s = db.StringGet("test"); + Assert.True(s.IsNullOrEmpty); + } +} diff --git a/tests/StackExchange.Redis.Tests/ServerSnapshotTests.cs b/tests/StackExchange.Redis.Tests/ServerSnapshotTests.cs new file mode 100644 index 000000000..2c81c3826 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ServerSnapshotTests.cs @@ -0,0 +1,126 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.Serialization; +using Xunit; +using static StackExchange.Redis.ConnectionMultiplexer; + +namespace StackExchange.Redis.Tests; + +public class ServerSnapshotTests +{ + [Fact] + [SuppressMessage("Assertions", "xUnit2012:Do not use boolean check to check if a value exists in a collection", Justification = "Explicit testing")] + [SuppressMessage("Assertions", "xUnit2013:Do not use equality check to check for collection size.", Justification = "Explicit testing")] + [SuppressMessage("Assertions", "xUnit2029:Do not use Empty() to check if a value does not exist in a collection", Justification = "Explicit testing")] + [SuppressMessage("Performance", "CA1829:Use Length/Count property instead of Count() when available", Justification = "Explicit testing")] + [SuppressMessage("Performance", "CA1860:Avoid using 'Enumerable.Any()' extension method", Justification = "Explicit testing")] + public void EmptyBehaviour() + { + var snapshot = ServerSnapshot.Empty; + Assert.Same(snapshot, snapshot.Add(null!)); + + Assert.Equal(0, snapshot.Count); + Assert.Equal(0, ManualCount(snapshot)); + Assert.Equal(0, ManualCount(snapshot, static _ => true)); + Assert.Equal(0, ManualCount(snapshot, static _ => false)); + + Assert.Equal(0, Enumerable.Count(snapshot)); + Assert.Equal(0, Enumerable.Count(snapshot, static _ => true)); + Assert.Equal(0, Enumerable.Count(snapshot, static _ => false)); + + Assert.False(Enumerable.Any(snapshot)); + Assert.False(snapshot.Any()); + + Assert.False(Enumerable.Any(snapshot, static _ => true)); + Assert.False(snapshot.Any(static _ => true)); + Assert.False(Enumerable.Any(snapshot, static _ => false)); + Assert.False(snapshot.Any(static _ => false)); + + Assert.Empty(snapshot); + Assert.Empty(Enumerable.Where(snapshot, static _ => true)); + Assert.Empty(snapshot.Where(static _ => true)); + Assert.Empty(Enumerable.Where(snapshot, static _ => false)); + Assert.Empty(snapshot.Where(static _ => false)); + + Assert.Empty(snapshot.Where(CommandFlags.DemandMaster)); + Assert.Empty(snapshot.Where(CommandFlags.DemandReplica)); + Assert.Empty(snapshot.Where(CommandFlags.None)); + Assert.Empty(snapshot.Where(CommandFlags.FireAndForget | CommandFlags.NoRedirect | CommandFlags.NoScriptCache)); + } + + [Theory] + [InlineData(1, 0)] + [InlineData(1, 1)] + [InlineData(5, 0)] + [InlineData(5, 3)] + [InlineData(5, 5)] + [SuppressMessage("Assertions", "xUnit2012:Do not use boolean check to check if a value exists in a collection", Justification = "Explicit testing")] + [SuppressMessage("Assertions", "xUnit2029:Do not use Empty() to check if a value does not exist in a collection", Justification = "Explicit testing")] + [SuppressMessage("Assertions", "xUnit2030:Do not use Assert.NotEmpty to check if a value exists in a collection", Justification = "Explicit testing")] + [SuppressMessage("Performance", "CA1829:Use Length/Count property instead of Count() when available", Justification = "Explicit testing")] + [SuppressMessage("Performance", "CA1860:Avoid using 'Enumerable.Any()' extension method", Justification = "Explicit testing")] + public void NonEmptyBehaviour(int count, int replicaCount) + { + var snapshot = ServerSnapshot.Empty; + for (int i = 0; i < count; i++) + { +#pragma warning disable SYSLIB0050 // Type or member is obsolete + var dummy = (ServerEndPoint)FormatterServices.GetSafeUninitializedObject(typeof(ServerEndPoint)); +#pragma warning restore SYSLIB0050 // Type or member is obsolete + dummy.IsReplica = i < replicaCount; + snapshot = snapshot.Add(dummy); + } + + Assert.Equal(count, snapshot.Count); + Assert.Equal(count, ManualCount(snapshot)); + Assert.Equal(count, ManualCount(snapshot, static _ => true)); + Assert.Equal(0, ManualCount(snapshot, static _ => false)); + Assert.Equal(replicaCount, ManualCount(snapshot, static s => s.IsReplica)); + + Assert.Equal(count, Enumerable.Count(snapshot)); + Assert.Equal(count, Enumerable.Count(snapshot, static _ => true)); + Assert.Equal(0, Enumerable.Count(snapshot, static _ => false)); + Assert.Equal(replicaCount, Enumerable.Count(snapshot, static s => s.IsReplica)); + + Assert.True(Enumerable.Any(snapshot)); + Assert.True(snapshot.Any()); + + Assert.True(Enumerable.Any(snapshot, static _ => true)); + Assert.True(snapshot.Any(static _ => true)); + Assert.False(Enumerable.Any(snapshot, static _ => false)); + Assert.False(snapshot.Any(static _ => false)); + + Assert.NotEmpty(snapshot); + Assert.NotEmpty(Enumerable.Where(snapshot, static _ => true)); + Assert.NotEmpty(snapshot.Where(static _ => true)); + Assert.Empty(Enumerable.Where(snapshot, static _ => false)); + Assert.Empty(snapshot.Where(static _ => false)); + + Assert.Equal(snapshot.Count - replicaCount, snapshot.Where(CommandFlags.DemandMaster).Count()); + Assert.Equal(replicaCount, snapshot.Where(CommandFlags.DemandReplica).Count()); + Assert.Equal(snapshot.Count, snapshot.Where(CommandFlags.None).Count()); + Assert.Equal(snapshot.Count, snapshot.Where(CommandFlags.FireAndForget | CommandFlags.NoRedirect | CommandFlags.NoScriptCache).Count()); + } + + private static int ManualCount(ServerSnapshot snapshot, Func? predicate = null) + { + // ^^^ tests the custom iterator implementation + int count = 0; + if (predicate is null) + { + foreach (var item in snapshot) + { + count++; + } + } + else + { + foreach (var item in snapshot.Where(predicate)) + { + count++; + } + } + return count; + } +} diff --git a/tests/StackExchange.Redis.Tests/SetTests.cs b/tests/StackExchange.Redis.Tests/SetTests.cs new file mode 100644 index 000000000..9326ca7a7 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SetTests.cs @@ -0,0 +1,389 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class SetTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task SetContains() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key); + for (int i = 1; i < 1001; i++) + { + db.SetAdd(key, i, CommandFlags.FireAndForget); + } + + // Single member + var isMemeber = db.SetContains(key, 1); + Assert.True(isMemeber); + + // Multi members + var areMemebers = db.SetContains(key, [0, 1, 2]); + Assert.Equal(3, areMemebers.Length); + Assert.False(areMemebers[0]); + Assert.True(areMemebers[1]); + + // key not exists + db.KeyDelete(key); + isMemeber = db.SetContains(key, 1); + Assert.False(isMemeber); + areMemebers = db.SetContains(key, [0, 1, 2]); + Assert.Equal(3, areMemebers.Length); + Assert.True(areMemebers.All(i => !i)); // Check that all the elements are False + } + + [Fact] + public async Task SetContainsAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + await db.KeyDeleteAsync(key); + for (int i = 1; i < 1001; i++) + { + db.SetAdd(key, i, CommandFlags.FireAndForget); + } + + // Single member + var isMemeber = await db.SetContainsAsync(key, 1); + Assert.True(isMemeber); + + // Multi members + var areMemebers = await db.SetContainsAsync(key, [0, 1, 2]); + Assert.Equal(3, areMemebers.Length); + Assert.False(areMemebers[0]); + Assert.True(areMemebers[1]); + + // key not exists + await db.KeyDeleteAsync(key); + isMemeber = await db.SetContainsAsync(key, 1); + Assert.False(isMemeber); + areMemebers = await db.SetContainsAsync(key, [0, 1, 2]); + Assert.Equal(3, areMemebers.Length); + Assert.True(areMemebers.All(i => !i)); // Check that all the elements are False + } + + [Fact] + public async Task SetIntersectionLength() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + + var key1 = Me() + "1"; + db.KeyDelete(key1, CommandFlags.FireAndForget); + db.SetAdd(key1, [0, 1, 2, 3, 4], CommandFlags.FireAndForget); + var key2 = Me() + "2"; + db.KeyDelete(key2, CommandFlags.FireAndForget); + db.SetAdd(key2, [1, 2, 3, 4, 5], CommandFlags.FireAndForget); + + Assert.Equal(4, db.SetIntersectionLength([key1, key2])); + // with limit + Assert.Equal(3, db.SetIntersectionLength([key1, key2], 3)); + + // Missing keys should be 0 + var key3 = Me() + "3"; + var key4 = Me() + "4"; + db.KeyDelete(key3, CommandFlags.FireAndForget); + Assert.Equal(0, db.SetIntersectionLength([key1, key3])); + Assert.Equal(0, db.SetIntersectionLength([key3, key4])); + } + + [Fact] + public async Task SetIntersectionLengthAsync() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + + var key1 = Me() + "1"; + db.KeyDelete(key1, CommandFlags.FireAndForget); + db.SetAdd(key1, [0, 1, 2, 3, 4], CommandFlags.FireAndForget); + var key2 = Me() + "2"; + db.KeyDelete(key2, CommandFlags.FireAndForget); + db.SetAdd(key2, [1, 2, 3, 4, 5], CommandFlags.FireAndForget); + + Assert.Equal(4, await db.SetIntersectionLengthAsync([key1, key2])); + // with limit + Assert.Equal(3, await db.SetIntersectionLengthAsync([key1, key2], 3)); + + // Missing keys should be 0 + var key3 = Me() + "3"; + var key4 = Me() + "4"; + db.KeyDelete(key3, CommandFlags.FireAndForget); + Assert.Equal(0, await db.SetIntersectionLengthAsync([key1, key3])); + Assert.Equal(0, await db.SetIntersectionLengthAsync([key3, key4])); + } + + [Fact] + public async Task SScan() + { + await using var conn = Create(); + + var server = GetAnyPrimary(conn); + + var key = Me(); + var db = conn.GetDatabase(); + int totalUnfiltered = 0, totalFiltered = 0; + for (int i = 1; i < 1001; i++) + { + db.SetAdd(key, i, CommandFlags.FireAndForget); + totalUnfiltered += i; + if (i.ToString().Contains('3')) totalFiltered += i; + } + + var unfilteredActual = db.SetScan(key).Select(x => (int)x).Sum(); + Assert.Equal(totalUnfiltered, unfilteredActual); + if (server.Features.Scan) + { + var filteredActual = db.SetScan(key, "*3*").Select(x => (int)x).Sum(); + Assert.Equal(totalFiltered, filteredActual); + } + } + + [Fact] + public async Task SetRemoveArgTests() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + + RedisValue[]? values = null; + Assert.Throws(() => db.SetRemove(key, values!)); + await Assert.ThrowsAsync(async () => await db.SetRemoveAsync(key, values!).ForAwait()).ForAwait(); + + values = []; + Assert.Equal(0, db.SetRemove(key, values)); + Assert.Equal(0, await db.SetRemoveAsync(key, values).ForAwait()); + } + + [Fact] + public async Task SetPopMulti_Multi() + { + await using var conn = Create(require: RedisFeatures.v3_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + for (int i = 1; i < 11; i++) + { + _ = db.SetAddAsync(key, i, CommandFlags.FireAndForget); + } + + var random = db.SetPop(key); + Assert.False(random.IsNull); + Assert.True((int)random > 0); + Assert.True((int)random <= 10); + Assert.Equal(9, db.SetLength(key)); + + var moreRandoms = db.SetPop(key, 2); + Assert.Equal(2, moreRandoms.Length); + Assert.False(moreRandoms[0].IsNull); + Assert.Equal(7, db.SetLength(key)); + } + + [Fact] + public async Task SetPopMulti_Single() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + for (int i = 1; i < 11; i++) + { + db.SetAdd(key, i, CommandFlags.FireAndForget); + } + + var random = db.SetPop(key); + Assert.False(random.IsNull); + Assert.True((int)random > 0); + Assert.True((int)random <= 10); + Assert.Equal(9, db.SetLength(key)); + + var moreRandoms = db.SetPop(key, 1); + Assert.Single(moreRandoms); + Assert.False(moreRandoms[0].IsNull); + Assert.Equal(8, db.SetLength(key)); + } + + [Fact] + public async Task SetPopMulti_Multi_Async() + { + await using var conn = Create(require: RedisFeatures.v3_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + for (int i = 1; i < 11; i++) + { + db.SetAdd(key, i, CommandFlags.FireAndForget); + } + + var random = await db.SetPopAsync(key).ForAwait(); + Assert.False(random.IsNull); + Assert.True((int)random > 0); + Assert.True((int)random <= 10); + Assert.Equal(9, db.SetLength(key)); + + var moreRandoms = await db.SetPopAsync(key, 2).ForAwait(); + Assert.Equal(2, moreRandoms.Length); + Assert.False(moreRandoms[0].IsNull); + Assert.Equal(7, db.SetLength(key)); + } + + [Fact] + public async Task SetPopMulti_Single_Async() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + for (int i = 1; i < 11; i++) + { + db.SetAdd(key, i, CommandFlags.FireAndForget); + } + + var random = await db.SetPopAsync(key).ForAwait(); + Assert.False(random.IsNull); + Assert.True((int)random > 0); + Assert.True((int)random <= 10); + Assert.Equal(9, db.SetLength(key)); + + var moreRandoms = db.SetPop(key, 1); + Assert.Single(moreRandoms); + Assert.False(moreRandoms[0].IsNull); + Assert.Equal(8, db.SetLength(key)); + } + + [Fact] + public async Task SetPopMulti_Zero_Async() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + for (int i = 1; i < 11; i++) + { + db.SetAdd(key, i, CommandFlags.FireAndForget); + } + + var t = db.SetPopAsync(key, count: 0); + Assert.True(t.IsCompleted); // sync + var arr = await t; + Assert.Empty(arr); + + Assert.Equal(10, db.SetLength(key)); + } + + [Fact] + public async Task SetAdd_Zero() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + + var result = db.SetAdd(key, Array.Empty()); + Assert.Equal(0, result); + + Assert.Equal(0, db.SetLength(key)); + } + + [Fact] + public async Task SetAdd_Zero_Async() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + + var t = db.SetAddAsync(key, Array.Empty()); + Assert.True(t.IsCompleted); // sync + var count = await t; + Assert.Equal(0, count); + + Assert.Equal(0, db.SetLength(key)); + } + + [Fact] + public async Task SetPopMulti_Nil() + { + await using var conn = Create(require: RedisFeatures.v3_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + + var arr = db.SetPop(key, 1); + Assert.Empty(arr); + } + + [Fact] + public async Task TestSortReadonlyPrimary() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + await db.KeyDeleteAsync(key); + + var random = new Random(); + var items = Enumerable.Repeat(0, 200).Select(_ => random.Next()).ToList(); + await db.SetAddAsync(key, items.Select(x => (RedisValue)x).ToArray()); + items.Sort(); + + var result = db.Sort(key).Select(x => (int)x); + Assert.Equal(items, result); + + result = (await db.SortAsync(key)).Select(x => (int)x); + Assert.Equal(items, result); + } + + [Fact] + public async Task TestSortReadonlyReplica() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + await db.KeyDeleteAsync(key); + + var random = new Random(); + var items = Enumerable.Repeat(0, 200).Select(_ => random.Next()).ToList(); + await db.SetAddAsync(key, items.Select(x => (RedisValue)x).ToArray()); + + await using var readonlyConn = Create(configuration: TestConfig.Current.ReplicaServerAndPort, require: RedisFeatures.v7_0_0_rc1); + var readonlyDb = conn.GetDatabase(); + + items.Sort(); + + var result = readonlyDb.Sort(key).Select(x => (int)x); + Assert.Equal(items, result); + + result = (await readonlyDb.SortAsync(key)).Select(x => (int)x); + Assert.Equal(items, result); + } +} diff --git a/tests/StackExchange.Redis.Tests/SocketTests.cs b/tests/StackExchange.Redis.Tests/SocketTests.cs new file mode 100644 index 000000000..2d11c0014 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SocketTests.cs @@ -0,0 +1,28 @@ +using System.Diagnostics; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class SocketTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public async Task CheckForSocketLeaks() + { + Skip.UnlessLongRunning(); + const int count = 2000; + for (var i = 0; i < count; i++) + { + await using var _ = Create(clientName: "Test: " + i); + // Intentionally just creating and disposing to leak sockets here + // ...so we can figure out what's happening. + } + // Force GC before memory dump in debug below... + CollectGarbage(); + + if (Debugger.IsAttached) + { + Debugger.Break(); + } + } +} diff --git a/tests/StackExchange.Redis.Tests/SortedSetTests.cs b/tests/StackExchange.Redis.Tests/SortedSetTests.cs new file mode 100644 index 000000000..a6e6271ea --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SortedSetTests.cs @@ -0,0 +1,1466 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class SortedSetTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + private static readonly SortedSetEntry[] entries = + [ + new SortedSetEntry("a", 1), + new SortedSetEntry("b", 2), + new SortedSetEntry("c", 3), + new SortedSetEntry("d", 4), + new SortedSetEntry("e", 5), + new SortedSetEntry("f", 6), + new SortedSetEntry("g", 7), + new SortedSetEntry("h", 8), + new SortedSetEntry("i", 9), + new SortedSetEntry("j", 10), + ]; + + private static readonly SortedSetEntry[] entriesPow2 = + [ + new SortedSetEntry("a", 1), + new SortedSetEntry("b", 2), + new SortedSetEntry("c", 4), + new SortedSetEntry("d", 8), + new SortedSetEntry("e", 16), + new SortedSetEntry("f", 32), + new SortedSetEntry("g", 64), + new SortedSetEntry("h", 128), + new SortedSetEntry("i", 256), + new SortedSetEntry("j", 512), + ]; + + private static readonly SortedSetEntry[] entriesPow3 = + [ + new SortedSetEntry("a", 1), + new SortedSetEntry("c", 4), + new SortedSetEntry("e", 16), + new SortedSetEntry("g", 64), + new SortedSetEntry("i", 256), + ]; + + private static readonly SortedSetEntry[] lexEntries = + [ + new SortedSetEntry("a", 0), + new SortedSetEntry("b", 0), + new SortedSetEntry("c", 0), + new SortedSetEntry("d", 0), + new SortedSetEntry("e", 0), + new SortedSetEntry("f", 0), + new SortedSetEntry("g", 0), + new SortedSetEntry("h", 0), + new SortedSetEntry("i", 0), + new SortedSetEntry("j", 0), + ]; + + [Fact] + public async Task SortedSetCombine() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key1 = Me(); + db.KeyDelete(key1, CommandFlags.FireAndForget); + var key2 = Me() + "2"; + db.KeyDelete(key2, CommandFlags.FireAndForget); + + db.SortedSetAdd(key1, entries); + db.SortedSetAdd(key2, entriesPow3); + + var diff = db.SortedSetCombine(SetOperation.Difference, [key1, key2]); + Assert.Equal(5, diff.Length); + Assert.Equal("b", diff[0]); + + var inter = db.SortedSetCombine(SetOperation.Intersect, [key1, key2]); + Assert.Equal(5, inter.Length); + Assert.Equal("a", inter[0]); + + var union = db.SortedSetCombine(SetOperation.Union, [key1, key2]); + Assert.Equal(10, union.Length); + Assert.Equal("a", union[0]); + } + + [Fact] + public async Task SortedSetCombineAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key1 = Me(); + db.KeyDelete(key1, CommandFlags.FireAndForget); + var key2 = Me() + "2"; + db.KeyDelete(key2, CommandFlags.FireAndForget); + + db.SortedSetAdd(key1, entries); + db.SortedSetAdd(key2, entriesPow3); + + var diff = await db.SortedSetCombineAsync(SetOperation.Difference, [key1, key2]); + Assert.Equal(5, diff.Length); + Assert.Equal("b", diff[0]); + + var inter = await db.SortedSetCombineAsync(SetOperation.Intersect, [key1, key2]); + Assert.Equal(5, inter.Length); + Assert.Equal("a", inter[0]); + + var union = await db.SortedSetCombineAsync(SetOperation.Union, [key1, key2]); + Assert.Equal(10, union.Length); + Assert.Equal("a", union[0]); + } + + [Fact] + public async Task SortedSetCombineWithScores() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key1 = Me(); + db.KeyDelete(key1, CommandFlags.FireAndForget); + var key2 = Me() + "2"; + db.KeyDelete(key2, CommandFlags.FireAndForget); + + db.SortedSetAdd(key1, entries); + db.SortedSetAdd(key2, entriesPow3); + + var diff = db.SortedSetCombineWithScores(SetOperation.Difference, [key1, key2]); + Assert.Equal(5, diff.Length); + Assert.Equal(new SortedSetEntry("b", 2), diff[0]); + + var inter = db.SortedSetCombineWithScores(SetOperation.Intersect, [key1, key2]); + Assert.Equal(5, inter.Length); + Assert.Equal(new SortedSetEntry("a", 2), inter[0]); + + var union = db.SortedSetCombineWithScores(SetOperation.Union, [key1, key2]); + Assert.Equal(10, union.Length); + Assert.Equal(new SortedSetEntry("a", 2), union[0]); + } + + [Fact] + public async Task SortedSetCombineWithScoresAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key1 = Me(); + db.KeyDelete(key1, CommandFlags.FireAndForget); + var key2 = Me() + "2"; + db.KeyDelete(key2, CommandFlags.FireAndForget); + + db.SortedSetAdd(key1, entries); + db.SortedSetAdd(key2, entriesPow3); + + var diff = await db.SortedSetCombineWithScoresAsync(SetOperation.Difference, [key1, key2]); + Assert.Equal(5, diff.Length); + Assert.Equal(new SortedSetEntry("b", 2), diff[0]); + + var inter = await db.SortedSetCombineWithScoresAsync(SetOperation.Intersect, [key1, key2]); + Assert.Equal(5, inter.Length); + Assert.Equal(new SortedSetEntry("a", 2), inter[0]); + + var union = await db.SortedSetCombineWithScoresAsync(SetOperation.Union, [key1, key2]); + Assert.Equal(10, union.Length); + Assert.Equal(new SortedSetEntry("a", 2), union[0]); + } + + [Fact] + public async Task SortedSetCombineAndStore() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key1 = Me(); + db.KeyDelete(key1, CommandFlags.FireAndForget); + var key2 = Me() + "2"; + db.KeyDelete(key2, CommandFlags.FireAndForget); + var destination = Me() + "dest"; + db.KeyDelete(destination, CommandFlags.FireAndForget); + + db.SortedSetAdd(key1, entries); + db.SortedSetAdd(key2, entriesPow3); + + var diff = db.SortedSetCombineAndStore(SetOperation.Difference, destination, [key1, key2]); + Assert.Equal(5, diff); + + var inter = db.SortedSetCombineAndStore(SetOperation.Intersect, destination, [key1, key2]); + Assert.Equal(5, inter); + + var union = db.SortedSetCombineAndStore(SetOperation.Union, destination, [key1, key2]); + Assert.Equal(10, union); + } + + [Fact] + public async Task SortedSetCombineAndStoreAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key1 = Me(); + db.KeyDelete(key1, CommandFlags.FireAndForget); + var key2 = Me() + "2"; + db.KeyDelete(key2, CommandFlags.FireAndForget); + var destination = Me() + "dest"; + db.KeyDelete(destination, CommandFlags.FireAndForget); + + db.SortedSetAdd(key1, entries); + db.SortedSetAdd(key2, entriesPow3); + + var diff = await db.SortedSetCombineAndStoreAsync(SetOperation.Difference, destination, [key1, key2]); + Assert.Equal(5, diff); + + var inter = await db.SortedSetCombineAndStoreAsync(SetOperation.Intersect, destination, [key1, key2]); + Assert.Equal(5, inter); + + var union = await db.SortedSetCombineAndStoreAsync(SetOperation.Union, destination, [key1, key2]); + Assert.Equal(10, union); + } + + [Fact] + public async Task SortedSetCombineErrors() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key1 = Me(); + db.KeyDelete(key1, CommandFlags.FireAndForget); + var key2 = Me() + "2"; + db.KeyDelete(key2, CommandFlags.FireAndForget); + var destination = Me() + "dest"; + db.KeyDelete(destination, CommandFlags.FireAndForget); + + db.SortedSetAdd(key1, entries); + db.SortedSetAdd(key2, entriesPow3); + + // ZDIFF can't be used with weights + var ex = Assert.Throws(() => db.SortedSetCombine(SetOperation.Difference, [key1, key2], [1, 2])); + Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message); + ex = Assert.Throws(() => db.SortedSetCombineWithScores(SetOperation.Difference, [key1, key2], [1, 2])); + Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message); + ex = Assert.Throws(() => db.SortedSetCombineAndStore(SetOperation.Difference, destination, [key1, key2], [1, 2])); + Assert.Equal("ZDIFFSTORE cannot be used with weights or aggregation.", ex.Message); + // and Async... + ex = await Assert.ThrowsAsync(() => db.SortedSetCombineAsync(SetOperation.Difference, [key1, key2], [1, 2])); + Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message); + ex = await Assert.ThrowsAsync(() => db.SortedSetCombineWithScoresAsync(SetOperation.Difference, [key1, key2], [1, 2])); + Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message); + ex = await Assert.ThrowsAsync(() => db.SortedSetCombineAndStoreAsync(SetOperation.Difference, destination, [key1, key2], [1, 2])); + Assert.Equal("ZDIFFSTORE cannot be used with weights or aggregation.", ex.Message); + + // ZDIFF can't be used with aggregation + ex = Assert.Throws(() => db.SortedSetCombine(SetOperation.Difference, [key1, key2], aggregate: Aggregate.Max)); + Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message); + ex = Assert.Throws(() => db.SortedSetCombineWithScores(SetOperation.Difference, [key1, key2], aggregate: Aggregate.Max)); + Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message); + ex = Assert.Throws(() => db.SortedSetCombineAndStore(SetOperation.Difference, destination, [key1, key2], aggregate: Aggregate.Max)); + Assert.Equal("ZDIFFSTORE cannot be used with weights or aggregation.", ex.Message); + // and Async... + ex = await Assert.ThrowsAsync(() => db.SortedSetCombineAsync(SetOperation.Difference, [key1, key2], aggregate: Aggregate.Max)); + Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message); + ex = await Assert.ThrowsAsync(() => db.SortedSetCombineWithScoresAsync(SetOperation.Difference, [key1, key2], aggregate: Aggregate.Max)); + Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message); + ex = await Assert.ThrowsAsync(() => db.SortedSetCombineAndStoreAsync(SetOperation.Difference, destination, [key1, key2], aggregate: Aggregate.Max)); + Assert.Equal("ZDIFFSTORE cannot be used with weights or aggregation.", ex.Message); + + // Too many weights + ex = Assert.Throws(() => db.SortedSetCombine(SetOperation.Union, [key1, key2], [1, 2, 3])); + Assert.StartsWith("Keys and weights should have the same number of elements.", ex.Message); + ex = Assert.Throws(() => db.SortedSetCombineWithScores(SetOperation.Union, [key1, key2], [1, 2, 3])); + Assert.StartsWith("Keys and weights should have the same number of elements.", ex.Message); + ex = Assert.Throws(() => db.SortedSetCombineAndStore(SetOperation.Union, destination, [key1, key2], [1, 2, 3])); + Assert.StartsWith("Keys and weights should have the same number of elements.", ex.Message); + // and Async... + ex = await Assert.ThrowsAsync(() => db.SortedSetCombineAsync(SetOperation.Union, [key1, key2], [1, 2, 3])); + Assert.StartsWith("Keys and weights should have the same number of elements.", ex.Message); + ex = await Assert.ThrowsAsync(() => db.SortedSetCombineWithScoresAsync(SetOperation.Union, [key1, key2], [1, 2, 3])); + Assert.StartsWith("Keys and weights should have the same number of elements.", ex.Message); + ex = await Assert.ThrowsAsync(() => db.SortedSetCombineAndStoreAsync(SetOperation.Union, destination, [key1, key2], [1, 2, 3])); + Assert.StartsWith("Keys and weights should have the same number of elements.", ex.Message); + } + + [Fact] + public async Task SortedSetIntersectionLength() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key1 = Me(); + db.KeyDelete(key1, CommandFlags.FireAndForget); + var key2 = Me() + "2"; + db.KeyDelete(key2, CommandFlags.FireAndForget); + + db.SortedSetAdd(key1, entries); + db.SortedSetAdd(key2, entriesPow3); + + var inter = db.SortedSetIntersectionLength([key1, key2]); + Assert.Equal(5, inter); + + // with limit + inter = db.SortedSetIntersectionLength([key1, key2], 3); + Assert.Equal(3, inter); + } + + [Fact] + public async Task SortedSetIntersectionLengthAsync() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key1 = Me(); + db.KeyDelete(key1, CommandFlags.FireAndForget); + var key2 = Me() + "2"; + db.KeyDelete(key2, CommandFlags.FireAndForget); + + db.SortedSetAdd(key1, entries); + db.SortedSetAdd(key2, entriesPow3); + + var inter = await db.SortedSetIntersectionLengthAsync([key1, key2]); + Assert.Equal(5, inter); + + // with limit + inter = await db.SortedSetIntersectionLengthAsync([key1, key2], 3); + Assert.Equal(3, inter); + } + + [Fact] + public async Task SortedSetRangeViaScript() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SortedSetAdd(key, entries, CommandFlags.FireAndForget); + + var result = db.ScriptEvaluate(script: "return redis.call('ZRANGE', KEYS[1], 0, -1, 'WITHSCORES')", keys: [key]); + AssertFlatArrayEntries(result); + } + + [Fact] + public async Task SortedSetRangeViaExecute() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SortedSetAdd(key, entries, CommandFlags.FireAndForget); + + var result = db.Execute("ZRANGE", [key, 0, -1, "WITHSCORES"]); + + if (TestContext.Current.IsResp3()) + { + AssertJaggedArrayEntries(result); + } + else + { + AssertFlatArrayEntries(result); + } + } + + private void AssertFlatArrayEntries(RedisResult result) + { + Assert.Equal(ResultType.Array, result.Resp2Type); + Assert.Equal(entries.Length * 2, (int)result.Length); + int index = 0; + foreach (var entry in entries) + { + var e = result[index++]; + Assert.Equal(ResultType.BulkString, e.Resp2Type); + Assert.Equal(entry.Element, e.AsRedisValue()); + + e = result[index++]; + Assert.Equal(ResultType.BulkString, e.Resp2Type); + Assert.Equal(entry.Score, e.AsDouble()); + } + } + + private void AssertJaggedArrayEntries(RedisResult result) + { + Assert.Equal(ResultType.Array, result.Resp2Type); + Assert.Equal(entries.Length, (int)result.Length); + int index = 0; + foreach (var entry in entries) + { + var arr = result[index++]; + Assert.Equal(ResultType.Array, arr.Resp2Type); + Assert.Equal(2, arr.Length); + + var e = arr[0]; + Assert.Equal(ResultType.BulkString, e.Resp2Type); + Assert.Equal(entry.Element, e.AsRedisValue()); + + e = arr[1]; + Assert.Equal(ResultType.SimpleString, e.Resp2Type); + Assert.Equal(ResultType.Double, e.Resp3Type); + Assert.Equal(entry.Score, e.AsDouble()); + } + } + + [Fact] + public async Task SortedSetPopMulti_Multi() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SortedSetAdd(key, entries, CommandFlags.FireAndForget); + + var first = db.SortedSetPop(key, Order.Ascending); + Assert.True(first.HasValue); + Assert.Equal(entries[0], first.Value); + Assert.Equal(9, db.SortedSetLength(key)); + + var lasts = db.SortedSetPop(key, 2, Order.Descending); + Assert.Equal(2, lasts.Length); + Assert.Equal(entries[9], lasts[0]); + Assert.Equal(entries[8], lasts[1]); + Assert.Equal(7, db.SortedSetLength(key)); + } + + [Fact] + public async Task SortedSetPopMulti_Single() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SortedSetAdd(key, entries, CommandFlags.FireAndForget); + + var last = db.SortedSetPop(key, Order.Descending); + Assert.True(last.HasValue); + Assert.Equal(entries[9], last.Value); + Assert.Equal(9, db.SortedSetLength(key)); + + var firsts = db.SortedSetPop(key, 1, Order.Ascending); + Assert.Single(firsts); + Assert.Equal(entries[0], firsts[0]); + Assert.Equal(8, db.SortedSetLength(key)); + } + + [Fact] + public async Task SortedSetPopMulti_Multi_Async() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SortedSetAdd(key, entries, CommandFlags.FireAndForget); + + var last = await db.SortedSetPopAsync(key, Order.Descending).ForAwait(); + Assert.True(last.HasValue); + Assert.Equal(entries[9], last.Value); + Assert.Equal(9, db.SortedSetLength(key)); + + var moreLasts = await db.SortedSetPopAsync(key, 2, Order.Descending).ForAwait(); + Assert.Equal(2, moreLasts.Length); + Assert.Equal(entries[8], moreLasts[0]); + Assert.Equal(entries[7], moreLasts[1]); + Assert.Equal(7, db.SortedSetLength(key)); + } + + [Fact] + public async Task SortedSetPopMulti_Single_Async() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SortedSetAdd(key, entries, CommandFlags.FireAndForget); + + var first = await db.SortedSetPopAsync(key).ForAwait(); + Assert.True(first.HasValue); + Assert.Equal(entries[0], first.Value); + Assert.Equal(9, db.SortedSetLength(key)); + + var moreFirsts = await db.SortedSetPopAsync(key, 1).ForAwait(); + Assert.Single(moreFirsts); + Assert.Equal(entries[1], moreFirsts[0]); + Assert.Equal(8, db.SortedSetLength(key)); + } + + [Fact] + public async Task SortedSetPopMulti_Zero_Async() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SortedSetAdd(key, entries, CommandFlags.FireAndForget); + + var t = db.SortedSetPopAsync(key, count: 0); + Assert.True(t.IsCompleted); // sync + var arr = await t; + Assert.NotNull(arr); + Assert.Empty(arr); + + Assert.Equal(10, db.SortedSetLength(key)); + } + + [Fact] + public async Task SortedSetRandomMembers() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + var key0 = Me() + "non-existing"; + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key0, CommandFlags.FireAndForget); + db.SortedSetAdd(key, entries, CommandFlags.FireAndForget); + + // single member + var randMember = db.SortedSetRandomMember(key); + Assert.True(Array.Exists(entries, element => element.Element.Equals(randMember))); + + // with count + var randMemberArray = db.SortedSetRandomMembers(key, 5); + Assert.Equal(5, randMemberArray.Length); + randMemberArray = db.SortedSetRandomMembers(key, 15); + Assert.Equal(10, randMemberArray.Length); + randMemberArray = db.SortedSetRandomMembers(key, -5); + Assert.Equal(5, randMemberArray.Length); + randMemberArray = db.SortedSetRandomMembers(key, -15); + Assert.Equal(15, randMemberArray.Length); + + // with scores + var randMemberArray2 = db.SortedSetRandomMembersWithScores(key, 2); + Assert.Equal(2, randMemberArray2.Length); + foreach (var member in randMemberArray2) + { + Assert.Contains(member, entries); + } + + // check missing key case + randMember = db.SortedSetRandomMember(key0); + Assert.True(randMember.IsNull); + randMemberArray = db.SortedSetRandomMembers(key0, 2); + Assert.True(randMemberArray.Length == 0); + randMemberArray2 = db.SortedSetRandomMembersWithScores(key0, 2); + Assert.True(randMemberArray2.Length == 0); + } + + [Fact] + public async Task SortedSetRandomMembersAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + var key0 = Me() + "non-existing"; + + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key0, CommandFlags.FireAndForget); + db.SortedSetAdd(key, entries, CommandFlags.FireAndForget); + + var randMember = await db.SortedSetRandomMemberAsync(key); + Assert.True(Array.Exists(entries, element => element.Element.Equals(randMember))); + + // with count + var randMemberArray = await db.SortedSetRandomMembersAsync(key, 5); + Assert.Equal(5, randMemberArray.Length); + randMemberArray = await db.SortedSetRandomMembersAsync(key, 15); + Assert.Equal(10, randMemberArray.Length); + randMemberArray = await db.SortedSetRandomMembersAsync(key, -5); + Assert.Equal(5, randMemberArray.Length); + randMemberArray = await db.SortedSetRandomMembersAsync(key, -15); + Assert.Equal(15, randMemberArray.Length); + + // with scores + var randMemberArray2 = await db.SortedSetRandomMembersWithScoresAsync(key, 2); + Assert.Equal(2, randMemberArray2.Length); + foreach (var member in randMemberArray2) + { + Assert.Contains(member, entries); + } + + // check missing key case + randMember = await db.SortedSetRandomMemberAsync(key0); + Assert.True(randMember.IsNull); + randMemberArray = await db.SortedSetRandomMembersAsync(key0, 2); + Assert.True(randMemberArray.Length == 0); + randMemberArray2 = await db.SortedSetRandomMembersWithScoresAsync(key0, 2); + Assert.True(randMemberArray2.Length == 0); + } + + [Fact] + public async Task SortedSetRangeStoreByRankAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + await db.SortedSetAddAsync(sourceKey, entries, CommandFlags.FireAndForget); + var res = await db.SortedSetRangeAndStoreAsync(sourceKey, destinationKey, 0, -1); + Assert.Equal(entries.Length, res); + } + + [Fact] + public async Task SortedSetRangeStoreByRankLimitedAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + await db.SortedSetAddAsync(sourceKey, entries, CommandFlags.FireAndForget); + var res = await db.SortedSetRangeAndStoreAsync(sourceKey, destinationKey, 1, 4); + var range = await db.SortedSetRangeByRankWithScoresAsync(destinationKey); + Assert.Equal(4, res); + for (var i = 1; i < 5; i++) + { + Assert.Equal(entries[i], range[i - 1]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByScoreAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + await db.SortedSetAddAsync(sourceKey, entriesPow2, CommandFlags.FireAndForget); + var res = await db.SortedSetRangeAndStoreAsync(sourceKey, destinationKey, 64, 128, SortedSetOrder.ByScore); + var range = await db.SortedSetRangeByRankWithScoresAsync(destinationKey); + Assert.Equal(2, res); + for (var i = 6; i < 8; i++) + { + Assert.Equal(entriesPow2[i], range[i - 6]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByScoreAsyncDefault() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + await db.SortedSetAddAsync(sourceKey, entriesPow2, CommandFlags.FireAndForget); + var res = await db.SortedSetRangeAndStoreAsync(sourceKey, destinationKey, double.NegativeInfinity, double.PositiveInfinity, SortedSetOrder.ByScore); + var range = await db.SortedSetRangeByRankWithScoresAsync(destinationKey); + Assert.Equal(10, res); + for (var i = 0; i < entriesPow2.Length; i++) + { + Assert.Equal(entriesPow2[i], range[i]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByScoreAsyncLimited() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + await db.SortedSetAddAsync(sourceKey, entriesPow2, CommandFlags.FireAndForget); + var res = await db.SortedSetRangeAndStoreAsync(sourceKey, destinationKey, double.NegativeInfinity, double.PositiveInfinity, SortedSetOrder.ByScore, skip: 1, take: 6); + var range = await db.SortedSetRangeByRankWithScoresAsync(destinationKey); + Assert.Equal(6, res); + for (var i = 1; i < 7; i++) + { + Assert.Equal(entriesPow2[i], range[i - 1]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByScoreAsyncExclusiveRange() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + await db.SortedSetAddAsync(sourceKey, entriesPow2, CommandFlags.FireAndForget); + var res = await db.SortedSetRangeAndStoreAsync(sourceKey, destinationKey, 32, 256, SortedSetOrder.ByScore, exclude: Exclude.Both); + var range = await db.SortedSetRangeByRankWithScoresAsync(destinationKey); + Assert.Equal(2, res); + for (var i = 6; i < 8; i++) + { + Assert.Equal(entriesPow2[i], range[i - 6]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByScoreAsyncReverse() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + await db.SortedSetAddAsync(sourceKey, entriesPow2, CommandFlags.FireAndForget); + var res = await db.SortedSetRangeAndStoreAsync(sourceKey, destinationKey, start: double.PositiveInfinity, double.NegativeInfinity, SortedSetOrder.ByScore, order: Order.Descending); + var range = await db.SortedSetRangeByRankWithScoresAsync(destinationKey); + Assert.Equal(10, res); + for (var i = 0; i < entriesPow2.Length; i++) + { + Assert.Equal(entriesPow2[i], range[i]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByLexAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + await db.SortedSetAddAsync(sourceKey, lexEntries, CommandFlags.FireAndForget); + var res = await db.SortedSetRangeAndStoreAsync(sourceKey, destinationKey, "a", "j", SortedSetOrder.ByLex); + var range = await db.SortedSetRangeByRankWithScoresAsync(destinationKey); + Assert.Equal(10, res); + for (var i = 0; i < lexEntries.Length; i++) + { + Assert.Equal(lexEntries[i], range[i]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByLexExclusiveRangeAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + await db.SortedSetAddAsync(sourceKey, lexEntries, CommandFlags.FireAndForget); + var res = await db.SortedSetRangeAndStoreAsync(sourceKey, destinationKey, "a", "j", SortedSetOrder.ByLex, Exclude.Both); + var range = await db.SortedSetRangeByRankWithScoresAsync(destinationKey); + Assert.Equal(8, res); + for (var i = 1; i < lexEntries.Length - 1; i++) + { + Assert.Equal(lexEntries[i], range[i - 1]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByLexRevRangeAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + await db.SortedSetAddAsync(sourceKey, lexEntries, CommandFlags.FireAndForget); + var res = await db.SortedSetRangeAndStoreAsync(sourceKey, destinationKey, "j", "a", SortedSetOrder.ByLex, exclude: Exclude.None, order: Order.Descending); + var range = await db.SortedSetRangeByRankWithScoresAsync(destinationKey); + Assert.Equal(10, res); + for (var i = 0; i < lexEntries.Length; i++) + { + Assert.Equal(lexEntries[i], range[i]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByRank() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, entries, CommandFlags.FireAndForget); + var res = db.SortedSetRangeAndStore(sourceKey, destinationKey, 0, -1); + Assert.Equal(entries.Length, res); + } + + [Fact] + public async Task SortedSetRangeStoreByRankLimited() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, entries, CommandFlags.FireAndForget); + var res = db.SortedSetRangeAndStore(sourceKey, destinationKey, 1, 4); + var range = db.SortedSetRangeByRankWithScores(destinationKey); + Assert.Equal(4, res); + for (var i = 1; i < 5; i++) + { + Assert.Equal(entries[i], range[i - 1]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByScore() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, entriesPow2, CommandFlags.FireAndForget); + var res = db.SortedSetRangeAndStore(sourceKey, destinationKey, 64, 128, SortedSetOrder.ByScore); + var range = db.SortedSetRangeByRankWithScores(destinationKey); + Assert.Equal(2, res); + for (var i = 6; i < 8; i++) + { + Assert.Equal(entriesPow2[i], range[i - 6]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByScoreDefault() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, entriesPow2, CommandFlags.FireAndForget); + var res = db.SortedSetRangeAndStore(sourceKey, destinationKey, double.NegativeInfinity, double.PositiveInfinity, SortedSetOrder.ByScore); + var range = db.SortedSetRangeByRankWithScores(destinationKey); + Assert.Equal(10, res); + for (var i = 0; i < entriesPow2.Length; i++) + { + Assert.Equal(entriesPow2[i], range[i]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByScoreLimited() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, entriesPow2, CommandFlags.FireAndForget); + var res = db.SortedSetRangeAndStore(sourceKey, destinationKey, double.NegativeInfinity, double.PositiveInfinity, SortedSetOrder.ByScore, skip: 1, take: 6); + var range = db.SortedSetRangeByRankWithScores(destinationKey); + Assert.Equal(6, res); + for (var i = 1; i < 7; i++) + { + Assert.Equal(entriesPow2[i], range[i - 1]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByScoreExclusiveRange() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, entriesPow2, CommandFlags.FireAndForget); + var res = db.SortedSetRangeAndStore(sourceKey, destinationKey, 32, 256, SortedSetOrder.ByScore, exclude: Exclude.Both); + var range = db.SortedSetRangeByRankWithScores(destinationKey); + Assert.Equal(2, res); + for (var i = 6; i < 8; i++) + { + Assert.Equal(entriesPow2[i], range[i - 6]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByScoreReverse() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, entriesPow2, CommandFlags.FireAndForget); + var res = db.SortedSetRangeAndStore(sourceKey, destinationKey, start: double.PositiveInfinity, double.NegativeInfinity, SortedSetOrder.ByScore, order: Order.Descending); + var range = db.SortedSetRangeByRankWithScores(destinationKey); + Assert.Equal(10, res); + for (var i = 0; i < entriesPow2.Length; i++) + { + Assert.Equal(entriesPow2[i], range[i]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByLex() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, lexEntries, CommandFlags.FireAndForget); + var res = db.SortedSetRangeAndStore(sourceKey, destinationKey, "a", "j", SortedSetOrder.ByLex); + var range = db.SortedSetRangeByRankWithScores(destinationKey); + Assert.Equal(10, res); + for (var i = 0; i < lexEntries.Length; i++) + { + Assert.Equal(lexEntries[i], range[i]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByLexExclusiveRange() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, lexEntries, CommandFlags.FireAndForget); + var res = db.SortedSetRangeAndStore(sourceKey, destinationKey, "a", "j", SortedSetOrder.ByLex, Exclude.Both); + var range = db.SortedSetRangeByRankWithScores(destinationKey); + Assert.Equal(8, res); + for (var i = 1; i < lexEntries.Length - 1; i++) + { + Assert.Equal(lexEntries[i], range[i - 1]); + } + } + + [Fact] + public async Task SortedSetRangeStoreByLexRevRange() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, lexEntries, CommandFlags.FireAndForget); + var res = db.SortedSetRangeAndStore(sourceKey, destinationKey, "j", "a", SortedSetOrder.ByLex, Exclude.None, Order.Descending); + var range = db.SortedSetRangeByRankWithScores(destinationKey); + Assert.Equal(10, res); + for (var i = 0; i < lexEntries.Length; i++) + { + Assert.Equal(lexEntries[i], range[i]); + } + } + + [Fact] + public async Task SortedSetRangeStoreFailErroneousTake() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, lexEntries, CommandFlags.FireAndForget); + var exception = Assert.Throws(() => db.SortedSetRangeAndStore(sourceKey, destinationKey, 0, -1, take: 5)); + Assert.Equal("take", exception.ParamName); + } + + [Fact] + public async Task SortedSetRangeStoreFailExclude() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, lexEntries, CommandFlags.FireAndForget); + var exception = Assert.Throws(() => db.SortedSetRangeAndStore(sourceKey, destinationKey, 0, -1, exclude: Exclude.Both)); + Assert.Equal("exclude", exception.ParamName); + } + + [Fact] + public async Task SortedSetMultiPopSingleKey() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key); + + db.SortedSetAdd( + key, + [ + new SortedSetEntry("rays", 100), + new SortedSetEntry("yankees", 92), + new SortedSetEntry("red sox", 92), + new SortedSetEntry("blue jays", 91), + new SortedSetEntry("orioles", 52), + ]); + + var highest = db.SortedSetPop([key], 1, order: Order.Descending); + Assert.False(highest.IsNull); + Assert.Equal(key, highest.Key); + var entry = Assert.Single(highest.Entries); + Assert.Equal("rays", entry.Element); + Assert.Equal(100, entry.Score); + + var bottom2 = db.SortedSetPop([key], 2); + Assert.False(bottom2.IsNull); + Assert.Equal(key, bottom2.Key); + Assert.Equal(2, bottom2.Entries.Length); + Assert.Equal("orioles", bottom2.Entries[0].Element); + Assert.Equal(52, bottom2.Entries[0].Score); + Assert.Equal("blue jays", bottom2.Entries[1].Element); + Assert.Equal(91, bottom2.Entries[1].Score); + } + + [Fact] + public async Task SortedSetMultiPopMultiKey() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key); + + db.SortedSetAdd( + key, + [ + new SortedSetEntry("rays", 100), + new SortedSetEntry("yankees", 92), + new SortedSetEntry("red sox", 92), + new SortedSetEntry("blue jays", 91), + new SortedSetEntry("orioles", 52), + ]); + + var highest = db.SortedSetPop(["not a real key", key, "yet another not a real key"], 1, order: Order.Descending); + Assert.False(highest.IsNull); + Assert.Equal(key, highest.Key); + var entry = Assert.Single(highest.Entries); + Assert.Equal("rays", entry.Element); + Assert.Equal(100, entry.Score); + + var bottom2 = db.SortedSetPop(["not a real key", key, "yet another not a real key"], 2); + Assert.False(bottom2.IsNull); + Assert.Equal(key, bottom2.Key); + Assert.Equal(2, bottom2.Entries.Length); + Assert.Equal("orioles", bottom2.Entries[0].Element); + Assert.Equal(52, bottom2.Entries[0].Score); + Assert.Equal("blue jays", bottom2.Entries[1].Element); + Assert.Equal(91, bottom2.Entries[1].Score); + } + + [Fact] + public async Task SortedSetMultiPopNoSet() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key); + var res = db.SortedSetPop([key], 1); + Assert.True(res.IsNull); + } + + [Fact] + public async Task SortedSetMultiPopCount0() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key); + var exception = Assert.Throws(() => db.SortedSetPop([key], 0)); + Assert.Contains("ERR count should be greater than 0", exception.Message); + } + + [Fact] + public async Task SortedSetMultiPopAsync() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key); + + db.SortedSetAdd( + key, + [ + new SortedSetEntry("rays", 100), + new SortedSetEntry("yankees", 92), + new SortedSetEntry("red sox", 92), + new SortedSetEntry("blue jays", 91), + new SortedSetEntry("orioles", 52), + ]); + + var highest = await db.SortedSetPopAsync( + ["not a real key", key, "yet another not a real key"], 1, order: Order.Descending); + Assert.False(highest.IsNull); + Assert.Equal(key, highest.Key); + var entry = Assert.Single(highest.Entries); + Assert.Equal("rays", entry.Element); + Assert.Equal(100, entry.Score); + + var bottom2 = await db.SortedSetPopAsync(["not a real key", key, "yet another not a real key"], 2); + Assert.False(bottom2.IsNull); + Assert.Equal(key, bottom2.Key); + Assert.Equal(2, bottom2.Entries.Length); + Assert.Equal("orioles", bottom2.Entries[0].Element); + Assert.Equal(52, bottom2.Entries[0].Score); + Assert.Equal("blue jays", bottom2.Entries[1].Element); + Assert.Equal(91, bottom2.Entries[1].Score); + } + + [Fact] + public async Task SortedSetMultiPopEmptyKeys() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var exception = Assert.Throws(() => db.SortedSetPop(Array.Empty(), 5)); + Assert.Contains("keys must have a size of at least 1", exception.Message); + } + + [Fact] + public async Task SortedSetRangeStoreFailForReplica() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var me = Me(); + var sourceKey = $"{me}:ZSetSource"; + var destinationKey = $"{me}:ZSetDestination"; + + db.KeyDelete([sourceKey, destinationKey], CommandFlags.FireAndForget); + db.SortedSetAdd(sourceKey, lexEntries, CommandFlags.FireAndForget); + var exception = Assert.Throws(() => db.SortedSetRangeAndStore(sourceKey, destinationKey, 0, -1, flags: CommandFlags.DemandReplica)); + Assert.Contains("Command cannot be issued to a replica", exception.Message); + } + + [Fact] + public async Task SortedSetScoresSingle() + { + await using var conn = Create(require: RedisFeatures.v2_1_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string memberName = "member"; + + db.KeyDelete(key); + db.SortedSetAdd(key, memberName, 1.5); + + var score = db.SortedSetScore(key, memberName); + + Assert.NotNull(score); + Assert.Equal((double)1.5, score); + } + + [Fact] + public async Task SortedSetScoresSingleAsync() + { + await using var conn = Create(require: RedisFeatures.v2_1_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string memberName = "member"; + + await db.KeyDeleteAsync(key); + await db.SortedSetAddAsync(key, memberName, 1.5); + + var score = await db.SortedSetScoreAsync(key, memberName); + + Assert.NotNull(score); + Assert.Equal((double)1.5, score.Value); + } + + [Fact] + public async Task SortedSetScoresSingle_MissingSetStillReturnsNull() + { + await using var conn = Create(require: RedisFeatures.v2_1_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key); + + // Attempt to retrieve score for a missing set, should still return null. + var score = db.SortedSetScore(key, "bogusMemberName"); + + Assert.Null(score); + } + + [Fact] + public async Task SortedSetScoresSingle_MissingSetStillReturnsNullAsync() + { + await using var conn = Create(require: RedisFeatures.v2_1_0); + + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key); + + // Attempt to retrieve score for a missing set, should still return null. + var score = await db.SortedSetScoreAsync(key, "bogusMemberName"); + + Assert.Null(score); + } + + [Fact] + public async Task SortedSetScoresSingle_ReturnsNullForMissingMember() + { + await using var conn = Create(require: RedisFeatures.v2_1_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key); + db.SortedSetAdd(key, "member1", 1.5); + + // Attempt to retrieve score for a missing member, should return null. + var score = db.SortedSetScore(key, "bogusMemberName"); + + Assert.Null(score); + } + + [Fact] + public async Task SortedSetScoresSingle_ReturnsNullForMissingMemberAsync() + { + await using var conn = Create(require: RedisFeatures.v2_1_0); + + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key); + await db.SortedSetAddAsync(key, "member1", 1.5); + + // Attempt to retrieve score for a missing member, should return null. + var score = await db.SortedSetScoreAsync(key, "bogusMemberName"); + + Assert.Null(score); + } + + [Fact] + public async Task SortedSetScoresMultiple() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string member1 = "member1", + member2 = "member2", + member3 = "member3"; + + db.KeyDelete(key); + db.SortedSetAdd(key, member1, 1.5); + db.SortedSetAdd(key, member2, 1.75); + db.SortedSetAdd(key, member3, 2); + + var scores = db.SortedSetScores(key, [member1, member2, member3]); + + Assert.NotNull(scores); + Assert.Equal(3, scores.Length); + Assert.Equal((double)1.5, scores[0]); + Assert.Equal((double)1.75, scores[1]); + Assert.Equal(2, scores[2]); + } + + [Fact] + public async Task SortedSetScoresMultipleAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string member1 = "member1", + member2 = "member2", + member3 = "member3"; + + await db.KeyDeleteAsync(key); + await db.SortedSetAddAsync(key, member1, 1.5); + await db.SortedSetAddAsync(key, member2, 1.75); + await db.SortedSetAddAsync(key, member3, 2); + + var scores = await db.SortedSetScoresAsync(key, [member1, member2, member3]); + + Assert.NotNull(scores); + Assert.Equal(3, scores.Length); + Assert.Equal((double)1.5, scores[0]); + Assert.Equal((double)1.75, scores[1]); + Assert.Equal(2, scores[2]); + } + + [Fact] + public async Task SortedSetScoresMultiple_ReturnsNullItemsForMissingSet() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key); + + // Missing set but should still return an array of nulls. + var scores = db.SortedSetScores(key, ["bogus1", "bogus2", "bogus3"]); + + Assert.NotNull(scores); + Assert.Equal(3, scores.Length); + Assert.Null(scores[0]); + Assert.Null(scores[1]); + Assert.Null(scores[2]); + } + + [Fact] + public async Task SortedSetScoresMultiple_ReturnsNullItemsForMissingSetAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key); + + // Missing set but should still return an array of nulls. + var scores = await db.SortedSetScoresAsync(key, ["bogus1", "bogus2", "bogus3"]); + + Assert.NotNull(scores); + Assert.Equal(3, scores.Length); + Assert.Null(scores[0]); + Assert.Null(scores[1]); + Assert.Null(scores[2]); + } + + [Fact] + public async Task SortedSetScoresMultiple_ReturnsScoresAndNullItems() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string member1 = "member1", + member2 = "member2", + member3 = "member3", + bogusMember = "bogusMember"; + + db.KeyDelete(key); + + db.SortedSetAdd(key, member1, 1.5); + db.SortedSetAdd(key, member2, 1.75); + db.SortedSetAdd(key, member3, 2); + + var scores = db.SortedSetScores(key, [member1, bogusMember, member2, member3]); + + Assert.NotNull(scores); + Assert.Equal(4, scores.Length); + Assert.Null(scores[1]); + Assert.Equal((double)1.5, scores[0]); + Assert.Equal((double)1.75, scores[2]); + Assert.Equal(2, scores[3]); + } + + [Fact] + public async Task SortedSetScoresMultiple_ReturnsScoresAndNullItemsAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string member1 = "member1", + member2 = "member2", + member3 = "member3", + bogusMember = "bogusMember"; + + await db.KeyDeleteAsync(key); + + await db.SortedSetAddAsync(key, member1, 1.5); + await db.SortedSetAddAsync(key, member2, 1.75); + await db.SortedSetAddAsync(key, member3, 2); + + var scores = await db.SortedSetScoresAsync(key, [member1, bogusMember, member2, member3]); + + Assert.NotNull(scores); + Assert.Equal(4, scores.Length); + Assert.Null(scores[1]); + Assert.Equal((double)1.5, scores[0]); + Assert.Equal((double)1.75, scores[2]); + Assert.Equal(2, scores[3]); + } + + [Fact] + public async Task SortedSetUpdate() + { + await using var conn = Create(require: RedisFeatures.v3_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + var member = "a"; + var values = new SortedSetEntry[] { new SortedSetEntry(member, 5) }; + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SortedSetAdd(key, member, 2); + + Assert.True(db.SortedSetUpdate(key, member, 1)); + Assert.Equal(1, db.SortedSetUpdate(key, values)); + + Assert.True(await db.SortedSetUpdateAsync(key, member, 1)); + Assert.Equal(1, await db.SortedSetUpdateAsync(key, values)); + } +} diff --git a/tests/StackExchange.Redis.Tests/SortedSetWhenTests.cs b/tests/StackExchange.Redis.Tests/SortedSetWhenTests.cs new file mode 100644 index 000000000..17c587079 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SortedSetWhenTests.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class SortedSetWhenTest(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task GreaterThanLessThan() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + var member = "a"; + db.KeyDelete(key, CommandFlags.FireAndForget); + db.SortedSetAdd(key, member, 2); + + Assert.True(db.SortedSetUpdate(key, member, 5, when: SortedSetWhen.GreaterThan)); + Assert.False(db.SortedSetUpdate(key, member, 1, when: SortedSetWhen.GreaterThan)); + Assert.True(db.SortedSetUpdate(key, member, 1, when: SortedSetWhen.LessThan)); + Assert.False(db.SortedSetUpdate(key, member, 5, when: SortedSetWhen.LessThan)); + } + + [Fact] + public async Task IllegalCombinations() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + var member = "a"; + db.KeyDelete(key, CommandFlags.FireAndForget); + + Assert.Throws(() => db.SortedSetAdd(key, member, 5, when: SortedSetWhen.LessThan | SortedSetWhen.GreaterThan)); + Assert.Throws(() => db.SortedSetAdd(key, member, 5, when: SortedSetWhen.Exists | SortedSetWhen.NotExists)); + Assert.Throws(() => db.SortedSetAdd(key, member, 5, when: SortedSetWhen.GreaterThan | SortedSetWhen.NotExists)); + Assert.Throws(() => db.SortedSetAdd(key, member, 5, when: SortedSetWhen.LessThan | SortedSetWhen.NotExists)); + } +} diff --git a/tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj b/tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj new file mode 100644 index 000000000..f6e38236b --- /dev/null +++ b/tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj @@ -0,0 +1,35 @@ + + + net481;net8.0 + Exe + StackExchange.Redis.Tests + true + true + full + enable + true + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/StackExchange.Redis.Tests/StreamTests.cs b/tests/StackExchange.Redis.Tests/StreamTests.cs new file mode 100644 index 000000000..58d2bb1fb --- /dev/null +++ b/tests/StackExchange.Redis.Tests/StreamTests.cs @@ -0,0 +1,2306 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class StreamTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + public override string Me([CallerFilePath] string? filePath = null, [CallerMemberName] string? caller = null) => + base.Me(filePath, caller) + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + + [Fact] + public async Task IsStreamType() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + db.StreamAdd(key, "field1", "value1"); + + var keyType = db.KeyType(key); + + Assert.Equal(RedisType.Stream, keyType); + } + + [Fact] + public async Task StreamAddSinglePairWithAutoId() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + var messageId = db.StreamAdd(key, "field1", "value1"); + + Assert.True(messageId != RedisValue.Null && ((string?)messageId)?.Length > 0); + } + + [Fact] + public async Task StreamAddMultipleValuePairsWithAutoId() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + var fields = new[] + { + new NameValueEntry("field1", "value1"), + new NameValueEntry("field2", "value2"), + }; + + var messageId = db.StreamAdd(key, fields); + + var entries = db.StreamRange(key); + + Assert.Single(entries); + Assert.Equal(messageId, entries[0].Id); + var vals = entries[0].Values; + Assert.NotNull(vals); + Assert.Equal(2, vals.Length); + Assert.Equal("field1", vals[0].Name); + Assert.Equal("value1", vals[0].Value); + Assert.Equal("field2", vals[1].Name); + Assert.Equal("value2", vals[1].Value); + } + + [Fact] + public async Task StreamAddWithManualId() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + const string id = "42-0"; + var key = Me(); + + var messageId = db.StreamAdd(key, "field1", "value1", id); + + Assert.Equal(id, messageId); + } + + [Fact] + public async Task StreamAddMultipleValuePairsWithManualId() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + const string id = "42-0"; + var key = Me(); + + var fields = new[] + { + new NameValueEntry("field1", "value1"), + new NameValueEntry("field2", "value2"), + }; + + var messageId = db.StreamAdd(key, fields, id); + var entries = db.StreamRange(key); + + Assert.Equal(id, messageId); + Assert.NotNull(entries); + Assert.Single(entries); + Assert.Equal(id, entries[0].Id); + } + + [Fact] + public async Task StreamAutoClaim_MissingKey() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer = "consumer"; + + db.KeyDelete(key); + + var ex = Assert.Throws(() => db.StreamAutoClaim(key, group, consumer, 0, "0-0")); + Assert.StartsWith("NOGROUP No such key", ex.Message); + + ex = await Assert.ThrowsAsync(() => db.StreamAutoClaimAsync(key, group, consumer, 0, "0-0")); + Assert.StartsWith("NOGROUP No such key", ex.Message); + } + + [Fact] + public async Task StreamAutoClaim_ClaimsPendingMessages() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer1 = "c1", + consumer2 = "c2"; + + // Create Consumer Group, add messages, and read messages into a consumer. + _ = StreamAutoClaim_PrepareTestData(db, key, group, consumer1); + + // Claim any pending messages and reassign them to consumer2. + var result = db.StreamAutoClaim(key, group, consumer2, 0, "0-0"); + + Assert.Equal("0-0", result.NextStartId); + Assert.NotEmpty(result.ClaimedEntries); + Assert.Empty(result.DeletedIds); + Assert.Equal(2, result.ClaimedEntries.Length); + Assert.Equal("value1", result.ClaimedEntries[0].Values[0].Value); + Assert.Equal("value2", result.ClaimedEntries[1].Values[0].Value); + } + + [Fact] + public async Task StreamAutoClaim_ClaimsPendingMessagesAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer1 = "c1", + consumer2 = "c2"; + + // Create Consumer Group, add messages, and read messages into a consumer. + _ = StreamAutoClaim_PrepareTestData(db, key, group, consumer1); + + // Claim any pending messages and reassign them to consumer2. + var result = await db.StreamAutoClaimAsync(key, group, consumer2, 0, "0-0"); + + Assert.Equal("0-0", result.NextStartId); + Assert.NotEmpty(result.ClaimedEntries); + Assert.Empty(result.DeletedIds); + Assert.Equal(2, result.ClaimedEntries.Length); + Assert.Equal("value1", result.ClaimedEntries[0].Values[0].Value); + Assert.Equal("value2", result.ClaimedEntries[1].Values[0].Value); + } + + [Fact] + public async Task StreamAutoClaim_ClaimsSingleMessageWithCountOption() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer1 = "c1", + consumer2 = "c2"; + + // Create Consumer Group, add messages, and read messages into a consumer. + var messageIds = StreamAutoClaim_PrepareTestData(db, key, group, consumer1); + + // Claim a single pending message and reassign it to consumer2. + var result = db.StreamAutoClaim(key, group, consumer2, 0, "0-0", count: 1); + + // Should be the second message ID from the call to prepare. + Assert.Equal(messageIds[1], result.NextStartId); + Assert.NotEmpty(result.ClaimedEntries); + Assert.Empty(result.DeletedIds); + Assert.Single(result.ClaimedEntries); + Assert.Equal("value1", result.ClaimedEntries[0].Values[0].Value); + } + + [Fact] + public async Task StreamAutoClaim_ClaimsSingleMessageWithCountOptionIdsOnly() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer1 = "c1", + consumer2 = "c2"; + + // Create Consumer Group, add messages, and read messages into a consumer. + var messageIds = StreamAutoClaim_PrepareTestData(db, key, group, consumer1); + + // Claim a single pending message and reassign it to consumer2. + var result = db.StreamAutoClaimIdsOnly(key, group, consumer2, 0, "0-0", count: 1); + + // Should be the second message ID from the call to prepare. + Assert.Equal(messageIds[1], result.NextStartId); + Assert.NotEmpty(result.ClaimedIds); + Assert.Single(result.ClaimedIds); + Assert.Equal(messageIds[0], result.ClaimedIds[0]); + Assert.Empty(result.DeletedIds); + } + + [Fact] + public async Task StreamAutoClaim_ClaimsSingleMessageWithCountOptionAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer1 = "c1", + consumer2 = "c2"; + + // Create Consumer Group, add messages, and read messages into a consumer. + var messageIds = StreamAutoClaim_PrepareTestData(db, key, group, consumer1); + + // Claim a single pending message and reassign it to consumer2. + var result = await db.StreamAutoClaimAsync(key, group, consumer2, 0, "0-0", count: 1); + + // Should be the second message ID from the call to prepare. + Assert.Equal(messageIds[1], result.NextStartId); + Assert.NotEmpty(result.ClaimedEntries); + Assert.Empty(result.DeletedIds); + Assert.Single(result.ClaimedEntries); + Assert.Equal("value1", result.ClaimedEntries[0].Values[0].Value); + } + + [Fact] + public async Task StreamAutoClaim_ClaimsSingleMessageWithCountOptionIdsOnlyAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer1 = "c1", + consumer2 = "c2"; + + // Create Consumer Group, add messages, and read messages into a consumer. + var messageIds = StreamAutoClaim_PrepareTestData(db, key, group, consumer1); + + // Claim a single pending message and reassign it to consumer2. + var result = await db.StreamAutoClaimIdsOnlyAsync(key, group, consumer2, 0, "0-0", count: 1); + + // Should be the second message ID from the call to prepare. + Assert.Equal(messageIds[1], result.NextStartId); + Assert.NotEmpty(result.ClaimedIds); + Assert.Single(result.ClaimedIds); + Assert.Equal(messageIds[0], result.ClaimedIds[0]); + Assert.Empty(result.DeletedIds); + } + + [Fact] + public async Task StreamAutoClaim_IncludesDeletedMessageId() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer1 = "c1", + consumer2 = "c2"; + + // Create Consumer Group, add messages, and read messages into a consumer. + var messageIds = StreamAutoClaim_PrepareTestData(db, key, group, consumer1); + + // Delete one of the messages, it should be included in the deleted message ID array. + db.StreamDelete(key, [messageIds[0]]); + + // Claim a single pending message and reassign it to consumer2. + var result = db.StreamAutoClaim(key, group, consumer2, 0, "0-0", count: 2); + + Assert.Equal("0-0", result.NextStartId); + Assert.NotEmpty(result.ClaimedEntries); + Assert.NotEmpty(result.DeletedIds); + Assert.Single(result.ClaimedEntries); + Assert.Single(result.DeletedIds); + Assert.Equal(messageIds[0], result.DeletedIds[0]); + } + + [Fact] + public async Task StreamAutoClaim_IncludesDeletedMessageIdAsync() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer1 = "c1", + consumer2 = "c2"; + + // Create Consumer Group, add messages, and read messages into a consumer. + var messageIds = StreamAutoClaim_PrepareTestData(db, key, group, consumer1); + + // Delete one of the messages, it should be included in the deleted message ID array. + db.StreamDelete(key, [messageIds[0]]); + + // Claim a single pending message and reassign it to consumer2. + var result = await db.StreamAutoClaimAsync(key, group, consumer2, 0, "0-0", count: 2); + + Assert.Equal("0-0", result.NextStartId); + Assert.NotEmpty(result.ClaimedEntries); + Assert.NotEmpty(result.DeletedIds); + Assert.Single(result.ClaimedEntries); + Assert.Single(result.DeletedIds); + Assert.Equal(messageIds[0], result.DeletedIds[0]); + } + + [Fact] + public async Task StreamAutoClaim_NoMessagesToClaim() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup"; + + // Create the group. + db.KeyDelete(key); + db.StreamCreateConsumerGroup(key, group, createStream: true); + + // **Don't add any messages to the stream** + + // Claim any pending messages (there aren't any) and reassign them to consumer2. + var result = db.StreamAutoClaim(key, group, "consumer1", 0, "0-0"); + + // Claimed entries should be empty + Assert.Equal("0-0", result.NextStartId); + Assert.Empty(result.ClaimedEntries); + Assert.Empty(result.DeletedIds); + } + + [Fact] + public async Task StreamAutoClaim_NoMessagesToClaimAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup"; + + // Create the group. + db.KeyDelete(key); + db.StreamCreateConsumerGroup(key, group, createStream: true); + + // **Don't add any messages to the stream** + + // Claim any pending messages (there aren't any) and reassign them to consumer2. + var result = await db.StreamAutoClaimAsync(key, group, "consumer1", 0, "0-0"); + + // Claimed entries should be empty + Assert.Equal("0-0", result.NextStartId); + Assert.Empty(result.ClaimedEntries); + Assert.Empty(result.DeletedIds); + } + + [Fact] + public async Task StreamAutoClaim_NoMessageMeetsMinIdleTime() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer1 = "c1", + consumer2 = "c2"; + + // Create Consumer Group, add messages, and read messages into a consumer. + _ = StreamAutoClaim_PrepareTestData(db, key, group, consumer1); + + // Claim messages idle for more than 5 minutes, should return an empty array. + var result = db.StreamAutoClaim(key, group, consumer2, 300000, "0-0"); + + Assert.Equal("0-0", result.NextStartId); + Assert.Empty(result.ClaimedEntries); + Assert.Empty(result.DeletedIds); + } + + [Fact] + public async Task StreamAutoClaim_NoMessageMeetsMinIdleTimeAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer1 = "c1", + consumer2 = "c2"; + + // Create Consumer Group, add messages, and read messages into a consumer. + _ = StreamAutoClaim_PrepareTestData(db, key, group, consumer1); + + // Claim messages idle for more than 5 minutes, should return an empty array. + var result = await db.StreamAutoClaimAsync(key, group, consumer2, 300000, "0-0"); + + Assert.Equal("0-0", result.NextStartId); + Assert.Empty(result.ClaimedEntries); + Assert.Empty(result.DeletedIds); + } + + [Fact] + public async Task StreamAutoClaim_ReturnsMessageIdOnly() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer1 = "c1", + consumer2 = "c2"; + + // Create Consumer Group, add messages, and read messages into a consumer. + var messageIds = StreamAutoClaim_PrepareTestData(db, key, group, consumer1); + + // Claim any pending messages and reassign them to consumer2. + var result = db.StreamAutoClaimIdsOnly(key, group, consumer2, 0, "0-0"); + + Assert.Equal("0-0", result.NextStartId); + Assert.NotEmpty(result.ClaimedIds); + Assert.Empty(result.DeletedIds); + Assert.Equal(2, result.ClaimedIds.Length); + Assert.Equal(messageIds[0], result.ClaimedIds[0]); + Assert.Equal(messageIds[1], result.ClaimedIds[1]); + } + + [Fact] + public async Task StreamAutoClaim_ReturnsMessageIdOnlyAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var key = Me(); + var db = conn.GetDatabase(); + const string group = "consumerGroup", + consumer1 = "c1", + consumer2 = "c2"; + + // Create Consumer Group, add messages, and read messages into a consumer. + var messageIds = StreamAutoClaim_PrepareTestData(db, key, group, consumer1); + + // Claim any pending messages and reassign them to consumer2. + var result = await db.StreamAutoClaimIdsOnlyAsync(key, group, consumer2, 0, "0-0"); + + Assert.Equal("0-0", result.NextStartId); + Assert.NotEmpty(result.ClaimedIds); + Assert.Empty(result.DeletedIds); + Assert.Equal(2, result.ClaimedIds.Length); + Assert.Equal(messageIds[0], result.ClaimedIds[0]); + Assert.Equal(messageIds[1], result.ClaimedIds[1]); + } + + private static RedisValue[] StreamAutoClaim_PrepareTestData(IDatabase db, RedisKey key, RedisValue group, RedisValue consumer) + { + // Create the group. + db.KeyDelete(key); + db.StreamCreateConsumerGroup(key, group, createStream: true); + + // Add some messages + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + + // Read the messages into the "c1" + db.StreamReadGroup(key, group, consumer); + + return [id1, id2]; + } + + [Fact] + public async Task StreamConsumerGroupSetId() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer = "consumer"; + + // Create a stream + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + + // Create a group and set the position to deliver new messages only. + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.NewMessages); + + // Read into the group, expect nothing + var firstRead = db.StreamReadGroup(key, groupName, consumer, StreamPosition.NewMessages); + + // Reset the ID back to read from the beginning. + db.StreamConsumerGroupSetPosition(key, groupName, StreamPosition.Beginning); + + var secondRead = db.StreamReadGroup(key, groupName, consumer, StreamPosition.NewMessages); + + Assert.NotNull(firstRead); + Assert.NotNull(secondRead); + Assert.Empty(firstRead); + Assert.Equal(2, secondRead.Length); + } + + [Fact] + public async Task StreamConsumerGroupWithNoConsumers() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group"; + + // Create a stream + db.StreamAdd(key, "field1", "value1"); + + // Create a group + db.StreamCreateConsumerGroup(key, groupName, "0-0"); + + // Query redis for the group consumers, expect an empty list in response. + var consumers = db.StreamConsumerInfo(key, groupName); + + Assert.Empty(consumers); + } + + [Fact] + public async Task StreamCreateConsumerGroup() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group"; + + // Create a stream + db.StreamAdd(key, "field1", "value1"); + + // Create a group + var result = db.StreamCreateConsumerGroup(key, groupName, StreamPosition.Beginning); + + Assert.True(result); + } + + [Fact] + public async Task StreamCreateConsumerGroupBeforeCreatingStream() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + // Ensure the key doesn't exist. + var keyExistsBeforeCreate = db.KeyExists(key); + + // The 'createStream' parameter is 'true' by default. + var groupCreated = db.StreamCreateConsumerGroup(key, "consumerGroup", StreamPosition.NewMessages); + + var keyExistsAfterCreate = db.KeyExists(key); + + Assert.False(keyExistsBeforeCreate); + Assert.True(groupCreated); + Assert.True(keyExistsAfterCreate); + } + + [Fact] + public async Task StreamCreateConsumerGroupFailsIfKeyDoesntExist() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + // Pass 'false' for 'createStream' to ensure that an + // exception is thrown when the stream doesn't exist. + Assert.ThrowsAny(() => db.StreamCreateConsumerGroup( + key, + "consumerGroup", + StreamPosition.NewMessages, + createStream: false)); + } + + [Fact] + public async Task StreamCreateConsumerGroupSucceedsWhenKeyExists() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.StreamAdd(key, "f1", "v1"); + + // Pass 'false' for 'createStream', should create the consumer group + // without issue since the stream already exists. + var groupCreated = db.StreamCreateConsumerGroup( + key, + "consumerGroup", + StreamPosition.NewMessages, + createStream: false); + + Assert.True(groupCreated); + } + + [Fact] + public async Task StreamConsumerGroupReadOnlyNewMessagesWithEmptyResponse() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group"; + + // Create a stream + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + + // Create a group. + db.StreamCreateConsumerGroup(key, groupName); + + // Read, expect no messages + var entries = db.StreamReadGroup(key, groupName, "test_consumer", "0-0"); + + Assert.Empty(entries); + } + + [Fact] + public async Task StreamConsumerGroupReadFromStreamBeginning() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group"; + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.Beginning); + + var entries = db.StreamReadGroup(key, groupName, "test_consumer", StreamPosition.NewMessages); + + Assert.Equal(2, entries.Length); + Assert.True(id1 == entries[0].Id); + Assert.True(id2 == entries[1].Id); + } + + [Fact] + public async Task StreamConsumerGroupReadFromStreamBeginningWithCount() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group"; + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + _ = db.StreamAdd(key, "field4", "value4"); + + // Start reading after id1. + db.StreamCreateConsumerGroup(key, groupName, id1); + + var entries = db.StreamReadGroup(key, groupName, "test_consumer", StreamPosition.NewMessages, 2); + + // Ensure we only received the requested count and that the IDs match the expected values. + Assert.Equal(2, entries.Length); + Assert.True(id2 == entries[0].Id); + Assert.True(id3 == entries[1].Id); + } + + [Fact] + public async Task StreamConsumerGroupAcknowledgeMessage() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer = "test_consumer"; + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + RedisValue notexist = "0-0"; + var id4 = db.StreamAdd(key, "field4", "value4"); + + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.Beginning); + + // Read all 4 messages, they will be assigned to the consumer + var entries = db.StreamReadGroup(key, groupName, consumer, StreamPosition.NewMessages); + Assert.Equal(4, entries.Length); + + // Send XACK for 3 of the messages + + // Single message Id overload. + var oneAck = db.StreamAcknowledge(key, groupName, id1); + Assert.Equal(1, oneAck); + + var nack = db.StreamAcknowledge(key, groupName, notexist); + Assert.Equal(0, nack); + + // Multiple message Id overload. + var twoAck = db.StreamAcknowledge(key, groupName, [id3, notexist, id4]); + + // Read the group again, it should only return the unacknowledged message. + var notAcknowledged = db.StreamReadGroup(key, groupName, consumer, "0-0"); + + Assert.Equal(2, twoAck); + Assert.Single(notAcknowledged); + Assert.Equal(id2, notAcknowledged[0].Id); + } + + [Theory] + [InlineData(StreamTrimMode.KeepReferences)] + [InlineData(StreamTrimMode.DeleteReferences)] + [InlineData(StreamTrimMode.Acknowledged)] + public void StreamConsumerGroupAcknowledgeAndDeleteMessage(StreamTrimMode mode) + { + using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + + var db = conn.GetDatabase(); + var key = Me() + ":" + mode; + const string groupName = "test_group", + consumer = "test_consumer"; + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + RedisValue notexist = "0-0"; + var id4 = db.StreamAdd(key, "field4", "value4"); + + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.Beginning); + + // Read all 4 messages, they will be assigned to the consumer + var entries = db.StreamReadGroup(key, groupName, consumer, StreamPosition.NewMessages); + Assert.Equal(4, entries.Length); + + // Send XACK for 3 of the messages + + // Single message Id overload. + var oneAck = db.StreamAcknowledgeAndDelete(key, groupName, mode, id1); + Assert.Equal(StreamTrimResult.Deleted, oneAck); + + StreamTrimResult nack = db.StreamAcknowledgeAndDelete(key, groupName, mode, notexist); + Assert.Equal(StreamTrimResult.NotFound, nack); + + // Multiple message Id overload. + RedisValue[] ids = new[] { id3, notexist, id4 }; + var twoAck = db.StreamAcknowledgeAndDelete(key, groupName, mode, ids); + + // Read the group again, it should only return the unacknowledged message. + var notAcknowledged = db.StreamReadGroup(key, groupName, consumer, "0-0"); + + Assert.Equal(3, twoAck.Length); + Assert.Equal(StreamTrimResult.Deleted, twoAck[0]); + Assert.Equal(StreamTrimResult.NotFound, twoAck[1]); + Assert.Equal(StreamTrimResult.Deleted, twoAck[2]); + + Assert.Single(notAcknowledged); + Assert.Equal(id2, notAcknowledged[0].Id); + } + + [Fact] + public async Task StreamConsumerGroupClaimMessages() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer1 = "test_consumer_1", + consumer2 = "test_consumer_2"; + + _ = db.StreamAdd(key, "field1", "value1"); + _ = db.StreamAdd(key, "field2", "value2"); + _ = db.StreamAdd(key, "field3", "value3"); + _ = db.StreamAdd(key, "field4", "value4"); + + db.StreamCreateConsumerGroup(key, groupName, "0-0"); + + // Read a single message into the first consumer. + db.StreamReadGroup(key, groupName, consumer1, count: 1); + + // Read the remaining messages into the second consumer. + db.StreamReadGroup(key, groupName, consumer2); + + // Claim the 3 messages consumed by consumer2 for consumer1. + + // Get the pending messages for consumer2. + var pendingMessages = db.StreamPendingMessages( + key, + groupName, + 10, + consumer2); + + // Claim the messages for consumer1. + var messages = db.StreamClaim( + key, + groupName, + consumer1, + 0, // Min message idle time + messageIds: pendingMessages.Select(pm => pm.MessageId).ToArray()); + + // Now see how many messages are pending for each consumer + var pendingSummary = db.StreamPending(key, groupName); + + Assert.NotNull(pendingSummary.Consumers); + Assert.Single(pendingSummary.Consumers); + Assert.Equal(4, pendingSummary.Consumers[0].PendingMessageCount); + Assert.Equal(pendingMessages.Length, messages.Length); + } + + [Fact] + public async Task StreamConsumerGroupClaimMessagesReturningIds() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer1 = "test_consumer_1", + consumer2 = "test_consumer_2"; + + _ = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.Beginning); + + // Read a single message into the first consumer. + _ = db.StreamReadGroup(key, groupName, consumer1, StreamPosition.NewMessages, 1); + + // Read the remaining messages into the second consumer. + _ = db.StreamReadGroup(key, groupName, consumer2); + + // Claim the 3 messages consumed by consumer2 for consumer1. + + // Get the pending messages for consumer2. + var pendingMessages = db.StreamPendingMessages( + key, + groupName, + 10, + consumer2); + + // Claim the messages for consumer1. + var messageIds = db.StreamClaimIdsOnly( + key, + groupName, + consumer1, + 0, // Min message idle time + messageIds: pendingMessages.Select(pm => pm.MessageId).ToArray()); + + // We should get an array of 3 message IDs. + Assert.Equal(3, messageIds.Length); + Assert.Equal(id2, messageIds[0]); + Assert.Equal(id3, messageIds[1]); + Assert.Equal(id4, messageIds[2]); + } + + [Fact] + public async Task StreamConsumerGroupReadMultipleOneReadBeginningOneReadNew() + { + // Create a group for each stream. One set to read from the beginning of the + // stream and the other to begin reading only new messages. + + // Ask redis to read from the beginning of both stream, expect messages + // for only the stream set to read from the beginning. + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + const string groupName = "test_group"; + var stream1 = Me() + "a"; + var stream2 = Me() + "b"; + + db.StreamAdd(stream1, "field1-1", "value1-1"); + db.StreamAdd(stream1, "field1-2", "value1-2"); + + db.StreamAdd(stream2, "field2-1", "value2-1"); + db.StreamAdd(stream2, "field2-2", "value2-2"); + db.StreamAdd(stream2, "field2-3", "value2-3"); + + // stream1 set up to read only new messages. + db.StreamCreateConsumerGroup(stream1, groupName, StreamPosition.NewMessages); + + // stream2 set up to read from the beginning of the stream + db.StreamCreateConsumerGroup(stream2, groupName, StreamPosition.Beginning); + + // Read for both streams from the beginning. We shouldn't get anything back for stream1. + var pairs = new[] + { + // StreamPosition.NewMessages will send ">" which indicates "Undelivered" messages. + new StreamPosition(stream1, StreamPosition.NewMessages), + new StreamPosition(stream2, StreamPosition.NewMessages), + }; + + var streams = db.StreamReadGroup(pairs, groupName, "test_consumer"); + + Assert.NotNull(streams); + Assert.Single(streams); + Assert.Equal(stream2, streams[0].Key); + Assert.Equal(3, streams[0].Entries.Length); + } + + [Fact] + public async Task StreamConsumerGroupReadMultipleOnlyNewMessagesExpectNoResult() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + const string groupName = "test_group"; + var stream1 = Me() + "a"; + var stream2 = Me() + "b"; + + db.StreamAdd(stream1, "field1-1", "value1-1"); + db.StreamAdd(stream2, "field2-1", "value2-1"); + + // set both streams to read only new messages (default behavior). + db.StreamCreateConsumerGroup(stream1, groupName); + db.StreamCreateConsumerGroup(stream2, groupName); + + // We shouldn't get anything for either stream. + var pairs = new[] + { + new StreamPosition(stream1, StreamPosition.Beginning), + new StreamPosition(stream2, StreamPosition.Beginning), + }; + + var streams = db.StreamReadGroup(pairs, groupName, "test_consumer"); + + Assert.NotNull(streams); + Assert.Equal(2, streams.Length); + Assert.Empty(streams[0].Entries); + Assert.Empty(streams[1].Entries); + } + + [Fact] + public async Task StreamConsumerGroupReadMultipleOnlyNewMessagesExpect1Result() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + const string groupName = "test_group"; + var stream1 = Me() + "a"; + var stream2 = Me() + "b"; + + // These messages won't be read. + db.StreamAdd(stream1, "field1-1", "value1-1"); + db.StreamAdd(stream2, "field2-1", "value2-1"); + + // set both streams to read only new messages (default behavior). + db.StreamCreateConsumerGroup(stream1, groupName); + db.StreamCreateConsumerGroup(stream2, groupName); + + // We should read these though. + var id1 = db.StreamAdd(stream1, "field1-2", "value1-2"); + var id2 = db.StreamAdd(stream2, "field2-2", "value2-2"); + + // Read the new messages (messages created after the group was created). + var pairs = new[] + { + new StreamPosition(stream1, StreamPosition.NewMessages), + new StreamPosition(stream2, StreamPosition.NewMessages), + }; + + var streams = db.StreamReadGroup(pairs, groupName, "test_consumer"); + + Assert.NotNull(streams); + Assert.Equal(2, streams.Length); + Assert.Single(streams[0].Entries); + Assert.Single(streams[1].Entries); + Assert.Equal(id1, streams[0].Entries[0].Id); + Assert.Equal(id2, streams[1].Entries[0].Id); + } + + [Fact] + public async Task StreamConsumerGroupReadMultipleRestrictCount() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + const string groupName = "test_group"; + var stream1 = Me() + "a"; + var stream2 = Me() + "b"; + + var id1_1 = db.StreamAdd(stream1, "field1-1", "value1-1"); + var id1_2 = db.StreamAdd(stream1, "field1-2", "value1-2"); + + var id2_1 = db.StreamAdd(stream2, "field2-1", "value2-1"); + _ = db.StreamAdd(stream2, "field2-2", "value2-2"); + _ = db.StreamAdd(stream2, "field2-3", "value2-3"); + + // Set the initial read point in each stream, *after* the first ID in both streams. + db.StreamCreateConsumerGroup(stream1, groupName, id1_1); + db.StreamCreateConsumerGroup(stream2, groupName, id2_1); + + var pairs = new[] + { + // Read after the first id in both streams + new StreamPosition(stream1, StreamPosition.NewMessages), + new StreamPosition(stream2, StreamPosition.NewMessages), + }; + + // Restrict the count to 2 (expect only 1 message from first stream, 2 from the second). + var streams = db.StreamReadGroup(pairs, groupName, "test_consumer", 2); + + Assert.NotNull(streams); + Assert.Equal(2, streams.Length); + Assert.Single(streams[0].Entries); + Assert.Equal(2, streams[1].Entries.Length); + Assert.Equal(id1_2, streams[0].Entries[0].Id); + } + + [Fact] + public async Task StreamConsumerGroupViewPendingInfoNoConsumers() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group"; + + db.StreamAdd(key, "field1", "value1"); + + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.Beginning); + + var pendingInfo = db.StreamPending(key, groupName); + + Assert.Equal(0, pendingInfo.PendingMessageCount); + Assert.Equal(RedisValue.Null, pendingInfo.LowestPendingMessageId); + Assert.Equal(RedisValue.Null, pendingInfo.HighestPendingMessageId); + Assert.NotNull(pendingInfo.Consumers); + Assert.Empty(pendingInfo.Consumers); + } + + [Fact] + public async Task StreamConsumerGroupViewPendingInfoWhenNothingPending() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group"; + + db.StreamAdd(key, "field1", "value1"); + + db.StreamCreateConsumerGroup(key, groupName, "0-0"); + + var pendingMessages = db.StreamPendingMessages( + key, + groupName, + 10, + consumerName: RedisValue.Null); + + Assert.NotNull(pendingMessages); + Assert.Empty(pendingMessages); + } + + [Fact] + public async Task StreamConsumerGroupViewPendingInfoSummary() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer1 = "test_consumer_1", + consumer2 = "test_consumer_2"; + + var id1 = db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.Beginning); + + // Read a single message into the first consumer. + db.StreamReadGroup(key, groupName, consumer1, StreamPosition.NewMessages, 1); + + // Read the remaining messages into the second consumer. + db.StreamReadGroup(key, groupName, consumer2); + + var pendingInfo = db.StreamPending(key, groupName); + + Assert.Equal(4, pendingInfo.PendingMessageCount); + Assert.Equal(id1, pendingInfo.LowestPendingMessageId); + Assert.Equal(id4, pendingInfo.HighestPendingMessageId); + Assert.Equal(2, pendingInfo.Consumers.Length); + + var consumer1Count = pendingInfo.Consumers.First(c => c.Name == consumer1).PendingMessageCount; + var consumer2Count = pendingInfo.Consumers.First(c => c.Name == consumer2).PendingMessageCount; + + Assert.Equal(1, consumer1Count); + Assert.Equal(3, consumer2Count); + } + + [Fact] + public async Task StreamConsumerGroupViewPendingMessageInfo() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer1 = "test_consumer_1", + consumer2 = "test_consumer_2"; + + var id1 = db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + db.StreamAdd(key, "field3", "value3"); + db.StreamAdd(key, "field4", "value4"); + + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.Beginning); + + // Read a single message into the first consumer. + db.StreamReadGroup(key, groupName, consumer1, count: 1); + + // Read the remaining messages into the second consumer. + _ = db.StreamReadGroup(key, groupName, consumer2) ?? throw new ArgumentNullException(nameof(consumer2), "db.StreamReadGroup(key, groupName, consumer2)"); + + await Task.Delay(10).ForAwait(); + + // Get the pending info about the messages themselves. + var pendingMessageInfoList = db.StreamPendingMessages(key, groupName, 10, RedisValue.Null); + + Assert.NotNull(pendingMessageInfoList); + Assert.Equal(4, pendingMessageInfoList.Length); + Assert.Equal(consumer1, pendingMessageInfoList[0].ConsumerName); + Assert.Equal(1, pendingMessageInfoList[0].DeliveryCount); + Assert.True((int)pendingMessageInfoList[0].IdleTimeInMilliseconds > 0); + Assert.Equal(id1, pendingMessageInfoList[0].MessageId); + } + + [Fact] + public async Task StreamConsumerGroupViewPendingMessageWithMinIdle() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer1 = "test_consumer_1"; + const int minIdleTimeInMs = 100; + + var id1 = db.StreamAdd(key, "field1", "value1"); + + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.Beginning); + + // Read a single message into the first consumer. + db.StreamReadGroup(key, groupName, consumer1, count: 1); + + var preDelayPendingMessages = + db.StreamPendingMessages(key, groupName, 10, RedisValue.Null, minId: id1, maxId: id1, minIdleTimeInMs: minIdleTimeInMs); + + await Task.Delay(minIdleTimeInMs * 2).ForAwait(); + + var postDelayPendingMessages = + db.StreamPendingMessages(key, groupName, 10, RedisValue.Null, minId: id1, maxId: id1, minIdleTimeInMs: minIdleTimeInMs); + + Assert.NotNull(preDelayPendingMessages); + Assert.Empty(preDelayPendingMessages); + Assert.NotNull(postDelayPendingMessages); + Assert.Single(postDelayPendingMessages); + Assert.Equal(1, postDelayPendingMessages[0].DeliveryCount); + Assert.True((int)postDelayPendingMessages[0].IdleTimeInMilliseconds > minIdleTimeInMs); + Assert.Equal(id1, postDelayPendingMessages[0].MessageId); + } + + [Fact] + public async Task StreamConsumerGroupViewPendingMessageInfoForConsumer() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer1 = "test_consumer_1", + consumer2 = "test_consumer_2"; + + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + db.StreamAdd(key, "field3", "value3"); + db.StreamAdd(key, "field4", "value4"); + + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.Beginning); + + // Read a single message into the first consumer. + db.StreamReadGroup(key, groupName, consumer1, count: 1); + + // Read the remaining messages into the second consumer. + db.StreamReadGroup(key, groupName, consumer2); + + // Get the pending info about the messages themselves. + var pendingMessageInfoList = db.StreamPendingMessages( + key, + groupName, + 10, + consumer2); + + Assert.NotNull(pendingMessageInfoList); + Assert.Equal(3, pendingMessageInfoList.Length); + } + + [Fact] + public async Task StreamDeleteConsumer() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer = "test_consumer"; + + // Add a message to create the stream. + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + + // Create a consumer group and read the message. + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.Beginning); + db.StreamReadGroup(key, groupName, consumer, StreamPosition.NewMessages); + + var preDeleteConsumers = db.StreamConsumerInfo(key, groupName); + + // Delete the consumer. + var deleteResult = db.StreamDeleteConsumer(key, groupName, consumer); + + // Should get 2 messages in the deleteResult. + var postDeleteConsumers = db.StreamConsumerInfo(key, groupName); + + Assert.Equal(2, deleteResult); + Assert.Single(preDeleteConsumers); + Assert.Empty(postDeleteConsumers); + } + + [Fact] + public async Task StreamDeleteConsumerGroup() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer = "test_consumer"; + + // Add a message to create the stream. + db.StreamAdd(key, "field1", "value1"); + + // Create a consumer group and read the messages. + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.Beginning); + db.StreamReadGroup(key, groupName, consumer, StreamPosition.Beginning); + + var preDeleteInfo = db.StreamInfo(key); + + // Now delete the group. + var deleteResult = db.StreamDeleteConsumerGroup(key, groupName); + + var postDeleteInfo = db.StreamInfo(key); + + Assert.True(deleteResult); + Assert.Equal(1, preDeleteInfo.ConsumerGroupCount); + Assert.Equal(0, postDeleteInfo.ConsumerGroupCount); + } + + [Fact] + public async Task StreamDeleteMessage() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + db.StreamAdd(key, "field4", "value4"); + + var deletedCount = db.StreamDelete(key, [id3]); + var messages = db.StreamRange(key); + + Assert.Equal(1, deletedCount); + Assert.Equal(3, messages.Length); + } + + [Fact] + public async Task StreamDeleteMessages() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + db.StreamAdd(key, "field4", "value4"); + + var deletedCount = db.StreamDelete(key, [id2, id3], CommandFlags.None); + var messages = db.StreamRange(key); + + Assert.Equal(2, deletedCount); + Assert.Equal(2, messages.Length); + } + + [Theory] + [InlineData(StreamTrimMode.KeepReferences)] + [InlineData(StreamTrimMode.DeleteReferences)] + [InlineData(StreamTrimMode.Acknowledged)] + public void StreamDeleteExMessage(StreamTrimMode mode) + { + using var conn = Create(require: RedisFeatures.v8_2_0_rc1); // XDELEX + + var db = conn.GetDatabase(); + var key = Me() + ":" + mode; + + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + db.StreamAdd(key, "field4", "value4"); + + var deleted = db.StreamDelete(key, new[] { id3 }, mode: mode); + var messages = db.StreamRange(key); + + Assert.Equal(StreamTrimResult.Deleted, Assert.Single(deleted)); + Assert.Equal(3, messages.Length); + } + + [Theory] + [InlineData(StreamTrimMode.KeepReferences)] + [InlineData(StreamTrimMode.DeleteReferences)] + [InlineData(StreamTrimMode.Acknowledged)] + public void StreamDeleteExMessages(StreamTrimMode mode) + { + using var conn = Create(require: RedisFeatures.v8_2_0_rc1); // XDELEX + + var db = conn.GetDatabase(); + var key = Me() + ":" + mode; + + db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + db.StreamAdd(key, "field4", "value4"); + + var deleted = db.StreamDelete(key, new[] { id2, id3 }, mode: mode); + var messages = db.StreamRange(key); + + Assert.Equal(2, deleted.Length); + Assert.Equal(StreamTrimResult.Deleted, deleted[0]); + Assert.Equal(StreamTrimResult.Deleted, deleted[1]); + Assert.Equal(2, messages.Length); + } + + [Fact] + public async Task StreamGroupInfoGet() + { + var key = Me(); + const string group1 = "test_group_1", + group2 = "test_group_2", + consumer1 = "test_consumer_1", + consumer2 = "test_consumer_2"; + + await using (var conn = Create(require: RedisFeatures.v5_0_0)) + { + var db = conn.GetDatabase(); + db.KeyDelete(key); + + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + db.StreamAdd(key, "field3", "value3"); + db.StreamAdd(key, "field4", "value4"); + + db.StreamCreateConsumerGroup(key, group1, StreamPosition.Beginning); + db.StreamCreateConsumerGroup(key, group2, StreamPosition.Beginning); + + var groupInfoList = db.StreamGroupInfo(key); + Assert.Equal(0, groupInfoList[0].EntriesRead); + Assert.Equal(4, groupInfoList[0].Lag); + Assert.Equal(0, groupInfoList[0].EntriesRead); + Assert.Equal(4, groupInfoList[1].Lag); + + // Read a single message into the first consumer. + db.StreamReadGroup(key, group1, consumer1, count: 1); + + // Read the remaining messages into the second consumer. + db.StreamReadGroup(key, group2, consumer2); + + groupInfoList = db.StreamGroupInfo(key); + + Assert.NotNull(groupInfoList); + Assert.Equal(2, groupInfoList.Length); + + Assert.Equal(group1, groupInfoList[0].Name); + Assert.Equal(1, groupInfoList[0].PendingMessageCount); + Assert.True(IsMessageId(groupInfoList[0].LastDeliveredId)); // can't test actual - will vary + Assert.Equal(1, groupInfoList[0].EntriesRead); + Assert.Equal(3, groupInfoList[0].Lag); + + Assert.Equal(group2, groupInfoList[1].Name); + Assert.Equal(4, groupInfoList[1].PendingMessageCount); + Assert.True(IsMessageId(groupInfoList[1].LastDeliveredId)); // can't test actual - will vary + Assert.Equal(4, groupInfoList[1].EntriesRead); + Assert.Equal(0, groupInfoList[1].Lag); + } + + static bool IsMessageId(string? value) + { + if (value.IsNullOrWhiteSpace()) return false; + return value.Length >= 3 && value.Contains('-'); + } + } + + [Fact] + public async Task StreamGroupConsumerInfoGet() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string group = "test_group", + consumer1 = "test_consumer_1", + consumer2 = "test_consumer_2"; + + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + db.StreamAdd(key, "field3", "value3"); + db.StreamAdd(key, "field4", "value4"); + + db.StreamCreateConsumerGroup(key, group, StreamPosition.Beginning); + db.StreamReadGroup(key, group, consumer1, count: 1); + db.StreamReadGroup(key, group, consumer2); + + var consumerInfoList = db.StreamConsumerInfo(key, group); + + Assert.NotNull(consumerInfoList); + Assert.Equal(2, consumerInfoList.Length); + + Assert.Equal(consumer1, consumerInfoList[0].Name); + Assert.Equal(consumer2, consumerInfoList[1].Name); + + Assert.Equal(1, consumerInfoList[0].PendingMessageCount); + Assert.Equal(3, consumerInfoList[1].PendingMessageCount); + } + + [Fact] + public async Task StreamInfoGet() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + db.StreamAdd(key, "field3", "value3"); + var id4 = db.StreamAdd(key, "field4", "value4"); + + var streamInfo = db.StreamInfo(key); + + Assert.Equal(4, streamInfo.Length); + Assert.True(streamInfo.RadixTreeKeys > 0); + Assert.True(streamInfo.RadixTreeNodes > 0); + Assert.Equal(id1, streamInfo.FirstEntry.Id); + Assert.Equal(id4, streamInfo.LastEntry.Id); + } + + [Fact] + public async Task StreamInfoGetWithEmptyStream() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + // Add an entry and then delete it so the stream is empty, then run streaminfo + // to ensure it functions properly on an empty stream. Namely, the first-entry + // and last-entry messages should be null. + var id = db.StreamAdd(key, "field1", "value1"); + db.StreamDelete(key, [id]); + + Assert.Equal(0, db.StreamLength(key)); + + var streamInfo = db.StreamInfo(key); + + Assert.True(streamInfo.FirstEntry.IsNull); + Assert.True(streamInfo.LastEntry.IsNull); + } + + [Fact] + public async Task StreamNoConsumerGroups() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.StreamAdd(key, "field1", "value1"); + + var groups = db.StreamGroupInfo(key); + + Assert.NotNull(groups); + Assert.Empty(groups); + } + + [Fact] + public async Task StreamPendingNoMessagesOrConsumers() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group"; + + var id = db.StreamAdd(key, "field1", "value1"); + db.StreamDelete(key, [id]); + + db.StreamCreateConsumerGroup(key, groupName, "0-0"); + + var pendingInfo = db.StreamPending(key, "test_group"); + + Assert.Equal(0, pendingInfo.PendingMessageCount); + Assert.Equal(RedisValue.Null, pendingInfo.LowestPendingMessageId); + Assert.Equal(RedisValue.Null, pendingInfo.HighestPendingMessageId); + Assert.NotNull(pendingInfo.Consumers); + Assert.Empty(pendingInfo.Consumers); + } + + [Fact] + public void StreamPositionDefaultValueIsBeginning() + { + RedisValue position = StreamPosition.Beginning; + Assert.Equal(StreamConstants.AllMessages, StreamPosition.Resolve(position, RedisCommand.XREAD)); + Assert.Equal(StreamConstants.AllMessages, StreamPosition.Resolve(position, RedisCommand.XREADGROUP)); + Assert.Equal(StreamConstants.AllMessages, StreamPosition.Resolve(position, RedisCommand.XGROUP)); + } + + [Fact] + public void StreamPositionValidateBeginning() + { + var position = StreamPosition.Beginning; + + Assert.Equal(StreamConstants.AllMessages, StreamPosition.Resolve(position, RedisCommand.XREAD)); + } + + [Fact] + public void StreamPositionValidateExplicit() + { + const string explicitValue = "1-0"; + const string position = explicitValue; + + Assert.Equal(explicitValue, StreamPosition.Resolve(position, RedisCommand.XREAD)); + } + + [Fact] + public void StreamPositionValidateNew() + { + var position = StreamPosition.NewMessages; + + Assert.Equal(StreamConstants.NewMessages, StreamPosition.Resolve(position, RedisCommand.XGROUP)); + Assert.Equal(StreamConstants.UndeliveredMessages, StreamPosition.Resolve(position, RedisCommand.XREADGROUP)); + Assert.ThrowsAny(() => StreamPosition.Resolve(position, RedisCommand.XREAD)); + } + + [Fact] + public async Task StreamRead() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + + // Read the entire stream from the beginning. + var entries = db.StreamRead(key, "0-0"); + + Assert.Equal(3, entries.Length); + Assert.Equal(id1, entries[0].Id); + Assert.Equal(id2, entries[1].Id); + Assert.Equal(id3, entries[2].Id); + } + + [Fact] + public async Task StreamReadEmptyStream() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + // Write to a stream to create the key. + var id1 = db.StreamAdd(key, "field1", "value1"); + + // Delete the key to empty the stream. + db.StreamDelete(key, [id1]); + var len = db.StreamLength(key); + + // Read the entire stream from the beginning. + var entries = db.StreamRead(key, "0-0"); + + Assert.Empty(entries); + Assert.Equal(0, len); + } + + [Fact] + public async Task StreamReadEmptyStreams() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key1 = Me() + "a"; + var key2 = Me() + "b"; + + // Write to a stream to create the key. + var id1 = db.StreamAdd(key1, "field1", "value1"); + var id2 = db.StreamAdd(key2, "field2", "value2"); + + // Delete the key to empty the stream. + db.StreamDelete(key1, [id1]); + db.StreamDelete(key2, [id2]); + + var len1 = db.StreamLength(key1); + var len2 = db.StreamLength(key2); + + // Read the entire stream from the beginning. + var entries1 = db.StreamRead(key1, "0-0"); + var entries2 = db.StreamRead(key2, "0-0"); + + Assert.Empty(entries1); + Assert.Empty(entries2); + + Assert.Equal(0, len1); + Assert.Equal(0, len2); + } + + [Fact] + public async Task StreamReadLastMessage() + { + await using var conn = Create(require: RedisFeatures.v7_4_0_rc1); + var db = conn.GetDatabase(); + var key1 = Me(); + + // Read the entire stream from the beginning. + db.StreamRead(key1, "0-0"); + db.StreamAdd(key1, "field2", "value2"); + db.StreamAdd(key1, "fieldLast", "valueLast"); + var entries = db.StreamRead(key1, "+"); + + Assert.NotNull(entries); + Assert.True(entries.Length > 0); + Assert.Equal(new[] { new NameValueEntry("fieldLast", "valueLast") }, entries[0].Values); + } + + [Fact] + public async Task StreamReadExpectedExceptionInvalidCountMultipleStream() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var streamPositions = new[] + { + new StreamPosition("key1", "0-0"), + new StreamPosition("key2", "0-0"), + }; + Assert.Throws(() => db.StreamRead(streamPositions, 0)); + } + + [Fact] + public async Task StreamReadExpectedExceptionInvalidCountSingleStream() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + Assert.Throws(() => db.StreamRead(key, "0-0", 0)); + } + + [Fact] + public async Task StreamReadExpectedExceptionNullStreamList() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + Assert.Throws(() => db.StreamRead(null!)); + } + + [Fact] + public async Task StreamReadExpectedExceptionEmptyStreamList() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var emptyList = Array.Empty(); + Assert.Throws(() => db.StreamRead(emptyList)); + } + + [Fact] + public async Task StreamReadMultipleStreams() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key1 = Me() + "a"; + var key2 = Me() + "b"; + + var id1 = db.StreamAdd(key1, "field1", "value1"); + var id2 = db.StreamAdd(key1, "field2", "value2"); + var id3 = db.StreamAdd(key2, "field3", "value3"); + var id4 = db.StreamAdd(key2, "field4", "value4"); + + // Read from both streams at the same time. + var streamList = new[] + { + new StreamPosition(key1, "0-0"), + new StreamPosition(key2, "0-0"), + }; + + var streams = db.StreamRead(streamList); + + Assert.Equal(2, streams.Length); + + Assert.Equal(key1, streams[0].Key); + Assert.Equal(2, streams[0].Entries.Length); + Assert.Equal(id1, streams[0].Entries[0].Id); + Assert.Equal(id2, streams[0].Entries[1].Id); + + Assert.Equal(key2, streams[1].Key); + Assert.Equal(2, streams[1].Entries.Length); + Assert.Equal(id3, streams[1].Entries[0].Id); + Assert.Equal(id4, streams[1].Entries[1].Id); + } + + [Fact] + public async Task StreamReadMultipleStreamsLastMessage() + { + await using var conn = Create(require: RedisFeatures.v7_4_0_rc1); + + var db = conn.GetDatabase(); + var key1 = Me() + "a"; + var key2 = Me() + "b"; + + var id1 = db.StreamAdd(key1, "field1", "value1"); + var id2 = db.StreamAdd(key1, "field2", "value2"); + var id3 = db.StreamAdd(key2, "field3", "value3"); + var id4 = db.StreamAdd(key2, "field4", "value4"); + + var streamList = new[] { new StreamPosition(key1, "0-0"), new StreamPosition(key2, "0-0") }; + db.StreamRead(streamList); + + var streams = db.StreamRead(streamList); + + db.StreamAdd(key1, "field5", "value5"); + db.StreamAdd(key1, "field6", "value6"); + db.StreamAdd(key2, "field7", "value7"); + db.StreamAdd(key2, "field8", "value8"); + + streamList = [new StreamPosition(key1, "+"), new StreamPosition(key2, "+")]; + + streams = db.StreamRead(streamList); + + Assert.NotNull(streams); + Assert.Equal(2, streams.Length); + + var stream1 = streams.Where(e => e.Key == key1).First(); + Assert.NotNull(stream1.Entries); + Assert.True(stream1.Entries.Length > 0); + Assert.Equal(new[] { new NameValueEntry("field6", "value6") }, stream1.Entries[0].Values); + + var stream2 = streams.Where(e => e.Key == key2).First(); + Assert.NotNull(stream2.Entries); + Assert.True(stream2.Entries.Length > 0); + Assert.Equal(new[] { new NameValueEntry("field8", "value8") }, stream2.Entries[0].Values); + } + + [Fact] + public async Task StreamReadMultipleStreamsWithCount() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key1 = Me() + "a"; + var key2 = Me() + "b"; + + var id1 = db.StreamAdd(key1, "field1", "value1"); + db.StreamAdd(key1, "field2", "value2"); + var id3 = db.StreamAdd(key2, "field3", "value3"); + db.StreamAdd(key2, "field4", "value4"); + + var streamList = new[] + { + new StreamPosition(key1, "0-0"), + new StreamPosition(key2, "0-0"), + }; + + var streams = db.StreamRead(streamList, countPerStream: 1); + + // We should get both streams back. + Assert.Equal(2, streams.Length); + + // Ensure we only got one message per stream. + Assert.Single(streams[0].Entries); + Assert.Single(streams[1].Entries); + + // Check the message IDs as well. + Assert.Equal(id1, streams[0].Entries[0].Id); + Assert.Equal(id3, streams[1].Entries[0].Id); + } + + [Fact] + public async Task StreamReadMultipleStreamsWithReadPastSecondStream() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key1 = Me() + "a"; + var key2 = Me() + "b"; + + db.StreamAdd(key1, "field1", "value1"); + db.StreamAdd(key1, "field2", "value2"); + db.StreamAdd(key2, "field3", "value3"); + var id4 = db.StreamAdd(key2, "field4", "value4"); + + var streamList = new[] + { + new StreamPosition(key1, "0-0"), + + // read past the end of stream # 2 + new StreamPosition(key2, id4), + }; + + var streams = db.StreamRead(streamList); + + // We should only get the first stream back. + Assert.Single(streams); + + Assert.Equal(key1, streams[0].Key); + Assert.Equal(2, streams[0].Entries.Length); + } + + [Fact] + public async Task StreamReadMultipleStreamsWithEmptyResponse() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key1 = Me() + "a"; + var key2 = Me() + "b"; + + db.StreamAdd(key1, "field1", "value1"); + var id2 = db.StreamAdd(key1, "field2", "value2"); + db.StreamAdd(key2, "field3", "value3"); + var id4 = db.StreamAdd(key2, "field4", "value4"); + + var streamList = new[] + { + // Read past the end of both streams. + new StreamPosition(key1, id2), + new StreamPosition(key2, id4), + }; + + var streams = db.StreamRead(streamList); + + // We expect an empty response. + Assert.Empty(streams); + } + + [Fact] + public async Task StreamReadPastEndOfStream() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + + // Read after the final ID in the stream, we expect an empty array as a response. + var entries = db.StreamRead(key, id2); + + Assert.Empty(entries); + } + + [Fact] + public async Task StreamReadRange() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + + var entries = db.StreamRange(key); + + Assert.Equal(2, entries.Length); + Assert.Equal(id1, entries[0].Id); + Assert.Equal(id2, entries[1].Id); + } + + [Fact] + public async Task StreamReadRangeOfEmptyStream() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + + var deleted = db.StreamDelete(key, [id1, id2]); + + var entries = db.StreamRange(key); + + Assert.Equal(2, deleted); + Assert.NotNull(entries); + Assert.Empty(entries); + } + + [Fact] + public async Task StreamReadRangeWithCount() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + + var entries = db.StreamRange(key, count: 1); + + Assert.Single(entries); + Assert.Equal(id1, entries[0].Id); + } + + [Fact] + public async Task StreamReadRangeReverse() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + + var entries = db.StreamRange(key, messageOrder: Order.Descending); + + Assert.Equal(2, entries.Length); + Assert.Equal(id2, entries[0].Id); + Assert.Equal(id1, entries[1].Id); + } + + [Fact] + public async Task StreamReadRangeReverseWithCount() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + + var entries = db.StreamRange(key, id1, id2, 1, Order.Descending); + + Assert.Single(entries); + Assert.Equal(id2, entries[0].Id); + } + + [Fact] + public async Task StreamReadWithAfterIdAndCount_1() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + db.StreamAdd(key, "field3", "value3"); + + // Only read a single item from the stream. + var entries = db.StreamRead(key, id1, 1); + + Assert.Single(entries); + Assert.Equal(id2, entries[0].Id); + } + + [Fact] + public async Task StreamReadWithAfterIdAndCount_2() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + var id1 = db.StreamAdd(key, "field1", "value1"); + var id2 = db.StreamAdd(key, "field2", "value2"); + var id3 = db.StreamAdd(key, "field3", "value3"); + db.StreamAdd(key, "field4", "value4"); + + // Read multiple items from the stream. + var entries = db.StreamRead(key, id1, 2); + + Assert.Equal(2, entries.Length); + Assert.Equal(id2, entries[0].Id); + Assert.Equal(id3, entries[1].Id); + } + + protected override string GetConfiguration() => "127.0.0.1:6379"; + + [Fact] + public async Task StreamTrimLength() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + // Add a couple items and check length. + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + db.StreamAdd(key, "field3", "value3"); + db.StreamAdd(key, "field4", "value4"); + + var numRemoved = db.StreamTrim(key, 1); + var len = db.StreamLength(key); + + Assert.Equal(3, numRemoved); + Assert.Equal(1, len); + } + + private static Version ForMode(StreamTrimMode mode, Version? defaultVersion = null) => mode switch + { + StreamTrimMode.KeepReferences => defaultVersion ?? RedisFeatures.v5_0_0, + StreamTrimMode.Acknowledged => RedisFeatures.v8_2_0_rc1, + StreamTrimMode.DeleteReferences => RedisFeatures.v8_2_0_rc1, + _ => throw new ArgumentOutOfRangeException(nameof(mode)), + }; + + [Theory] + [InlineData(StreamTrimMode.KeepReferences)] + [InlineData(StreamTrimMode.DeleteReferences)] + [InlineData(StreamTrimMode.Acknowledged)] + public void StreamTrimByMinId(StreamTrimMode mode) + { + using var conn = Create(require: ForMode(mode, RedisFeatures.v6_2_0)); + + var db = conn.GetDatabase(); + var key = Me() + ":" + mode; + + // Add a couple items and check length. + db.StreamAdd(key, "field1", "value1", 1111111110); + db.StreamAdd(key, "field2", "value2", 1111111111); + db.StreamAdd(key, "field3", "value3", 1111111112); + + var numRemoved = db.StreamTrimByMinId(key, 1111111111, mode: mode); + var len = db.StreamLength(key); + + Assert.Equal(1, numRemoved); + Assert.Equal(2, len); + } + + [Theory] + [InlineData(StreamTrimMode.KeepReferences)] + [InlineData(StreamTrimMode.DeleteReferences)] + [InlineData(StreamTrimMode.Acknowledged)] + public void StreamTrimByMinIdWithApproximateAndLimit(StreamTrimMode mode) + { + using var conn = Create(require: ForMode(mode, RedisFeatures.v6_2_0)); + + var db = conn.GetDatabase(); + var key = Me() + ":" + mode; + + const int maxLength = 1000; + const int limit = 100; + + for (var i = 0; i < maxLength; i++) + { + db.StreamAdd(key, $"field", $"value", 1111111110 + i); + } + + var numRemoved = db.StreamTrimByMinId(key, 1111111110 + maxLength, useApproximateMaxLength: true, limit: limit, mode: mode); + var expectRemoved = mode switch + { + StreamTrimMode.KeepReferences => limit, + StreamTrimMode.DeleteReferences => 0, + StreamTrimMode.Acknowledged => 0, + _ => throw new ArgumentOutOfRangeException(nameof(mode)), + }; + var len = db.StreamLength(key); + + Assert.Equal(expectRemoved, numRemoved); + Assert.Equal(maxLength - expectRemoved, len); + } + + [Fact] + public async Task StreamVerifyLength() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + + // Add a couple items and check length. + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + + var len = db.StreamLength(key); + + Assert.Equal(2, len); + } + + [Fact] + public async Task AddWithApproxCountAsync() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + await db.StreamAddAsync(key, "field", "value", maxLength: 10, useApproximateMaxLength: true, flags: CommandFlags.None).ConfigureAwait(false); + } + + [Theory] + [InlineData(StreamTrimMode.KeepReferences)] + [InlineData(StreamTrimMode.DeleteReferences)] + [InlineData(StreamTrimMode.Acknowledged)] + public async Task AddWithApproxCount(StreamTrimMode mode) + { + await using var conn = Create(require: ForMode(mode)); + + var db = conn.GetDatabase(); + var key = Me() + ":" + mode; + db.StreamAdd(key, "field", "value", maxLength: 10, useApproximateMaxLength: true, trimMode: mode, flags: CommandFlags.None); + } + + [Theory] + [InlineData(StreamTrimMode.KeepReferences, 1)] + [InlineData(StreamTrimMode.DeleteReferences, 1)] + [InlineData(StreamTrimMode.Acknowledged, 1)] + [InlineData(StreamTrimMode.KeepReferences, 2)] + [InlineData(StreamTrimMode.DeleteReferences, 2)] + [InlineData(StreamTrimMode.Acknowledged, 2)] + public async Task AddWithMultipleApproxCount(StreamTrimMode mode, int count) + { + await using var conn = Create(require: ForMode(mode)); + + var db = conn.GetDatabase(); + var key = Me() + ":" + mode; + + var pairs = new NameValueEntry[count]; + for (var i = 0; i < count; i++) + { + pairs[i] = new NameValueEntry($"field{i}", $"value{i}"); + } + db.StreamAdd(key, maxLength: 10, useApproximateMaxLength: true, trimMode: mode, flags: CommandFlags.None, streamPairs: pairs); + } + + [Fact] + public async Task StreamReadGroupWithNoAckShowsNoPendingMessages() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer = "consumer"; + + db.StreamAdd(key, "field1", "value1"); + db.StreamAdd(key, "field2", "value2"); + + db.StreamCreateConsumerGroup(key, groupName, StreamPosition.NewMessages); + + db.StreamReadGroup( + key, + groupName, + consumer, + StreamPosition.NewMessages, + noAck: true); + + var pendingInfo = db.StreamPending(key, groupName); + + Assert.Equal(0, pendingInfo.PendingMessageCount); + } + + [Fact] + public async Task StreamReadGroupMultiStreamWithNoAckShowsNoPendingMessages() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key1 = Me() + "a"; + var key2 = Me() + "b"; + const string groupName = "test_group", + consumer = "consumer"; + + db.StreamAdd(key1, "field1", "value1"); + db.StreamAdd(key1, "field2", "value2"); + + db.StreamAdd(key2, "field3", "value3"); + db.StreamAdd(key2, "field4", "value4"); + + db.StreamCreateConsumerGroup(key1, groupName, StreamPosition.NewMessages); + db.StreamCreateConsumerGroup(key2, groupName, StreamPosition.NewMessages); + + db.StreamReadGroup( + [ + new StreamPosition(key1, StreamPosition.NewMessages), + new StreamPosition(key2, StreamPosition.NewMessages), + ], + groupName, + consumer, + noAck: true); + + var pending1 = db.StreamPending(key1, groupName); + var pending2 = db.StreamPending(key2, groupName); + + Assert.Equal(0, pending1.PendingMessageCount); + Assert.Equal(0, pending2.PendingMessageCount); + } + + [Fact] + public async Task StreamReadIndexerUsage() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var streamName = Me(); + + await db.StreamAddAsync( + streamName, + [ + new NameValueEntry("x", "blah"), + new NameValueEntry("msg", /*lang=json,strict*/ @"{""name"":""test"",""id"":123}"), + new NameValueEntry("y", "more blah"), + ]); + + var streamResult = await db.StreamRangeAsync(streamName, count: 1000); + var evntJson = streamResult + .Select(x => (dynamic?)JsonConvert.DeserializeObject(x["msg"]!)) + .ToList(); + var obj = Assert.Single(evntJson); + Assert.Equal(123, (int)obj!.id); + Assert.Equal("test", (string)obj.name); + } + + [Fact] + public async Task StreamConsumerGroupInfoLagIsNull() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer = "consumer"; + + await db.StreamCreateConsumerGroupAsync(key, groupName); + await db.StreamReadGroupAsync(key, groupName, consumer, "0-0", 1); + await db.StreamAddAsync(key, "field1", "value1"); + await db.StreamAddAsync(key, "field1", "value1"); + + var streamInfo = await db.StreamInfoAsync(key); + await db.StreamDeleteAsync(key, new[] { streamInfo.LastEntry.Id }); + + Assert.Null((await db.StreamGroupInfoAsync(key))[0].Lag); + } + + [Fact] + public async Task StreamConsumerGroupInfoLagIsTwo() + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string groupName = "test_group", + consumer = "consumer"; + + await db.StreamCreateConsumerGroupAsync(key, groupName); + await db.StreamReadGroupAsync(key, groupName, consumer, "0-0", 1); + await db.StreamAddAsync(key, "field1", "value1"); + await db.StreamAddAsync(key, "field1", "value1"); + + Assert.Equal(2, (await db.StreamGroupInfoAsync(key))[0].Lag); + } +} diff --git a/tests/StackExchange.Redis.Tests/StringTests.cs b/tests/StackExchange.Redis.Tests/StringTests.cs new file mode 100644 index 000000000..85bcc7dd5 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/StringTests.cs @@ -0,0 +1,1034 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +/// +/// Tests for . +/// +[RunPerProtocol] +public class StringTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task Append() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var server = GetServer(conn); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + var l0 = server.Features.StringLength ? db.StringLengthAsync(key) : null; + + var s0 = db.StringGetAsync(key); + + db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); + var s1 = db.StringGetAsync(key); + var l1 = server.Features.StringLength ? db.StringLengthAsync(key) : null; + + var result = db.StringAppendAsync(key, Encode("defgh")); + var s3 = db.StringGetAsync(key); + var l2 = server.Features.StringLength ? db.StringLengthAsync(key) : null; + + Assert.Null((string?)await s0); + Assert.Equal("abc", await s1); + Assert.Equal(8, await result); + Assert.Equal("abcdefgh", await s3); + + if (server.Features.StringLength) + { + Assert.Equal(0, await l0!); + Assert.Equal(3, await l1!); + Assert.Equal(8, await l2!); + } + } + + [Fact] + public async Task Set() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); + var v1 = db.StringGetAsync(key); + + db.StringSet(key, Encode("def"), flags: CommandFlags.FireAndForget); + var v2 = db.StringGetAsync(key); + + Assert.Equal("abc", await v1); + Assert.Equal("def", Decode(await v2)); + } + + [Fact] + public async Task SetEmpty() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, new byte[] { }); + var exists = await db.KeyExistsAsync(key); + var val = await db.StringGetAsync(key); + + Assert.True(exists); + Log("Value: " + val); + Assert.Equal(0, val.Length()); + } + + [Fact] + public async Task StringGetSetExpiryNoValue() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + var emptyVal = await db.StringGetSetExpiryAsync(key, TimeSpan.FromHours(1)); + + Assert.Equal(RedisValue.Null, emptyVal); + } + + [Fact] + public async Task StringGetSetExpiryRelative() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "abc", TimeSpan.FromHours(1)); + var relativeSec = db.StringGetSetExpiryAsync(key, TimeSpan.FromMinutes(30)); + var relativeSecTtl = db.KeyTimeToLiveAsync(key); + + Assert.Equal("abc", await relativeSec); + var time = await relativeSecTtl; + Assert.NotNull(time); + Assert.InRange(time.Value, TimeSpan.FromMinutes(29.8), TimeSpan.FromMinutes(30.2)); + } + + [Fact] + public async Task StringGetSetExpiryAbsolute() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "abc", TimeSpan.FromHours(1)); + var newDate = DateTime.UtcNow.AddMinutes(30); + var val = db.StringGetSetExpiryAsync(key, newDate); + var valTtl = db.KeyTimeToLiveAsync(key); + + Assert.Equal("abc", await val); + var time = await valTtl; + Assert.NotNull(time); + Assert.InRange(time.Value, TimeSpan.FromMinutes(29.8), TimeSpan.FromMinutes(30.2)); + + // And ensure our type checking works + var ex = await Assert.ThrowsAsync(() => db.StringGetSetExpiryAsync(key, new DateTime(100, DateTimeKind.Unspecified))); + Assert.NotNull(ex); + } + + [Fact] + public async Task StringGetSetExpiryPersist() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "abc", TimeSpan.FromHours(1)); + var val = db.StringGetSetExpiryAsync(key, null); + var valTtl = db.KeyTimeToLiveAsync(key); + + Assert.Equal("abc", await val); + Assert.Null(await valTtl); + } + + [Fact] + public async Task GetLease() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); + using (var v1 = await db.StringGetLeaseAsync(key).ConfigureAwait(false)) + { + string? s = v1?.DecodeString(); + Assert.Equal("abc", s); + } + } + + [Fact] + public async Task GetLeaseAsStream() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); + var lease = await db.StringGetLeaseAsync(key).ConfigureAwait(false); + Assert.NotNull(lease); + using (var v1 = lease.AsStream()) + { + using (var sr = new StreamReader(v1)) + { + string s = sr.ReadToEnd(); + Assert.Equal("abc", s); + } + } + } + + [Fact] + public async Task GetDelete() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var prefix = Me(); + db.KeyDelete(prefix + "1", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "2", CommandFlags.FireAndForget); + db.StringSet(prefix + "1", "abc", flags: CommandFlags.FireAndForget); + + Assert.True(db.KeyExists(prefix + "1")); + Assert.False(db.KeyExists(prefix + "2")); + + var s0 = db.StringGetDelete(prefix + "1"); + var s2 = db.StringGetDelete(prefix + "2"); + + Assert.False(db.KeyExists(prefix + "1")); + Assert.Equal("abc", s0); + Assert.Equal(RedisValue.Null, s2); + } + + [Fact] + public async Task GetDeleteAsync() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var prefix = Me(); + db.KeyDelete(prefix + "1", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "2", CommandFlags.FireAndForget); + db.StringSet(prefix + "1", "abc", flags: CommandFlags.FireAndForget); + + Assert.True(db.KeyExists(prefix + "1")); + Assert.False(db.KeyExists(prefix + "2")); + + var s0 = db.StringGetDeleteAsync(prefix + "1"); + var s2 = db.StringGetDeleteAsync(prefix + "2"); + + Assert.False(db.KeyExists(prefix + "1")); + Assert.Equal("abc", await s0); + Assert.Equal(RedisValue.Null, await s2); + } + + [Fact] + public async Task SetNotExists() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var prefix = Me(); + db.KeyDelete(prefix + "1", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "2", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "3", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "4", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "5", CommandFlags.FireAndForget); + db.StringSet(prefix + "1", "abc", flags: CommandFlags.FireAndForget); + + var x0 = db.StringSetAsync(prefix + "1", "def", when: When.NotExists); + var x1 = db.StringSetAsync(prefix + "1", Encode("def"), when: When.NotExists); + var x2 = db.StringSetAsync(prefix + "2", "def", when: When.NotExists); + var x3 = db.StringSetAsync(prefix + "3", Encode("def"), when: When.NotExists); + var x4 = db.StringSetAsync(prefix + "4", "def", expiry: TimeSpan.FromSeconds(4), when: When.NotExists); + var x5 = db.StringSetAsync(prefix + "5", "def", expiry: TimeSpan.FromMilliseconds(4001), when: When.NotExists); + + var s0 = db.StringGetAsync(prefix + "1"); + var s2 = db.StringGetAsync(prefix + "2"); + var s3 = db.StringGetAsync(prefix + "3"); + + Assert.False(await x0); + Assert.False(await x1); + Assert.True(await x2); + Assert.True(await x3); + Assert.True(await x4); + Assert.True(await x5); + Assert.Equal("abc", await s0); + Assert.Equal("def", await s2); + Assert.Equal("def", await s3); + } + + [Fact] + public async Task SetKeepTtl() + { + await using var conn = Create(require: RedisFeatures.v6_0_0); + + var db = conn.GetDatabase(); + var prefix = Me(); + db.KeyDelete(prefix + "1", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "2", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "3", CommandFlags.FireAndForget); + db.StringSet(prefix + "1", "abc", flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "2", "abc", expiry: TimeSpan.FromMinutes(5), flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "3", "abc", expiry: TimeSpan.FromMinutes(10), flags: CommandFlags.FireAndForget); + + var x0 = db.KeyTimeToLiveAsync(prefix + "1"); + var x1 = db.KeyTimeToLiveAsync(prefix + "2"); + var x2 = db.KeyTimeToLiveAsync(prefix + "3"); + + Assert.Null(await x0); + Assert.True(await x1 > TimeSpan.FromMinutes(4), "Over 4"); + Assert.True(await x1 <= TimeSpan.FromMinutes(5), "Under 5"); + Assert.True(await x2 > TimeSpan.FromMinutes(9), "Over 9"); + Assert.True(await x2 <= TimeSpan.FromMinutes(10), "Under 10"); + + db.StringSet(prefix + "1", "def", keepTtl: true, flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "2", "def", flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "3", "def", keepTtl: true, flags: CommandFlags.FireAndForget); + + var y0 = db.KeyTimeToLiveAsync(prefix + "1"); + var y1 = db.KeyTimeToLiveAsync(prefix + "2"); + var y2 = db.KeyTimeToLiveAsync(prefix + "3"); + + Assert.Null(await y0); + Assert.Null(await y1); + Assert.True(await y2 > TimeSpan.FromMinutes(9), "Over 9"); + Assert.True(await y2 <= TimeSpan.FromMinutes(10), "Under 10"); + } + + [Fact] + public async Task SetAndGet() + { + await using var conn = Create(require: RedisFeatures.v6_2_0); + + var db = conn.GetDatabase(); + var prefix = Me(); + db.KeyDelete(prefix + "1", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "2", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "3", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "4", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "5", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "6", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "7", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "8", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "9", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "10", CommandFlags.FireAndForget); + db.StringSet(prefix + "1", "abc", flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "2", "abc", flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "4", "abc", flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "6", "abc", flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "7", "abc", flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "8", "abc", flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "9", "abc", flags: CommandFlags.FireAndForget); + db.StringSet(prefix + "10", "abc", expiry: TimeSpan.FromMinutes(10), flags: CommandFlags.FireAndForget); + + var x0 = db.StringSetAndGetAsync(prefix + "1", RedisValue.Null); + var x1 = db.StringSetAndGetAsync(prefix + "2", "def"); + var x2 = db.StringSetAndGetAsync(prefix + "3", "def"); + var x3 = db.StringSetAndGetAsync(prefix + "4", "def", when: When.Exists); + var x4 = db.StringSetAndGetAsync(prefix + "5", "def", when: When.Exists); + var x5 = db.StringSetAndGetAsync(prefix + "6", "def", expiry: TimeSpan.FromSeconds(4)); + var x6 = db.StringSetAndGetAsync(prefix + "7", "def", expiry: TimeSpan.FromMilliseconds(4001)); + var x7 = db.StringSetAndGetAsync(prefix + "8", "def", expiry: TimeSpan.FromSeconds(4), when: When.Exists); + var x8 = db.StringSetAndGetAsync(prefix + "9", "def", expiry: TimeSpan.FromMilliseconds(4001), when: When.Exists); + + var y0 = db.StringSetAndGetAsync(prefix + "10", "def", keepTtl: true); + var y1 = db.KeyTimeToLiveAsync(prefix + "10"); + var y2 = db.StringGetAsync(prefix + "10"); + + var s0 = db.StringGetAsync(prefix + "1"); + var s1 = db.StringGetAsync(prefix + "2"); + var s2 = db.StringGetAsync(prefix + "3"); + var s3 = db.StringGetAsync(prefix + "4"); + var s4 = db.StringGetAsync(prefix + "5"); + + Assert.Equal("abc", await x0); + Assert.Equal("abc", await x1); + Assert.Equal(RedisValue.Null, await x2); + Assert.Equal("abc", await x3); + Assert.Equal(RedisValue.Null, await x4); + Assert.Equal("abc", await x5); + Assert.Equal("abc", await x6); + Assert.Equal("abc", await x7); + Assert.Equal("abc", await x8); + + Assert.Equal("abc", await y0); + Assert.True(await y1 <= TimeSpan.FromMinutes(10), "Under 10 min"); + Assert.True(await y1 >= TimeSpan.FromMinutes(8), "Over 8 min"); + Assert.Equal("def", await y2); + + Assert.Equal(RedisValue.Null, await s0); + Assert.Equal("def", await s1); + Assert.Equal("def", await s2); + Assert.Equal("def", await s3); + Assert.Equal(RedisValue.Null, await s4); + } + + [Fact] + public async Task SetNotExistsAndGet() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var prefix = Me(); + db.KeyDelete(prefix + "1", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "2", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "3", CommandFlags.FireAndForget); + db.KeyDelete(prefix + "4", CommandFlags.FireAndForget); + db.StringSet(prefix + "1", "abc", flags: CommandFlags.FireAndForget); + + var x0 = db.StringSetAndGetAsync(prefix + "1", "def", when: When.NotExists); + var x1 = db.StringSetAndGetAsync(prefix + "2", "def", when: When.NotExists); + var x2 = db.StringSetAndGetAsync(prefix + "3", "def", expiry: TimeSpan.FromSeconds(4), when: When.NotExists); + var x3 = db.StringSetAndGetAsync(prefix + "4", "def", expiry: TimeSpan.FromMilliseconds(4001), when: When.NotExists); + + var s0 = db.StringGetAsync(prefix + "1"); + var s1 = db.StringGetAsync(prefix + "2"); + + Assert.Equal("abc", await x0); + Assert.Equal(RedisValue.Null, await x1); + Assert.Equal(RedisValue.Null, await x2); + Assert.Equal(RedisValue.Null, await x3); + + Assert.Equal("abc", await s0); + Assert.Equal("def", await s1); + } + + [Fact] + public async Task Ranges() + { + await using var conn = Create(require: RedisFeatures.v2_1_8); + + var db = conn.GetDatabase(); + var key = Me(); + + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "abcdefghi", flags: CommandFlags.FireAndForget); + db.StringSetRange(key, 2, "xy", CommandFlags.FireAndForget); + db.StringSetRange(key, 4, Encode("z"), CommandFlags.FireAndForget); + + var val = db.StringGetAsync(key); + + Assert.Equal("abxyzfghi", await val); + } + + [Fact] + public async Task IncrDecr() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "2", flags: CommandFlags.FireAndForget); + var v1 = db.StringIncrementAsync(key); + var v2 = db.StringIncrementAsync(key, 5); + var v3 = db.StringIncrementAsync(key, -2); + var v4 = db.StringDecrementAsync(key); + var v5 = db.StringDecrementAsync(key, 5); + var v6 = db.StringDecrementAsync(key, -2); + var s = db.StringGetAsync(key); + + Assert.Equal(3, await v1); + Assert.Equal(8, await v2); + Assert.Equal(6, await v3); + Assert.Equal(5, await v4); + Assert.Equal(0, await v5); + Assert.Equal(2, await v6); + Assert.Equal("2", await s); + } + + [Fact] + public async Task IncrDecrFloat() + { + await using var conn = Create(require: RedisFeatures.v2_6_0); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "2", flags: CommandFlags.FireAndForget); + var v1 = db.StringIncrementAsync(key, 1.1); + var v2 = db.StringIncrementAsync(key, 5.0); + var v3 = db.StringIncrementAsync(key, -2.0); + var v4 = db.StringIncrementAsync(key, -1.0); + var v5 = db.StringIncrementAsync(key, -5.0); + var v6 = db.StringIncrementAsync(key, 2.0); + + var s = db.StringGetAsync(key); + + Assert.Equal(3.1, await v1, 5); + Assert.Equal(8.1, await v2, 5); + Assert.Equal(6.1, await v3, 5); + Assert.Equal(5.1, await v4, 5); + Assert.Equal(0.1, await v5, 5); + Assert.Equal(2.1, await v6, 5); + Assert.Equal(2.1, (double)await s, 5); + } + + [Fact] + public async Task GetRange() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, CommandFlags.FireAndForget); + + db.StringSet(key, "abcdefghi", flags: CommandFlags.FireAndForget); + var s = db.StringGetRangeAsync(key, 2, 4); + var b = db.StringGetRangeAsync(key, 2, 4); + + Assert.Equal("cde", await s); + Assert.Equal("cde", Decode(await b)); + } + + [Fact] + public async Task BitCount() + { + await using var conn = Create(require: RedisFeatures.v2_6_0); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, flags: CommandFlags.FireAndForget); + db.StringSet(key, "foobar", flags: CommandFlags.FireAndForget); + + var r1 = db.StringBitCount(key); + var r2 = db.StringBitCount(key, 0, 0); + var r3 = db.StringBitCount(key, 1, 1); + + Assert.Equal(26, r1); + Assert.Equal(4, r2); + Assert.Equal(6, r3); + + // Async + r1 = await db.StringBitCountAsync(key); + r2 = await db.StringBitCountAsync(key, 0, 0); + r3 = await db.StringBitCountAsync(key, 1, 1); + + Assert.Equal(26, r1); + Assert.Equal(4, r2); + Assert.Equal(6, r3); + } + + [Fact] + public async Task BitCountWithBitUnit() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, flags: CommandFlags.FireAndForget); + db.StringSet(key, "foobar", flags: CommandFlags.FireAndForget); + + var r1 = db.StringBitCount(key, 1, 1); // Using default byte + var r2 = db.StringBitCount(key, 1, 1, StringIndexType.Bit); + + Assert.Equal(6, r1); + Assert.Equal(1, r2); + + // Async + r1 = await db.StringBitCountAsync(key, 1, 1); // Using default byte + r2 = await db.StringBitCountAsync(key, 1, 1, StringIndexType.Bit); + + Assert.Equal(6, r1); + Assert.Equal(1, r2); + } + + [Fact] + public async Task BitOp() + { + await using var conn = Create(require: RedisFeatures.v2_6_0); + + var db = conn.GetDatabase(); + var prefix = Me(); + var key1 = prefix + "1"; + var key2 = prefix + "2"; + var key3 = prefix + "3"; + db.StringSet(key1, new byte[] { 3 }, flags: CommandFlags.FireAndForget); + db.StringSet(key2, new byte[] { 6 }, flags: CommandFlags.FireAndForget); + db.StringSet(key3, new byte[] { 12 }, flags: CommandFlags.FireAndForget); + + var len_and = db.StringBitOperationAsync(Bitwise.And, "and", [key1, key2, key3]); + var len_or = db.StringBitOperationAsync(Bitwise.Or, "or", [key1, key2, key3]); + var len_xor = db.StringBitOperationAsync(Bitwise.Xor, "xor", [key1, key2, key3]); + var len_not = db.StringBitOperationAsync(Bitwise.Not, "not", key1); + + Assert.Equal(1, await len_and); + Assert.Equal(1, await len_or); + Assert.Equal(1, await len_xor); + Assert.Equal(1, await len_not); + + var r_and = ((byte[]?)(await db.StringGetAsync("and").ForAwait()))?.Single(); + var r_or = ((byte[]?)(await db.StringGetAsync("or").ForAwait()))?.Single(); + var r_xor = ((byte[]?)(await db.StringGetAsync("xor").ForAwait()))?.Single(); + var r_not = ((byte[]?)(await db.StringGetAsync("not").ForAwait()))?.Single(); + + Assert.Equal((byte)(3 & 6 & 12), r_and); + Assert.Equal((byte)(3 | 6 | 12), r_or); + Assert.Equal((byte)(3 ^ 6 ^ 12), r_xor); + Assert.Equal(unchecked((byte)(~3)), r_not); + } + + [Fact] + public async Task BitOpExtended() + { + await using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var keyX = prefix + "X"; + var keyY1 = prefix + "Y1"; + var keyY2 = prefix + "Y2"; + var keyY3 = prefix + "Y3"; + + // Clean up keys + db.KeyDelete([keyX, keyY1, keyY2, keyY3], CommandFlags.FireAndForget); + + // Set up test data with more complex patterns + // X = 11110000 (240) + // Y1 = 10101010 (170) + // Y2 = 01010101 (85) + // Y3 = 11001100 (204) + db.StringSet(keyX, new byte[] { 240 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY1, new byte[] { 170 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY2, new byte[] { 85 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY3, new byte[] { 204 }, flags: CommandFlags.FireAndForget); + + // Test DIFF: X ∧ ¬(Y1 ∨ Y2 ∨ Y3) + // Y1 ∨ Y2 ∨ Y3 = 170 | 85 | 204 = 255 + // X ∧ ¬(Y1 ∨ Y2 ∨ Y3) = 240 & ~255 = 240 & 0 = 0 + var len_diff = await db.StringBitOperationAsync(Bitwise.Diff, "diff", [keyX, keyY1, keyY2, keyY3]); + Assert.Equal(1, len_diff); + var r_diff = ((byte[]?)(await db.StringGetAsync("diff")))?.Single(); + Assert.Equal((byte)0, r_diff); + + // Test DIFF1: ¬X ∧ (Y1 ∨ Y2 ∨ Y3) + // ¬X = ~240 = 15 + // Y1 ∨ Y2 ∨ Y3 = 255 + // ¬X ∧ (Y1 ∨ Y2 ∨ Y3) = 15 & 255 = 15 + var len_diff1 = await db.StringBitOperationAsync(Bitwise.Diff1, "diff1", [keyX, keyY1, keyY2, keyY3]); + Assert.Equal(1, len_diff1); + var r_diff1 = ((byte[]?)(await db.StringGetAsync("diff1")))?.Single(); + Assert.Equal((byte)15, r_diff1); + + // Test ANDOR: X ∧ (Y1 ∨ Y2 ∨ Y3) + // Y1 ∨ Y2 ∨ Y3 = 255 + // X ∧ (Y1 ∨ Y2 ∨ Y3) = 240 & 255 = 240 + var len_andor = await db.StringBitOperationAsync(Bitwise.AndOr, "andor", [keyX, keyY1, keyY2, keyY3]); + Assert.Equal(1, len_andor); + var r_andor = ((byte[]?)(await db.StringGetAsync("andor")))?.Single(); + Assert.Equal((byte)240, r_andor); + + // Test ONE: bits set in exactly one bitmap + // For X=240, Y1=170, Y2=85, Y3=204 + // We need to count bits that appear in exactly one of these values + var len_one = await db.StringBitOperationAsync(Bitwise.One, "one", [keyX, keyY1, keyY2, keyY3]); + Assert.Equal(1, len_one); + var r_one = ((byte[]?)(await db.StringGetAsync("one")))?.Single(); + + // Calculate expected ONE result manually + // Bit 7: X=1, Y1=1, Y2=0, Y3=1 -> count=3, not exactly 1 + // Bit 6: X=1, Y1=0, Y2=1, Y3=1 -> count=3, not exactly 1 + // Bit 5: X=1, Y1=1, Y2=0, Y3=0 -> count=2, not exactly 1 + // Bit 4: X=1, Y1=0, Y2=1, Y3=0 -> count=2, not exactly 1 + // Bit 3: X=0, Y1=1, Y2=0, Y3=1 -> count=2, not exactly 1 + // Bit 2: X=0, Y1=0, Y2=1, Y3=1 -> count=2, not exactly 1 + // Bit 1: X=0, Y1=1, Y2=0, Y3=0 -> count=1, exactly 1! -> bit should be set + // Bit 0: X=0, Y1=0, Y2=1, Y3=0 -> count=1, exactly 1! -> bit should be set + // Expected result: 00000011 = 3 + Assert.Equal((byte)3, r_one); + } + + [Fact] + public async Task BitOpTwoOperands() + { + await using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var key1 = prefix + "1"; + var key2 = prefix + "2"; + + // Clean up keys + db.KeyDelete([key1, key2], CommandFlags.FireAndForget); + + // Test with two operands: key1=10101010 (170), key2=11001100 (204) + db.StringSet(key1, new byte[] { 170 }, flags: CommandFlags.FireAndForget); + db.StringSet(key2, new byte[] { 204 }, flags: CommandFlags.FireAndForget); + + // Test DIFF: key1 ∧ ¬key2 = 170 & ~204 = 170 & 51 = 34 + var len_diff = await db.StringBitOperationAsync(Bitwise.Diff, "diff2", [key1, key2]); + Assert.Equal(1, len_diff); + var r_diff = ((byte[]?)(await db.StringGetAsync("diff2")))?.Single(); + Assert.Equal((byte)(170 & ~204), r_diff); + + // Test ONE with two operands (should be equivalent to XOR) + var len_one = await db.StringBitOperationAsync(Bitwise.One, "one2", [key1, key2]); + Assert.Equal(1, len_one); + var r_one = ((byte[]?)(await db.StringGetAsync("one2")))?.Single(); + Assert.Equal((byte)(170 ^ 204), r_one); + + // Verify ONE equals XOR for two operands + var len_xor = await db.StringBitOperationAsync(Bitwise.Xor, "xor2", [key1, key2]); + Assert.Equal(1, len_xor); + var r_xor = ((byte[]?)(await db.StringGetAsync("xor2")))?.Single(); + Assert.Equal(r_one, r_xor); + } + + [Fact] + public async Task BitOpDiff() + { + await using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var keyX = prefix + "X"; + var keyY1 = prefix + "Y1"; + var keyY2 = prefix + "Y2"; + var keyResult = prefix + "result"; + + // Clean up keys + db.KeyDelete([keyX, keyY1, keyY2, keyResult], CommandFlags.FireAndForget); + + // Set up test data: X=11110000, Y1=10100000, Y2=01010000 + // Expected DIFF result: X ∧ ¬(Y1 ∨ Y2) = 11110000 ∧ ¬(11110000) = 00000000 + db.StringSet(keyX, new byte[] { 0b11110000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY1, new byte[] { 0b10100000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY2, new byte[] { 0b01010000 }, flags: CommandFlags.FireAndForget); + + var length = db.StringBitOperation(Bitwise.Diff, keyResult, [keyX, keyY1, keyY2]); + Assert.Equal(1, length); + + var result = ((byte[]?)db.StringGet(keyResult))?.Single(); + // X ∧ ¬(Y1 ∨ Y2) = 11110000 ∧ ¬(11110000) = 11110000 ∧ 00001111 = 00000000 + Assert.Equal((byte)0b00000000, result); + } + + [Fact] + public async Task BitOpDiff1() + { + await using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var keyX = prefix + "X"; + var keyY1 = prefix + "Y1"; + var keyY2 = prefix + "Y2"; + var keyResult = prefix + "result"; + + // Clean up keys + db.KeyDelete([keyX, keyY1, keyY2, keyResult], CommandFlags.FireAndForget); + + // Set up test data: X=11000000, Y1=10100000, Y2=01010000 + // Expected DIFF1 result: ¬X ∧ (Y1 ∨ Y2) = ¬11000000 ∧ (10100000 ∨ 01010000) = 00111111 ∧ 11110000 = 00110000 + db.StringSet(keyX, new byte[] { 0b11000000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY1, new byte[] { 0b10100000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY2, new byte[] { 0b01010000 }, flags: CommandFlags.FireAndForget); + + var length = db.StringBitOperation(Bitwise.Diff1, keyResult, [keyX, keyY1, keyY2]); + Assert.Equal(1, length); + + var result = ((byte[]?)db.StringGet(keyResult))?.Single(); + // ¬X ∧ (Y1 ∨ Y2) = 00111111 ∧ 11110000 = 00110000 + Assert.Equal((byte)0b00110000, result); + } + + [Fact] + public async Task BitOpAndOr() + { + await using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var keyX = prefix + "X"; + var keyY1 = prefix + "Y1"; + var keyY2 = prefix + "Y2"; + var keyResult = prefix + "result"; + + // Clean up keys + db.KeyDelete([keyX, keyY1, keyY2, keyResult], CommandFlags.FireAndForget); + + // Set up test data: X=11110000, Y1=10100000, Y2=01010000 + // Expected ANDOR result: X ∧ (Y1 ∨ Y2) = 11110000 ∧ (10100000 ∨ 01010000) = 11110000 ∧ 11110000 = 11110000 + db.StringSet(keyX, new byte[] { 0b11110000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY1, new byte[] { 0b10100000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY2, new byte[] { 0b01010000 }, flags: CommandFlags.FireAndForget); + + var length = db.StringBitOperation(Bitwise.AndOr, keyResult, [keyX, keyY1, keyY2]); + Assert.Equal(1, length); + + var result = ((byte[]?)db.StringGet(keyResult))?.Single(); + // X ∧ (Y1 ∨ Y2) = 11110000 ∧ 11110000 = 11110000 + Assert.Equal((byte)0b11110000, result); + } + + [Fact] + public async Task BitOpOne() + { + await using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var key1 = prefix + "1"; + var key2 = prefix + "2"; + var key3 = prefix + "3"; + var keyResult = prefix + "result"; + + // Clean up keys + db.KeyDelete([key1, key2, key3, keyResult], CommandFlags.FireAndForget); + + // Set up test data: key1=10100000, key2=01010000, key3=00110000 + // Expected ONE result: bits set in exactly one bitmap = 11000000 + db.StringSet(key1, new byte[] { 0b10100000 }, flags: CommandFlags.FireAndForget); + db.StringSet(key2, new byte[] { 0b01010000 }, flags: CommandFlags.FireAndForget); + db.StringSet(key3, new byte[] { 0b00110000 }, flags: CommandFlags.FireAndForget); + + var length = db.StringBitOperation(Bitwise.One, keyResult, [key1, key2, key3]); + Assert.Equal(1, length); + + var result = ((byte[]?)db.StringGet(keyResult))?.Single(); + // Bits set in exactly one: position 7 (key1 only), position 6 (key2 only) = 11000000 + Assert.Equal((byte)0b11000000, result); + } + + [Fact] + public async Task BitOpDiffAsync() + { + await using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var keyX = prefix + "X"; + var keyY1 = prefix + "Y1"; + var keyResult = prefix + "result"; + + // Clean up keys + db.KeyDelete([keyX, keyY1, keyResult], CommandFlags.FireAndForget); + + // Set up test data: X=11110000, Y1=10100000 + // Expected DIFF result: X ∧ ¬Y1 = 11110000 ∧ 01011111 = 01010000 + db.StringSet(keyX, new byte[] { 0b11110000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY1, new byte[] { 0b10100000 }, flags: CommandFlags.FireAndForget); + + var length = await db.StringBitOperationAsync(Bitwise.Diff, keyResult, [keyX, keyY1]); + Assert.Equal(1, length); + + var result = ((byte[]?)await db.StringGetAsync(keyResult))?.Single(); + // X ∧ ¬Y1 = 11110000 ∧ 01011111 = 01010000 + Assert.Equal((byte)0b01010000, result); + } + + [Fact] + public async Task BitOpEdgeCases() + { + await using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var keyEmpty = prefix + "empty"; + var keyNonEmpty = prefix + "nonempty"; + var keyResult = prefix + "result"; + + // Clean up keys + db.KeyDelete([keyEmpty, keyNonEmpty, keyResult], CommandFlags.FireAndForget); + + // Test with empty bitmap + db.StringSet(keyNonEmpty, new byte[] { 0b11110000 }, flags: CommandFlags.FireAndForget); + + // DIFF with empty key should return the first key + var length = db.StringBitOperation(Bitwise.Diff, keyResult, [keyNonEmpty, keyEmpty]); + Assert.Equal(1, length); + + var result = ((byte[]?)db.StringGet(keyResult))?.Single(); + Assert.Equal((byte)0b11110000, result); + + // ONE with single key should return that key + length = db.StringBitOperation(Bitwise.One, keyResult, [keyNonEmpty]); + Assert.Equal(1, length); + + result = ((byte[]?)db.StringGet(keyResult))?.Single(); + Assert.Equal((byte)0b11110000, result); + } + + [Fact] + public async Task BitPosition() + { + await using var conn = Create(require: RedisFeatures.v2_6_0); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, flags: CommandFlags.FireAndForget); + db.StringSet(key, "foo", flags: CommandFlags.FireAndForget); + + var r1 = db.StringBitPosition(key, true); + var r2 = db.StringBitPosition(key, true, 10, 10); + var r3 = db.StringBitPosition(key, true, 1, 3); + + Assert.Equal(1, r1); + Assert.Equal(-1, r2); + Assert.Equal(9, r3); + + // Async + r1 = await db.StringBitPositionAsync(key, true); + r2 = await db.StringBitPositionAsync(key, true, 10, 10); + r3 = await db.StringBitPositionAsync(key, true, 1, 3); + + Assert.Equal(1, r1); + Assert.Equal(-1, r2); + Assert.Equal(9, r3); + } + + [Fact] + public async Task BitPositionWithBitUnit() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key = Me(); + db.KeyDelete(key, flags: CommandFlags.FireAndForget); + db.StringSet(key, "foo", flags: CommandFlags.FireAndForget); + + var r1 = db.StringBitPositionAsync(key, true, 1, 3); // Using default byte + var r2 = db.StringBitPositionAsync(key, true, 1, 3, StringIndexType.Bit); + + Assert.Equal(9, await r1); + Assert.Equal(1, await r2); + } + + [Fact] + public async Task RangeString() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var key = Me(); + db.StringSet(key, "hello world", flags: CommandFlags.FireAndForget); + var result = db.StringGetRangeAsync(key, 2, 6); + Assert.Equal("llo w", await result); + } + + [Fact] + public async Task HashStringLengthAsync() + { + await using var conn = Create(require: RedisFeatures.v3_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string value = "hello world"; + db.HashSet(key, "field", value); + var resAsync = db.HashStringLengthAsync(key, "field"); + var resNonExistingAsync = db.HashStringLengthAsync(key, "non-existing-field"); + Assert.Equal(value.Length, await resAsync); + Assert.Equal(0, await resNonExistingAsync); + } + + [Fact] + public async Task HashStringLength() + { + await using var conn = Create(require: RedisFeatures.v3_2_0); + + var db = conn.GetDatabase(); + var key = Me(); + const string value = "hello world"; + db.HashSet(key, "field", value); + Assert.Equal(value.Length, db.HashStringLength(key, "field")); + Assert.Equal(0, db.HashStringLength(key, "non-existing-field")); + } + + [Fact] + public async Task LongestCommonSubsequence() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key1 = Me() + "1"; + var key2 = Me() + "2"; + db.KeyDelete(key1); + db.KeyDelete(key2); + db.StringSet(key1, "ohmytext"); + db.StringSet(key2, "mynewtext"); + + Assert.Equal("mytext", db.StringLongestCommonSubsequence(key1, key2)); + Assert.Equal(6, db.StringLongestCommonSubsequenceLength(key1, key2)); + + var stringMatchResult = db.StringLongestCommonSubsequenceWithMatches(key1, key2); + Assert.Equal(2, stringMatchResult.Matches.Length); // "my" and "text" are the two matches of the result + Assert.Equivalent(new LCSMatchResult.LCSMatch(4, 5, length: 4), stringMatchResult.Matches[0]); // the string "text" starts at index 4 in the first string and at index 5 in the second string + Assert.Equivalent(new LCSMatchResult.LCSMatch(2, 0, length: 2), stringMatchResult.Matches[1]); // the string "my" starts at index 2 in the first string and at index 0 in the second string + + stringMatchResult = db.StringLongestCommonSubsequenceWithMatches(key1, key2, 5); + Assert.Empty(stringMatchResult.Matches); // no matches longer than 5 characters + Assert.Equal(6, stringMatchResult.LongestMatchLength); + + // Missing keys + db.KeyDelete(key1); + Assert.Equal(string.Empty, db.StringLongestCommonSubsequence(key1, key2)); + db.KeyDelete(key2); + Assert.Equal(string.Empty, db.StringLongestCommonSubsequence(key1, key2)); + stringMatchResult = db.StringLongestCommonSubsequenceWithMatches(key1, key2); + Assert.NotNull(stringMatchResult.Matches); + Assert.Empty(stringMatchResult.Matches); + Assert.Equal(0, stringMatchResult.LongestMatchLength); + + // Default value + stringMatchResult = db.StringLongestCommonSubsequenceWithMatches(key1, key2, flags: CommandFlags.FireAndForget); + Assert.True(stringMatchResult.IsEmpty); + } + + [Fact] + public async Task LongestCommonSubsequenceAsync() + { + await using var conn = Create(require: RedisFeatures.v7_0_0_rc1); + + var db = conn.GetDatabase(); + var key1 = Me() + "1"; + var key2 = Me() + "2"; + db.KeyDelete(key1); + db.KeyDelete(key2); + db.StringSet(key1, "ohmytext"); + db.StringSet(key2, "mynewtext"); + + Assert.Equal("mytext", await db.StringLongestCommonSubsequenceAsync(key1, key2)); + Assert.Equal(6, await db.StringLongestCommonSubsequenceLengthAsync(key1, key2)); + + var stringMatchResult = await db.StringLongestCommonSubsequenceWithMatchesAsync(key1, key2); + Assert.Equal(2, stringMatchResult.Matches.Length); // "my" and "text" are the two matches of the result + Assert.Equivalent(new LCSMatchResult.LCSMatch(4, 5, length: 4), stringMatchResult.Matches[0]); // the string "text" starts at index 4 in the first string and at index 5 in the second string + Assert.Equivalent(new LCSMatchResult.LCSMatch(2, 0, length: 2), stringMatchResult.Matches[1]); // the string "my" starts at index 2 in the first string and at index 0 in the second string + + stringMatchResult = await db.StringLongestCommonSubsequenceWithMatchesAsync(key1, key2, 5); + Assert.Empty(stringMatchResult.Matches); // no matches longer than 5 characters + Assert.Equal(6, stringMatchResult.LongestMatchLength); + + // Missing keys + db.KeyDelete(key1); + Assert.Equal(string.Empty, await db.StringLongestCommonSubsequenceAsync(key1, key2)); + db.KeyDelete(key2); + Assert.Equal(string.Empty, await db.StringLongestCommonSubsequenceAsync(key1, key2)); + stringMatchResult = await db.StringLongestCommonSubsequenceWithMatchesAsync(key1, key2); + Assert.NotNull(stringMatchResult.Matches); + Assert.Empty(stringMatchResult.Matches); + Assert.Equal(0, stringMatchResult.LongestMatchLength); + + // Default value + stringMatchResult = await db.StringLongestCommonSubsequenceWithMatchesAsync(key1, key2, flags: CommandFlags.FireAndForget); + Assert.True(stringMatchResult.IsEmpty); + } + + private static byte[] Encode(string value) => Encoding.UTF8.GetBytes(value); + private static string? Decode(byte[]? value) => value is null ? null : Encoding.UTF8.GetString(value); +} diff --git a/tests/StackExchange.Redis.Tests/SyncContextTests.cs b/tests/StackExchange.Redis.Tests/SyncContextTests.cs new file mode 100644 index 000000000..b98caefeb --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SyncContextTests.cs @@ -0,0 +1,177 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests +{ + public class SyncContextTests(ITestOutputHelper testOutput) : TestBase(testOutput) + { + /* Note A (referenced below) + * + * When sync-context is *enabled*, we don't validate OpCount > 0 - this is because *with the additional checks*, + * it can genuinely happen that by the time we actually await it, it has completed - which results in a brittle test. + */ + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task DetectSyncContextUnsafe(bool continueOnCapturedContext) + { + using var ctx = new MySyncContext(Writer); + Assert.Equal(0, ctx.OpCount); + await Task.Delay(100).ConfigureAwait(continueOnCapturedContext); + + AssertState(continueOnCapturedContext, ctx); + } + + private void AssertState(bool continueOnCapturedContext, MySyncContext ctx) + { + Log($"Context in AssertState: {ctx}"); + if (continueOnCapturedContext) + { + Assert.True(ctx.IsCurrent, nameof(ctx.IsCurrent)); + // see note A re OpCount + } + else + { + // no guarantees on sync-context still being current; depends on sync vs async + Assert.Equal(0, ctx.OpCount); + } + } + + [Fact] + public async Task SyncPing() + { + using var ctx = new MySyncContext(Writer); + await using var conn = Create(); + Assert.Equal(0, ctx.OpCount); + var db = conn.GetDatabase(); + db.Ping(); + Assert.Equal(0, ctx.OpCount); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task AsyncPing(bool continueOnCapturedContext) + { + using var ctx = new MySyncContext(Writer); + await using var conn = Create(); + Assert.Equal(0, ctx.OpCount); + var db = conn.GetDatabase(); + Log($"Context before await: {ctx}"); + await db.PingAsync().ConfigureAwait(continueOnCapturedContext); + + AssertState(continueOnCapturedContext, ctx); + } + + [Fact] + public async Task SyncConfigure() + { + using var ctx = new MySyncContext(Writer); + await using var conn = Create(); + Assert.Equal(0, ctx.OpCount); + Assert.True(conn.Configure()); + Assert.Equal(0, ctx.OpCount); + } + + [Theory] + [InlineData(true)] // fail: Expected: Not RanToCompletion, Actual: RanToCompletion + [InlineData(false)] // pass + public async Task AsyncConfigure(bool continueOnCapturedContext) + { + using var ctx = new MySyncContext(Writer); + await using var conn = Create(); + + Log($"Context initial: {ctx}"); + await Task.Delay(500); + await conn.GetDatabase().PingAsync(); // ensure we're all ready + ctx.Reset(); + Log($"Context before: {ctx}"); + + Assert.Equal(0, ctx.OpCount); + Assert.True(await conn.ConfigureAsync(Writer).ConfigureAwait(continueOnCapturedContext), "config ran"); + + AssertState(continueOnCapturedContext, ctx); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ConnectAsync(bool continueOnCapturedContext) + { + using var ctx = new MySyncContext(Writer); + var config = GetConfiguration(); // not ideal, but sufficient + await ConnectionMultiplexer.ConnectAsync(config, Writer).ConfigureAwait(continueOnCapturedContext); + + AssertState(continueOnCapturedContext, ctx); + } + + public sealed class MySyncContext : SynchronizationContext, IDisposable + { + private readonly SynchronizationContext? _previousContext; + private readonly TextWriter _log; + public MySyncContext(TextWriter log) + { + _previousContext = Current; + _log = log; + SetSynchronizationContext(this); + } + public int OpCount => Volatile.Read(ref _opCount); + private int _opCount; + private void Incr() => Interlocked.Increment(ref _opCount); + + public void Reset() => Thread.VolatileWrite(ref _opCount, 0); + + public override string ToString() => $"Sync context ({(IsCurrent ? "active" : "inactive")}): {OpCount}"; + + void IDisposable.Dispose() => SetSynchronizationContext(_previousContext); + + public override void Post(SendOrPostCallback d, object? state) + { + Log(_log, "sync-ctx: Post"); + Incr(); + ThreadPool.QueueUserWorkItem( + static state => + { + var tuple = (Tuple)state!; + tuple.Item1.Invoke(tuple.Item2, tuple.Item3); + }, + Tuple.Create(this, d, state)); + } + + private void Invoke(SendOrPostCallback d, object? state) + { + Log(_log, "sync-ctx: Invoke"); + if (!IsCurrent) SetSynchronizationContext(this); + d(state); + } + + public override void Send(SendOrPostCallback d, object? state) + { + Log(_log, "sync-ctx: Send"); + Incr(); + Invoke(d, state); + } + + public bool IsCurrent => ReferenceEquals(this, Current); + + public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) + { + Incr(); + return base.Wait(waitHandles, waitAll, millisecondsTimeout); + } + public override void OperationStarted() + { + Incr(); + base.OperationStarted(); + } + public override void OperationCompleted() + { + Incr(); + base.OperationCompleted(); + } + } + } +} diff --git a/tests/StackExchange.Redis.Tests/TaskExtensions.cs b/tests/StackExchange.Redis.Tests/TaskExtensions.cs new file mode 100644 index 000000000..19db48f7c --- /dev/null +++ b/tests/StackExchange.Redis.Tests/TaskExtensions.cs @@ -0,0 +1,49 @@ +#if !NET +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace StackExchange.Redis.Tests; + +internal static class TaskExtensions +{ + // suboptimal polyfill version of the .NET 6+ API; I'm not recommending this for production use, + // but it's good enough for tests + public static Task WaitAsync(this Task task, CancellationToken cancellationToken) + { + if (task.IsCompleted || !cancellationToken.CanBeCanceled) return task; + return Wrap(task, cancellationToken); + + static async Task Wrap(Task task, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + using var reg = cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)); + _ = task.ContinueWith(t => + { + if (t.IsCanceled) tcs.TrySetCanceled(); + else if (t.IsFaulted) tcs.TrySetException(t.Exception!); + else tcs.TrySetResult(t.Result); + }); + return await tcs.Task; + } + } + + public static Task WaitAsync(this Task task, TimeSpan timeout) + { + if (task.IsCompleted) return task; + return Wrap(task, timeout); + + static async Task Wrap(Task task, TimeSpan timeout) + { + Task other = Task.Delay(timeout); + var first = await Task.WhenAny(task, other); + if (ReferenceEquals(first, other)) + { + throw new TimeoutException(); + } + return await task; + } + } +} + +#endif diff --git a/tests/StackExchange.Redis.Tests/TestBase.cs b/tests/StackExchange.Redis.Tests/TestBase.cs new file mode 100644 index 000000000..68dbb6055 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/TestBase.cs @@ -0,0 +1,568 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using StackExchange.Redis.Profiling; +using StackExchange.Redis.Tests.Helpers; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public abstract class TestBase : IDisposable +{ + private ITestOutputHelper Output { get; } + protected TextWriterOutputHelper Writer { get; } + protected virtual string GetConfiguration() => GetDefaultConfiguration(); + internal static string GetDefaultConfiguration() => TestConfig.Current.PrimaryServerAndPort; + + private readonly SharedConnectionFixture? _fixture; + + protected bool SharedFixtureAvailable => _fixture != null && _fixture.IsEnabled && !HighIntegrity; + + protected TestBase(ITestOutputHelper output, SharedConnectionFixture? fixture = null) + { + Output = output; + Output.WriteFrameworkVersion(); + Writer = new TextWriterOutputHelper(output); + _fixture = fixture; + ClearAmbientFailures(); + } + + /// + /// Useful to temporarily get extra worker threads for an otherwise synchronous test case which will 'block' the thread, + /// on a synchronous API like or . + /// + /// + /// Must NOT be used for test cases which *goes async*, as then the inferred return type will become 'async void', + /// and we will fail to observe the result of the async part. + /// + /// See 'ConnectFailTimeout' class for example usage. + protected static Task RunBlockingSynchronousWithExtraThreadAsync(Action testScenario) => Task.Factory.StartNew(testScenario, CancellationToken.None, TaskCreationOptions.LongRunning | TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + + public static void Log(TextWriter output, string message) + { + lock (output) + { + output?.WriteLine(Time() + ": " + message); + } + } + + protected void Log(string? message, params object[] args) + { + if (args is { Length: > 0 }) + { + Output.WriteLine(Time() + ": " + message, args); + } + else + { + // avoid "not intended as a format specifier" scenarios + Output.WriteLine(Time() + ": " + message); + } + } + + protected ProfiledCommandEnumerable Log(ProfilingSession session) + { + var profile = session.FinishProfiling(); + foreach (var command in profile) + { + Writer.WriteLineNoTime(command.ToString()); + } + return profile; + } + + protected static void CollectGarbage() + { + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); + GC.WaitForPendingFinalizers(); + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Trust me yo")] + public void Dispose() + { + _fixture?.Teardown(Writer); + Teardown(); + Writer.Dispose(); + GC.SuppressFinalize(this); + } + +#if VERBOSE + protected const int AsyncOpsQty = 100, SyncOpsQty = 10; +#else + protected const int AsyncOpsQty = 2000, SyncOpsQty = 2000; +#endif + + static TestBase() + { + TaskScheduler.UnobservedTaskException += (sender, args) => + { + Console.WriteLine("Unobserved: " + args.Exception); + args.SetObserved(); + lock (sharedFailCount) + { + if (sharedFailCount != null) + { + sharedFailCount.Value++; + } + } + lock (backgroundExceptions) + { + backgroundExceptions.Add(args.Exception.ToString()); + } + }; + Console.WriteLine("Setup information:"); + Console.WriteLine(" GC IsServer: " + GCSettings.IsServerGC); + Console.WriteLine(" GC LOH Mode: " + GCSettings.LargeObjectHeapCompactionMode); + Console.WriteLine(" GC Latency Mode: " + GCSettings.LatencyMode); + } + + internal static string Time() => DateTime.UtcNow.ToString("HH:mm:ss.ffff"); + protected void OnConnectionFailed(object? sender, ConnectionFailedEventArgs e) + { + Interlocked.Increment(ref privateFailCount); + lock (privateExceptions) + { + privateExceptions.Add($"{Time()}: Connection failed ({e.FailureType}): {EndPointCollection.ToString(e.EndPoint)}/{e.ConnectionType}: {e.Exception}"); + } + Log($"Connection Failed ({e.ConnectionType},{e.FailureType}): {e.Exception}"); + } + + protected void OnInternalError(object? sender, InternalErrorEventArgs e) + { + Interlocked.Increment(ref privateFailCount); + lock (privateExceptions) + { + privateExceptions.Add(Time() + ": Internal error: " + e.Origin + ", " + EndPointCollection.ToString(e.EndPoint) + "/" + e.ConnectionType); + } + } + + private int privateFailCount; + private static readonly AsyncLocal sharedFailCount = new AsyncLocal(); + private volatile int expectedFailCount; + + private readonly List privateExceptions = []; + private static readonly List backgroundExceptions = []; + + public void ClearAmbientFailures() + { + Interlocked.Exchange(ref privateFailCount, 0); + lock (sharedFailCount) + { + sharedFailCount.Value = 0; + } + expectedFailCount = 0; + lock (privateExceptions) + { + privateExceptions.Clear(); + } + lock (backgroundExceptions) + { + backgroundExceptions.Clear(); + } + } + + public void SetExpectedAmbientFailureCount(int count) + { + expectedFailCount = count; + } + + public void Teardown() + { + int sharedFails; + lock (sharedFailCount) + { + sharedFails = sharedFailCount.Value; + sharedFailCount.Value = 0; + } + if (expectedFailCount >= 0 && (sharedFails + privateFailCount) != expectedFailCount) + { + lock (privateExceptions) + { + foreach (var item in privateExceptions.Take(5)) + { + Log(item); + } + } + lock (backgroundExceptions) + { + foreach (var item in backgroundExceptions.Take(5)) + { + Log(item); + } + } + Assert.Skip($"There were {privateFailCount} private and {sharedFailCount.Value} ambient exceptions; expected {expectedFailCount}."); + } + var pool = SocketManager.Shared?.SchedulerPool; + Log($"Service Counts: (Scheduler) Queue: {pool?.TotalServicedByQueue.ToString()}, Pool: {pool?.TotalServicedByPool.ToString()}, Workers: {pool?.WorkerCount.ToString()}, Available: {pool?.AvailableCount.ToString()}"); + } + + protected static IServer GetServer(IConnectionMultiplexer muxer) + { + IServer? result = null; + foreach (var server in muxer.GetServers()) + { + if (server.IsReplica || !server.IsConnected) continue; + if (result != null) throw new InvalidOperationException("Requires exactly one primary endpoint (found " + server.EndPoint + " and " + result.EndPoint + ")"); + result = server; + } + if (result == null) throw new InvalidOperationException("Requires exactly one primary endpoint (found none)"); + return result; + } + + protected static IServer GetAnyPrimary(IConnectionMultiplexer muxer) + { + foreach (var endpoint in muxer.GetEndPoints()) + { + var server = muxer.GetServer(endpoint); + if (!server.IsReplica) return server; + } + throw new InvalidOperationException("Requires a primary endpoint (found none)"); + } + + internal virtual bool HighIntegrity => false; + + internal virtual IInternalConnectionMultiplexer Create( + string? clientName = null, + int? syncTimeout = null, + int? asyncTimeout = null, + bool? allowAdmin = null, + int? keepAlive = null, + int? connectTimeout = null, + string? password = null, + string? tieBreaker = null, + TextWriter? log = null, + bool fail = true, + string[]? disabledCommands = null, + string[]? enabledCommands = null, + bool checkConnect = true, + string? failMessage = null, + string? channelPrefix = null, + Proxy? proxy = null, + string? configuration = null, + bool logTransactionData = true, + bool shared = true, + int? defaultDatabase = null, + BacklogPolicy? backlogPolicy = null, + Version? require = null, + RedisProtocol? protocol = null, + [CallerMemberName] string caller = "") + { + if (Output == null) + { + Assert.Fail("Failure: Be sure to call the TestBase constructor like this: BasicOpsTests(ITestOutputHelper output) : base(output) { }"); + } + + // Default to protocol context if not explicitly passed in + protocol ??= TestContext.Current.GetProtocol(); + + // Share a connection if instructed to and we can - many specifics mean no sharing + bool highIntegrity = HighIntegrity; + if (shared && expectedFailCount == 0 + && _fixture != null && _fixture.IsEnabled + && GetConfiguration() == GetDefaultConfiguration() + && CanShare(allowAdmin, password, tieBreaker, fail, disabledCommands, enabledCommands, channelPrefix, proxy, configuration, defaultDatabase, backlogPolicy, highIntegrity)) + { + configuration = GetConfiguration(); + var fixtureConn = _fixture.GetConnection(this, protocol.Value, caller: caller); + // Only return if we match + TestBase.ThrowIfIncorrectProtocol(fixtureConn, protocol); + + if (configuration == _fixture.Configuration) + { + TestBase.ThrowIfBelowMinVersion(fixtureConn, require); + return fixtureConn; + } + } + + var conn = CreateDefault( + Writer, + configuration ?? GetConfiguration(), + clientName, + syncTimeout, + asyncTimeout, + allowAdmin, + keepAlive, + connectTimeout, + password, + tieBreaker, + log, + fail, + disabledCommands, + enabledCommands, + checkConnect, + failMessage, + channelPrefix, + proxy, + logTransactionData, + defaultDatabase, + backlogPolicy, + protocol, + highIntegrity, + caller); + + TestBase.ThrowIfIncorrectProtocol(conn, protocol); + TestBase.ThrowIfBelowMinVersion(conn, require); + + conn.InternalError += OnInternalError; + conn.ConnectionFailed += OnConnectionFailed; + conn.ConnectionRestored += (s, e) => Log($"Connection Restored ({e.ConnectionType},{e.FailureType}): {e.Exception}"); + return conn; + } + + internal static bool CanShare( + bool? allowAdmin, + string? password, + string? tieBreaker, + bool fail, + string[]? disabledCommands, + string[]? enabledCommands, + string? channelPrefix, + Proxy? proxy, + string? configuration, + int? defaultDatabase, + BacklogPolicy? backlogPolicy, + bool highIntegrity) + => enabledCommands == null + && disabledCommands == null + && fail + && channelPrefix == null + && proxy == null + && configuration == null + && password == null + && tieBreaker == null + && defaultDatabase == null + && (allowAdmin == null || allowAdmin == true) + && backlogPolicy == null + && !highIntegrity; + + internal static void ThrowIfIncorrectProtocol(IInternalConnectionMultiplexer conn, RedisProtocol? requiredProtocol) + { + if (requiredProtocol is null) + { + return; + } + + var serverProtocol = conn.GetServerEndPoint(conn.GetEndPoints()[0]).Protocol ?? RedisProtocol.Resp2; + if (serverProtocol != requiredProtocol) + { + Assert.Skip($"Requires protocol {requiredProtocol}, but connection is {serverProtocol}."); + } + } + + internal static void ThrowIfBelowMinVersion(IInternalConnectionMultiplexer conn, Version? requiredVersion) + { + if (requiredVersion is null) + { + return; + } + + var serverVersion = conn.GetServerEndPoint(conn.GetEndPoints()[0]).Version; + if (!serverVersion.IsAtLeast(requiredVersion)) + { + Assert.Skip($"Requires server version {requiredVersion}, but server is only {serverVersion}."); + } + } + + public static ConnectionMultiplexer CreateDefault( + TextWriter? output, + string configuration, + string? clientName = null, + int? syncTimeout = null, + int? asyncTimeout = null, + bool? allowAdmin = null, + int? keepAlive = null, + int? connectTimeout = null, + string? password = null, + string? tieBreaker = null, + TextWriter? log = null, + bool fail = true, + string[]? disabledCommands = null, + string[]? enabledCommands = null, + bool checkConnect = true, + string? failMessage = null, + string? channelPrefix = null, + Proxy? proxy = null, + bool logTransactionData = true, + int? defaultDatabase = null, + BacklogPolicy? backlogPolicy = null, + RedisProtocol? protocol = null, + bool highIntegrity = false, + [CallerMemberName] string caller = "") + { + StringWriter? localLog = null; + log ??= localLog = new StringWriter(); + try + { + var config = ConfigurationOptions.Parse(configuration); + if (disabledCommands != null && disabledCommands.Length != 0) + { + config.CommandMap = CommandMap.Create([.. disabledCommands], false); + } + else if (enabledCommands != null && enabledCommands.Length != 0) + { + config.CommandMap = CommandMap.Create([.. enabledCommands], true); + } + + if (Debugger.IsAttached) + { + syncTimeout = int.MaxValue; + } + + if (channelPrefix is not null) config.ChannelPrefix = RedisChannel.Literal(channelPrefix); + if (tieBreaker is not null) config.TieBreaker = tieBreaker; + if (password is not null) config.Password = string.IsNullOrEmpty(password) ? null : password; + if (clientName is not null) config.ClientName = clientName; + else if (!string.IsNullOrEmpty(caller)) config.ClientName = caller; + if (syncTimeout is not null) config.SyncTimeout = syncTimeout.Value; + if (asyncTimeout is not null) config.AsyncTimeout = asyncTimeout.Value; + if (allowAdmin is not null) config.AllowAdmin = allowAdmin.Value; + if (keepAlive is not null) config.KeepAlive = keepAlive.Value; + if (connectTimeout is not null) config.ConnectTimeout = connectTimeout.Value; + if (proxy is not null) config.Proxy = proxy.Value; + if (defaultDatabase is not null) config.DefaultDatabase = defaultDatabase.Value; + if (backlogPolicy is not null) config.BacklogPolicy = backlogPolicy; + if (protocol is not null) config.Protocol = protocol; + if (highIntegrity) config.HighIntegrity = highIntegrity; + var watch = Stopwatch.StartNew(); + var task = ConnectionMultiplexer.ConnectAsync(config, log); + if (!task.Wait(config.ConnectTimeout >= (int.MaxValue / 2) ? int.MaxValue : config.ConnectTimeout * 2)) + { + task.ContinueWith( + x => + { + try + { + GC.KeepAlive(x.Exception); + } + catch { /* No boom */ } + }, + TaskContinuationOptions.OnlyOnFaulted); + throw new TimeoutException("Connect timeout"); + } + watch.Stop(); + if (output != null) + { + Log(output, "Connect took: " + watch.ElapsedMilliseconds + "ms"); + } + var conn = task.Result; + if (checkConnect && !conn.IsConnected) + { + // If fail is true, we throw. + Assert.False(fail, failMessage + "Server is not available"); + Assert.Skip(failMessage + "Server is not available"); + } + if (output != null) + { + conn.MessageFaulted += (msg, ex, origin) => + { + output?.WriteLine($"Faulted from '{origin}': '{msg}' - '{(ex == null ? "(null)" : ex.Message)}'"); + if (ex != null && ex.Data.Contains("got")) + { + output?.WriteLine($"Got: '{ex.Data["got"]}'"); + } + }; + conn.Connecting += (e, t) => output?.WriteLine($"Connecting to {Format.ToString(e)} as {t}"); + if (logTransactionData) + { + conn.TransactionLog += msg => output?.WriteLine("tran: " + msg); + } + conn.InfoMessage += msg => output?.WriteLine(msg); + conn.Resurrecting += (e, t) => output?.WriteLine($"Resurrecting {Format.ToString(e)} as {t}"); + conn.Closing += complete => output?.WriteLine(complete ? "Closed" : "Closing..."); + } + return conn; + } + catch + { + if (localLog != null) output?.WriteLine(localLog.ToString()); + throw; + } + } + + public virtual string Me([CallerFilePath] string? filePath = null, [CallerMemberName] string? caller = null) => + Environment.Version.ToString() + "-" + GetType().Name + "-" + Path.GetFileNameWithoutExtension(filePath) + "-" + caller + TestContext.Current.KeySuffix(); + + protected TimeSpan RunConcurrent(Action work, int threads, int timeout = 10000, [CallerMemberName] string? caller = null) + { + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + if (threads < 1) + { + throw new ArgumentOutOfRangeException(nameof(threads)); + } + if (string.IsNullOrWhiteSpace(caller)) + { + caller = Me(); + } + + Stopwatch? watch = null; + ManualResetEvent allDone = new ManualResetEvent(false); + object token = new object(); + int active = 0; + void Callback() + { + lock (token) + { + int nowActive = Interlocked.Increment(ref active); + if (nowActive == threads) + { + watch = Stopwatch.StartNew(); + Monitor.PulseAll(token); + } + else + { + Monitor.Wait(token); + } + } + work(); + if (Interlocked.Decrement(ref active) == 0) + { + watch?.Stop(); + allDone.Set(); + } + } + + var threadArr = new Thread[threads]; + for (int i = 0; i < threads; i++) + { + var thd = new Thread(Callback) + { + Name = caller, + }; + threadArr[i] = thd; + thd.Start(); + } + if (!allDone.WaitOne(timeout)) + { + for (int i = 0; i < threads; i++) + { + var thd = threadArr[i]; +#if !NET6_0_OR_GREATER + if (thd.IsAlive) thd.Abort(); +#endif + } + throw new TimeoutException(); + } + + return watch?.Elapsed ?? TimeSpan.Zero; + } + + private static readonly TimeSpan DefaultWaitPerLoop = TimeSpan.FromMilliseconds(50); + protected static async Task UntilConditionAsync(TimeSpan maxWaitTime, Func predicate, TimeSpan? waitPerLoop = null) + { + TimeSpan spent = TimeSpan.Zero; + while (spent < maxWaitTime && !predicate()) + { + var wait = waitPerLoop ?? DefaultWaitPerLoop; + await Task.Delay(wait).ForAwait(); + spent += wait; + } + } +} diff --git a/tests/StackExchange.Redis.Tests/TransactionTests.cs b/tests/StackExchange.Redis.Tests/TransactionTests.cs new file mode 100644 index 000000000..3a0f1e40e --- /dev/null +++ b/tests/StackExchange.Redis.Tests/TransactionTests.cs @@ -0,0 +1,1434 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public class TransactionTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task BasicEmptyTran() + { + await using var conn = Create(); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + + var tran = db.CreateTransaction(); + + var result = tran.Execute(); + Assert.True(result); + } + + [Fact] + public async Task NestedTransactionThrows() + { + await using var conn = Create(); + + var db = conn.GetDatabase(); + var tran = db.CreateTransaction(); + var redisTransaction = Assert.IsType(tran); + Assert.Throws(() => redisTransaction.CreateTransaction(null)); + } + + [Theory] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(true, false, false)] + [InlineData(true, true, true)] + public async Task BasicTranWithExistsCondition(bool demandKeyExists, bool keyExists, bool expectTranResult) + { + await using var conn = Create(disabledCommands: ["info", "config"]); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + if (keyExists) db.StringSet(key2, "any value", flags: CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + Assert.Equal(keyExists, db.KeyExists(key2)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(demandKeyExists ? Condition.KeyExists(key2) : Condition.KeyNotExists(key2)); + var incr = tran.StringIncrementAsync(key); + var exec = tran.ExecuteAsync(); + var get = db.StringGet(key); + + Assert.Equal(expectTranResult, await exec); + if (demandKeyExists == keyExists) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.Equal(1, await incr); // eq: incr + Assert.Equal(1, (long)get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(incr)); // neq: incr + Assert.Equal(0, (long)get); // neq: get + } + } + + [Theory] + [InlineData("same", "same", true, true)] + [InlineData("x", "y", true, false)] + [InlineData("x", null, true, false)] + [InlineData(null, "y", true, false)] + [InlineData(null, null, true, true)] + + [InlineData("same", "same", false, false)] + [InlineData("x", "y", false, true)] + [InlineData("x", null, false, true)] + [InlineData(null, "y", false, true)] + [InlineData(null, null, false, false)] + public async Task BasicTranWithEqualsCondition(string? expected, string? value, bool expectEqual, bool expectTranResult) + { + await using var conn = Create(); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + if (value != null) db.StringSet(key2, value, flags: CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + Assert.Equal(value, db.StringGet(key2)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(expectEqual ? Condition.StringEqual(key2, expected) : Condition.StringNotEqual(key2, expected)); + var incr = tran.StringIncrementAsync(key); + var exec = tran.ExecuteAsync(); + var get = db.StringGet(key); + + Assert.Equal(expectTranResult, await exec); + if (expectEqual == (value == expected)) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.Equal(1, await incr); // eq: incr + Assert.Equal(1, (long)get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(incr)); // neq: incr + Assert.Equal(0, (long)get); // neq: get + } + } + + [Theory] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(true, false, false)] + [InlineData(true, true, true)] + public async Task BasicTranWithHashExistsCondition(bool demandKeyExists, bool keyExists, bool expectTranResult) + { + await using var conn = Create(disabledCommands: ["info", "config"]); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + RedisValue hashField = "field"; + if (keyExists) db.HashSet(key2, hashField, "any value", flags: CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + Assert.Equal(keyExists, db.HashExists(key2, hashField)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(demandKeyExists ? Condition.HashExists(key2, hashField) : Condition.HashNotExists(key2, hashField)); + var incr = tran.StringIncrementAsync(key); + var exec = tran.ExecuteAsync(); + var get = db.StringGet(key); + + Assert.Equal(expectTranResult, await exec); + if (demandKeyExists == keyExists) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.Equal(1, await incr); // eq: incr + Assert.Equal(1, (long)get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(incr)); // neq: incr + Assert.Equal(0, (long)get); // neq: get + } + } + + [Theory] + [InlineData("same", "same", true, true)] + [InlineData("x", "y", true, false)] + [InlineData("x", null, true, false)] + [InlineData(null, "y", true, false)] + [InlineData(null, null, true, true)] + + [InlineData("same", "same", false, false)] + [InlineData("x", "y", false, true)] + [InlineData("x", null, false, true)] + [InlineData(null, "y", false, true)] + [InlineData(null, null, false, false)] + public async Task BasicTranWithHashEqualsCondition(string? expected, string? value, bool expectEqual, bool expectedTranResult) + { + await using var conn = Create(); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + RedisValue hashField = "field"; + if (value != null) db.HashSet(key2, hashField, value, flags: CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + Assert.Equal(value, db.HashGet(key2, hashField)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(expectEqual ? Condition.HashEqual(key2, hashField, expected) : Condition.HashNotEqual(key2, hashField, expected)); + var incr = tran.StringIncrementAsync(key); + var exec = tran.ExecuteAsync(); + var get = db.StringGet(key); + + Assert.Equal(expectedTranResult, await exec); + if (expectEqual == (value == expected)) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.Equal(1, await incr); // eq: incr + Assert.Equal(1, (long)get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(incr)); // neq: incr + Assert.Equal(0, (long)get); // neq: get + } + } + + private static TaskStatus SafeStatus(Task task) + { + if (task.Status == TaskStatus.WaitingForActivation) + { + try + { + if (!task.Wait(1000)) throw new TimeoutException("timeout waiting for task to complete"); + } + catch (AggregateException ex) + when (ex.InnerException is TaskCanceledException + || (ex.InnerExceptions.Count == 1 && ex.InnerException is TaskCanceledException)) + { + return TaskStatus.Canceled; + } + catch (TaskCanceledException) + { + return TaskStatus.Canceled; + } + } + return task.Status; + } + + [Theory] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(true, false, false)] + [InlineData(true, true, true)] + public async Task BasicTranWithListExistsCondition(bool demandKeyExists, bool keyExists, bool expectTranResult) + { + await using var conn = Create(disabledCommands: ["info", "config"]); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + if (keyExists) db.ListRightPush(key2, "any value", flags: CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + Assert.Equal(keyExists, db.KeyExists(key2)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(demandKeyExists ? Condition.ListIndexExists(key2, 0) : Condition.ListIndexNotExists(key2, 0)); + var push = tran.ListRightPushAsync(key, "any value"); + var exec = tran.ExecuteAsync(); + var get = db.ListGetByIndex(key, 0); + + Assert.Equal(expectTranResult, await exec); + if (demandKeyExists == keyExists) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.Equal(1, await push); // eq: push + Assert.Equal("any value", get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(push)); // neq: push + Assert.Null((string?)get); // neq: get + } + } + + [Theory] + [InlineData("same", "same", true, true)] + [InlineData("x", "y", true, false)] + [InlineData("x", null, true, false)] + [InlineData(null, "y", true, false)] + [InlineData(null, null, true, true)] + + [InlineData("same", "same", false, false)] + [InlineData("x", "y", false, true)] + [InlineData("x", null, false, true)] + [InlineData(null, "y", false, true)] + [InlineData(null, null, false, false)] + public async Task BasicTranWithListEqualsCondition(string? expected, string? value, bool expectEqual, bool expectTranResult) + { + await using var conn = Create(); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + if (value != null) db.ListRightPush(key2, value, flags: CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + Assert.Equal(value, db.ListGetByIndex(key2, 0)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(expectEqual ? Condition.ListIndexEqual(key2, 0, expected) : Condition.ListIndexNotEqual(key2, 0, expected)); + var push = tran.ListRightPushAsync(key, "any value"); + var exec = tran.ExecuteAsync(); + var get = db.ListGetByIndex(key, 0); + + Assert.Equal(expectTranResult, await exec); + if (expectEqual == (value == expected)) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.Equal(1, await push); // eq: push + Assert.Equal("any value", get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(push)); // neq: push + Assert.Null((string?)get); // neq: get + } + } + + public enum ComparisonType + { + Equal, + LessThan, + GreaterThan, + } + + [Theory] + [InlineData("five", ComparisonType.Equal, 5L, false)] + [InlineData("four", ComparisonType.Equal, 4L, true)] + [InlineData("three", ComparisonType.Equal, 3L, false)] + [InlineData("", ComparisonType.Equal, 2L, false)] + [InlineData("", ComparisonType.Equal, 0L, true)] + [InlineData(null, ComparisonType.Equal, 1L, false)] + [InlineData(null, ComparisonType.Equal, 0L, true)] + + [InlineData("five", ComparisonType.LessThan, 5L, true)] + [InlineData("four", ComparisonType.LessThan, 4L, false)] + [InlineData("three", ComparisonType.LessThan, 3L, false)] + [InlineData("", ComparisonType.LessThan, 2L, true)] + [InlineData("", ComparisonType.LessThan, 0L, false)] + [InlineData(null, ComparisonType.LessThan, 1L, true)] + [InlineData(null, ComparisonType.LessThan, 0L, false)] + + [InlineData("five", ComparisonType.GreaterThan, 5L, false)] + [InlineData("four", ComparisonType.GreaterThan, 4L, false)] + [InlineData("three", ComparisonType.GreaterThan, 3L, true)] + [InlineData("", ComparisonType.GreaterThan, 2L, false)] + [InlineData("", ComparisonType.GreaterThan, 0L, false)] + [InlineData(null, ComparisonType.GreaterThan, 1L, false)] + [InlineData(null, ComparisonType.GreaterThan, 0L, false)] + public async Task BasicTranWithStringLengthCondition(string? value, ComparisonType type, long length, bool expectTranResult) + { + await using var conn = Create(); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + bool expectSuccess; + Condition? condition; + var valueLength = value?.Length ?? 0; + switch (type) + { + case ComparisonType.Equal: + expectSuccess = valueLength == length; + condition = Condition.StringLengthEqual(key2, length); + Assert.Contains("String length == " + length, condition.ToString()); + break; + case ComparisonType.GreaterThan: + expectSuccess = valueLength > length; + condition = Condition.StringLengthGreaterThan(key2, length); + Assert.Contains("String length > " + length, condition.ToString()); + break; + case ComparisonType.LessThan: + expectSuccess = valueLength < length; + condition = Condition.StringLengthLessThan(key2, length); + Assert.Contains("String length < " + length, condition.ToString()); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + + if (value != null) db.StringSet(key2, value, flags: CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + Assert.Equal(value, db.StringGet(key2)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(condition); + var push = tran.StringSetAsync(key, "any value"); + var exec = tran.ExecuteAsync(); + var get = db.StringLength(key); + + Assert.Equal(expectTranResult, await exec); + + if (expectSuccess) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.True(await push); // eq: push + Assert.Equal("any value".Length, get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(push)); // neq: push + Assert.Equal(0, get); // neq: get + } + } + + [Theory] + [InlineData("five", ComparisonType.Equal, 5L, false)] + [InlineData("four", ComparisonType.Equal, 4L, true)] + [InlineData("three", ComparisonType.Equal, 3L, false)] + [InlineData("", ComparisonType.Equal, 2L, false)] + [InlineData("", ComparisonType.Equal, 0L, true)] + + [InlineData("five", ComparisonType.LessThan, 5L, true)] + [InlineData("four", ComparisonType.LessThan, 4L, false)] + [InlineData("three", ComparisonType.LessThan, 3L, false)] + [InlineData("", ComparisonType.LessThan, 2L, true)] + [InlineData("", ComparisonType.LessThan, 0L, false)] + + [InlineData("five", ComparisonType.GreaterThan, 5L, false)] + [InlineData("four", ComparisonType.GreaterThan, 4L, false)] + [InlineData("three", ComparisonType.GreaterThan, 3L, true)] + [InlineData("", ComparisonType.GreaterThan, 2L, false)] + [InlineData("", ComparisonType.GreaterThan, 0L, false)] + public async Task BasicTranWithHashLengthCondition(string value, ComparisonType type, long length, bool expectTranResult) + { + await using var conn = Create(); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + bool expectSuccess; + Condition? condition; + var valueLength = value?.Length ?? 0; + switch (type) + { + case ComparisonType.Equal: + expectSuccess = valueLength == length; + condition = Condition.HashLengthEqual(key2, length); + break; + case ComparisonType.GreaterThan: + expectSuccess = valueLength > length; + condition = Condition.HashLengthGreaterThan(key2, length); + break; + case ComparisonType.LessThan: + expectSuccess = valueLength < length; + condition = Condition.HashLengthLessThan(key2, length); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + + for (var i = 0; i < valueLength; i++) + { + db.HashSet(key2, i, value![i].ToString(), flags: CommandFlags.FireAndForget); + } + Assert.False(db.KeyExists(key)); + Assert.Equal(valueLength, db.HashLength(key2)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(condition); + var push = tran.StringSetAsync(key, "any value"); + var exec = tran.ExecuteAsync(); + var get = db.StringLength(key); + + Assert.Equal(expectTranResult, await exec); + + if (expectSuccess) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.True(await push); // eq: push + Assert.Equal("any value".Length, get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(push)); // neq: push + Assert.Equal(0, get); // neq: get + } + } + + [Theory] + [InlineData("five", ComparisonType.Equal, 5L, false)] + [InlineData("four", ComparisonType.Equal, 4L, true)] + [InlineData("three", ComparisonType.Equal, 3L, false)] + [InlineData("", ComparisonType.Equal, 2L, false)] + [InlineData("", ComparisonType.Equal, 0L, true)] + + [InlineData("five", ComparisonType.LessThan, 5L, true)] + [InlineData("four", ComparisonType.LessThan, 4L, false)] + [InlineData("three", ComparisonType.LessThan, 3L, false)] + [InlineData("", ComparisonType.LessThan, 2L, true)] + [InlineData("", ComparisonType.LessThan, 0L, false)] + + [InlineData("five", ComparisonType.GreaterThan, 5L, false)] + [InlineData("four", ComparisonType.GreaterThan, 4L, false)] + [InlineData("three", ComparisonType.GreaterThan, 3L, true)] + [InlineData("", ComparisonType.GreaterThan, 2L, false)] + [InlineData("", ComparisonType.GreaterThan, 0L, false)] + public async Task BasicTranWithSetCardinalityCondition(string value, ComparisonType type, long length, bool expectTranResult) + { + await using var conn = Create(); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + bool expectSuccess; + Condition? condition; + var valueLength = value?.Length ?? 0; + switch (type) + { + case ComparisonType.Equal: + expectSuccess = valueLength == length; + condition = Condition.SetLengthEqual(key2, length); + break; + case ComparisonType.GreaterThan: + expectSuccess = valueLength > length; + condition = Condition.SetLengthGreaterThan(key2, length); + break; + case ComparisonType.LessThan: + expectSuccess = valueLength < length; + condition = Condition.SetLengthLessThan(key2, length); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + + for (var i = 0; i < valueLength; i++) + { + db.SetAdd(key2, i, flags: CommandFlags.FireAndForget); + } + Assert.False(db.KeyExists(key)); + Assert.Equal(valueLength, db.SetLength(key2)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(condition); + var push = tran.StringSetAsync(key, "any value"); + var exec = tran.ExecuteAsync(); + var get = db.StringLength(key); + + Assert.Equal(expectTranResult, await exec); + + if (expectSuccess) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.True(await push); // eq: push + Assert.Equal("any value".Length, get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(push)); // neq: push + Assert.Equal(0, get); // neq: get + } + } + + [Theory] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(true, false, false)] + [InlineData(true, true, true)] + public async Task BasicTranWithSetContainsCondition(bool demandKeyExists, bool keyExists, bool expectTranResult) + { + await using var conn = Create(disabledCommands: ["info", "config"]); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + RedisValue member = "value"; + if (keyExists) db.SetAdd(key2, member, flags: CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + Assert.Equal(keyExists, db.SetContains(key2, member)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(demandKeyExists ? Condition.SetContains(key2, member) : Condition.SetNotContains(key2, member)); + var incr = tran.StringIncrementAsync(key); + var exec = tran.ExecuteAsync(); + var get = db.StringGet(key); + + Assert.Equal(expectTranResult, await exec); + if (demandKeyExists == keyExists) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.Equal(1, await incr); // eq: incr + Assert.Equal(1, (long)get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(incr)); // neq: incr + Assert.Equal(0, (long)get); // neq: get + } + } + + [Theory] + [InlineData("five", ComparisonType.Equal, 5L, false)] + [InlineData("four", ComparisonType.Equal, 4L, true)] + [InlineData("three", ComparisonType.Equal, 3L, false)] + [InlineData("", ComparisonType.Equal, 2L, false)] + [InlineData("", ComparisonType.Equal, 0L, true)] + + [InlineData("five", ComparisonType.LessThan, 5L, true)] + [InlineData("four", ComparisonType.LessThan, 4L, false)] + [InlineData("three", ComparisonType.LessThan, 3L, false)] + [InlineData("", ComparisonType.LessThan, 2L, true)] + [InlineData("", ComparisonType.LessThan, 0L, false)] + + [InlineData("five", ComparisonType.GreaterThan, 5L, false)] + [InlineData("four", ComparisonType.GreaterThan, 4L, false)] + [InlineData("three", ComparisonType.GreaterThan, 3L, true)] + [InlineData("", ComparisonType.GreaterThan, 2L, false)] + [InlineData("", ComparisonType.GreaterThan, 0L, false)] + public async Task BasicTranWithSortedSetCardinalityCondition(string value, ComparisonType type, long length, bool expectTranResult) + { + await using var conn = Create(); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + bool expectSuccess; + Condition? condition; + var valueLength = value?.Length ?? 0; + switch (type) + { + case ComparisonType.Equal: + expectSuccess = valueLength == length; + condition = Condition.SortedSetLengthEqual(key2, length); + break; + case ComparisonType.GreaterThan: + expectSuccess = valueLength > length; + condition = Condition.SortedSetLengthGreaterThan(key2, length); + break; + case ComparisonType.LessThan: + expectSuccess = valueLength < length; + condition = Condition.SortedSetLengthLessThan(key2, length); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + + for (var i = 0; i < valueLength; i++) + { + db.SortedSetAdd(key2, i, i, flags: CommandFlags.FireAndForget); + } + Assert.False(db.KeyExists(key)); + Assert.Equal(valueLength, db.SortedSetLength(key2)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(condition); + var push = tran.StringSetAsync(key, "any value"); + var exec = tran.ExecuteAsync(); + var get = db.StringLength(key); + + Assert.Equal(expectTranResult, await exec); + + if (expectSuccess) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.True(await push); // eq: push + Assert.Equal("any value".Length, get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(push)); // neq: push + Assert.Equal(0, get); // neq: get + } + } + + [Theory] + [InlineData(1, 4, ComparisonType.Equal, 5L, false)] + [InlineData(1, 4, ComparisonType.Equal, 4L, true)] + [InlineData(1, 2, ComparisonType.Equal, 3L, false)] + [InlineData(1, 1, ComparisonType.Equal, 2L, false)] + [InlineData(0, 0, ComparisonType.Equal, 0L, false)] + + [InlineData(1, 4, ComparisonType.LessThan, 5L, true)] + [InlineData(1, 4, ComparisonType.LessThan, 4L, false)] + [InlineData(1, 3, ComparisonType.LessThan, 3L, false)] + [InlineData(1, 1, ComparisonType.LessThan, 2L, true)] + [InlineData(0, 0, ComparisonType.LessThan, 0L, false)] + + [InlineData(1, 5, ComparisonType.GreaterThan, 5L, false)] + [InlineData(1, 4, ComparisonType.GreaterThan, 4L, false)] + [InlineData(1, 4, ComparisonType.GreaterThan, 3L, true)] + [InlineData(1, 2, ComparisonType.GreaterThan, 2L, false)] + [InlineData(0, 0, ComparisonType.GreaterThan, 0L, true)] + public async Task BasicTranWithSortedSetRangeCountCondition(double min, double max, ComparisonType type, long length, bool expectTranResult) + { + await using var conn = Create(); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + bool expectSuccess; + Condition? condition; + var valueLength = (int)(max - min) + 1; + switch (type) + { + case ComparisonType.Equal: + expectSuccess = valueLength == length; + condition = Condition.SortedSetLengthEqual(key2, length, min, max); + break; + case ComparisonType.GreaterThan: + expectSuccess = valueLength > length; + condition = Condition.SortedSetLengthGreaterThan(key2, length, min, max); + break; + case ComparisonType.LessThan: + expectSuccess = valueLength < length; + condition = Condition.SortedSetLengthLessThan(key2, length, min, max); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + + for (var i = 0; i < 5; i++) + { + db.SortedSetAdd(key2, i, i, flags: CommandFlags.FireAndForget); + } + Assert.False(db.KeyExists(key)); + Assert.Equal(5, db.SortedSetLength(key2)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(condition); + var push = tran.StringSetAsync(key, "any value"); + var exec = tran.ExecuteAsync(); + var get = db.StringLength(key); + + Assert.Equal(expectTranResult, await exec); + + if (expectSuccess) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.True(await push); // eq: push + Assert.Equal("any value".Length, get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(push)); // neq: push + Assert.Equal(0, get); // neq: get + } + } + + [Theory] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(true, false, false)] + [InlineData(true, true, true)] + public async Task BasicTranWithSortedSetContainsCondition(bool demandKeyExists, bool keyExists, bool expectTranResult) + { + await using var conn = Create(disabledCommands: ["info", "config"]); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + RedisValue member = "value"; + if (keyExists) db.SortedSetAdd(key2, member, 0.0, flags: CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + Assert.Equal(keyExists, db.SortedSetScore(key2, member).HasValue); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(demandKeyExists ? Condition.SortedSetContains(key2, member) : Condition.SortedSetNotContains(key2, member)); + var incr = tran.StringIncrementAsync(key); + var exec = tran.ExecuteAsync(); + var get = db.StringGet(key); + + Assert.Equal(expectTranResult, await exec); + if (demandKeyExists == keyExists) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.Equal(1, await incr); // eq: incr + Assert.Equal(1, (long)get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(incr)); // neq: incr + Assert.Equal(0, (long)get); // neq: get + } + } + + public enum SortedSetValue + { + None, + Exact, + Shorter, + Longer, + } + + [Theory] + [InlineData(false, SortedSetValue.None, true)] + [InlineData(false, SortedSetValue.Shorter, true)] + [InlineData(false, SortedSetValue.Exact, false)] + [InlineData(false, SortedSetValue.Longer, false)] + [InlineData(true, SortedSetValue.None, false)] + [InlineData(true, SortedSetValue.Shorter, false)] + [InlineData(true, SortedSetValue.Exact, true)] + [InlineData(true, SortedSetValue.Longer, true)] + public async Task BasicTranWithSortedSetStartsWithCondition_String(bool requestExists, SortedSetValue existingValue, bool expectTranResult) + { + using var conn = Create(); + + RedisKey key1 = Me() + "_1", key2 = Me() + "_2"; + var db = conn.GetDatabase(); + db.KeyDelete(key1, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + db.SortedSetAdd(key2, "unrelated", 0.0, flags: CommandFlags.FireAndForget); + switch (existingValue) + { + case SortedSetValue.Shorter: + db.SortedSetAdd(key2, "see", 0.0, flags: CommandFlags.FireAndForget); + break; + case SortedSetValue.Exact: + db.SortedSetAdd(key2, "seek", 0.0, flags: CommandFlags.FireAndForget); + break; + case SortedSetValue.Longer: + db.SortedSetAdd(key2, "seeks", 0.0, flags: CommandFlags.FireAndForget); + break; + } + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(requestExists ? Condition.SortedSetContainsStarting(key2, "seek") : Condition.SortedSetNotContainsStarting(key2, "seek")); + var incr = tran.StringIncrementAsync(key1); + var exec = await tran.ExecuteAsync(); + var get = await db.StringGetAsync(key1); + + Assert.Equal(expectTranResult, exec); + Assert.Equal(expectTranResult, cond.WasSatisfied); + + if (expectTranResult) + { + Assert.Equal(1, await incr); // eq: incr + Assert.Equal(1, (long)get); // eq: get + } + else + { + Assert.Equal(TaskStatus.Canceled, SafeStatus(incr)); // neq: incr + Assert.Equal(0, (long)get); // neq: get + } + } + + [Theory] + [InlineData(false, SortedSetValue.None, true)] + [InlineData(false, SortedSetValue.Shorter, true)] + [InlineData(false, SortedSetValue.Exact, false)] + [InlineData(false, SortedSetValue.Longer, false)] + [InlineData(true, SortedSetValue.None, false)] + [InlineData(true, SortedSetValue.Shorter, false)] + [InlineData(true, SortedSetValue.Exact, true)] + [InlineData(true, SortedSetValue.Longer, true)] + public async Task BasicTranWithSortedSetStartsWithCondition_Integer(bool requestExists, SortedSetValue existingValue, bool expectTranResult) + { + using var conn = Create(); + + RedisKey key1 = Me() + "_1", key2 = Me() + "_2"; + var db = conn.GetDatabase(); + db.KeyDelete(key1, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + db.SortedSetAdd(key2, 789, 0.0, flags: CommandFlags.FireAndForget); + switch (existingValue) + { + case SortedSetValue.Shorter: + db.SortedSetAdd(key2, 123, 0.0, flags: CommandFlags.FireAndForget); + break; + case SortedSetValue.Exact: + db.SortedSetAdd(key2, 1234, 0.0, flags: CommandFlags.FireAndForget); + break; + case SortedSetValue.Longer: + db.SortedSetAdd(key2, 12345, 0.0, flags: CommandFlags.FireAndForget); + break; + } + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(requestExists ? Condition.SortedSetContainsStarting(key2, 1234) : Condition.SortedSetNotContainsStarting(key2, 1234)); + var incr = tran.StringIncrementAsync(key1); + var exec = await tran.ExecuteAsync(); + var get = await db.StringGetAsync(key1); + + Assert.Equal(expectTranResult, exec); + Assert.Equal(expectTranResult, cond.WasSatisfied); + + if (expectTranResult) + { + Assert.Equal(1, await incr); // eq: incr + Assert.Equal(1, (long)get); // eq: get + } + else + { + Assert.Equal(TaskStatus.Canceled, SafeStatus(incr)); // neq: incr + Assert.Equal(0, (long)get); // neq: get + } + } + + [Theory] + [InlineData(4D, 4D, true, true)] + [InlineData(4D, 5D, true, false)] + [InlineData(4D, null, true, false)] + [InlineData(null, 5D, true, false)] + [InlineData(null, null, true, true)] + + [InlineData(4D, 4D, false, false)] + [InlineData(4D, 5D, false, true)] + [InlineData(4D, null, false, true)] + [InlineData(null, 5D, false, true)] + [InlineData(null, null, false, false)] + public async Task BasicTranWithSortedSetEqualCondition(double? expected, double? value, bool expectEqual, bool expectedTranResult) + { + await using var conn = Create(); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + RedisValue member = "member"; + if (value != null) db.SortedSetAdd(key2, member, value.Value, flags: CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + Assert.Equal(value, db.SortedSetScore(key2, member)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(expectEqual ? Condition.SortedSetEqual(key2, member, expected) : Condition.SortedSetNotEqual(key2, member, expected)); + var incr = tran.StringIncrementAsync(key); + var exec = tran.ExecuteAsync(); + var get = db.StringGet(key); + + Assert.Equal(expectedTranResult, await exec); + if (expectEqual == (value == expected)) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.Equal(1, await incr); // eq: incr + Assert.Equal(1, (long)get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(incr)); // neq: incr + Assert.Equal(0, (long)get); // neq: get + } + } + + [Theory] + [InlineData(true, true, true, true)] + [InlineData(true, false, true, true)] + [InlineData(false, true, true, true)] + [InlineData(true, true, false, false)] + [InlineData(true, false, false, false)] + [InlineData(false, true, false, false)] + [InlineData(false, false, true, false)] + [InlineData(false, false, false, true)] + public async Task BasicTranWithSortedSetScoreExistsCondition(bool member1HasScore, bool member2HasScore, bool demandScoreExists, bool expectedTranResult) + { + await using var conn = Create(); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + const double Score = 4D; + RedisValue member1 = "member1"; + RedisValue member2 = "member2"; + if (member1HasScore) + { + db.SortedSetAdd(key2, member1, Score, flags: CommandFlags.FireAndForget); + } + + if (member2HasScore) + { + db.SortedSetAdd(key2, member2, Score, flags: CommandFlags.FireAndForget); + } + + Assert.False(db.KeyExists(key)); + Assert.Equal(member1HasScore ? Score : null, db.SortedSetScore(key2, member1)); + Assert.Equal(member2HasScore ? Score : null, db.SortedSetScore(key2, member2)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(demandScoreExists ? Condition.SortedSetScoreExists(key2, Score) : Condition.SortedSetScoreNotExists(key2, Score)); + var incr = tran.StringIncrementAsync(key); + var exec = tran.ExecuteAsync(); + var get = db.StringGet(key); + + Assert.Equal(expectedTranResult, await exec); + if ((member1HasScore || member2HasScore) == demandScoreExists) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.Equal(1, await incr); // eq: incr + Assert.Equal(1, (long)get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(incr)); // neq: incr + Assert.Equal(0, (long)get); // neq: get + } + } + + [Theory] + [InlineData(true, true, 2L, true, true)] + [InlineData(true, true, 2L, false, false)] + [InlineData(true, true, 1L, true, false)] + [InlineData(true, true, 1L, false, true)] + [InlineData(true, false, 2L, true, false)] + [InlineData(true, false, 2L, false, true)] + [InlineData(true, false, 1L, true, true)] + [InlineData(true, false, 1L, false, false)] + [InlineData(false, true, 2L, true, false)] + [InlineData(false, true, 2L, false, true)] + [InlineData(false, true, 1L, true, true)] + [InlineData(false, true, 1L, false, false)] + [InlineData(false, false, 2L, true, false)] + [InlineData(false, false, 2L, false, true)] + [InlineData(false, false, 1L, true, false)] + [InlineData(false, false, 1L, false, true)] + public async Task BasicTranWithSortedSetScoreCountExistsCondition(bool member1HasScore, bool member2HasScore, long expectedLength, bool expectEqual, bool expectedTranResult) + { + await using var conn = Create(); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + const double Score = 4D; + var length = 0L; + RedisValue member1 = "member1"; + RedisValue member2 = "member2"; + if (member1HasScore) + { + db.SortedSetAdd(key2, member1, Score, flags: CommandFlags.FireAndForget); + length++; + } + + if (member2HasScore) + { + db.SortedSetAdd(key2, member2, Score, flags: CommandFlags.FireAndForget); + length++; + } + + Assert.False(db.KeyExists(key)); + Assert.Equal(length, db.SortedSetLength(key2, Score, Score)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(expectEqual ? Condition.SortedSetScoreExists(key2, Score, expectedLength) : Condition.SortedSetScoreNotExists(key2, Score, expectedLength)); + var incr = tran.StringIncrementAsync(key); + var exec = tran.ExecuteAsync(); + var get = db.StringGet(key); + + Assert.Equal(expectedTranResult, await exec); + if (expectEqual == (length == expectedLength)) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.Equal(1, await incr); // eq: incr + Assert.Equal(1, (long)get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(incr)); // neq: incr + Assert.Equal(0, (long)get); // neq: get + } + } + + [Theory] + [InlineData("five", ComparisonType.Equal, 5L, false)] + [InlineData("four", ComparisonType.Equal, 4L, true)] + [InlineData("three", ComparisonType.Equal, 3L, false)] + [InlineData("", ComparisonType.Equal, 2L, false)] + [InlineData("", ComparisonType.Equal, 0L, true)] + + [InlineData("five", ComparisonType.LessThan, 5L, true)] + [InlineData("four", ComparisonType.LessThan, 4L, false)] + [InlineData("three", ComparisonType.LessThan, 3L, false)] + [InlineData("", ComparisonType.LessThan, 2L, true)] + [InlineData("", ComparisonType.LessThan, 0L, false)] + + [InlineData("five", ComparisonType.GreaterThan, 5L, false)] + [InlineData("four", ComparisonType.GreaterThan, 4L, false)] + [InlineData("three", ComparisonType.GreaterThan, 3L, true)] + [InlineData("", ComparisonType.GreaterThan, 2L, false)] + [InlineData("", ComparisonType.GreaterThan, 0L, false)] + public async Task BasicTranWithListLengthCondition(string value, ComparisonType type, long length, bool expectTranResult) + { + await using var conn = Create(); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + bool expectSuccess; + Condition? condition; + var valueLength = value?.Length ?? 0; + switch (type) + { + case ComparisonType.Equal: + expectSuccess = valueLength == length; + condition = Condition.ListLengthEqual(key2, length); + break; + case ComparisonType.GreaterThan: + expectSuccess = valueLength > length; + condition = Condition.ListLengthGreaterThan(key2, length); + break; + case ComparisonType.LessThan: + expectSuccess = valueLength < length; + condition = Condition.ListLengthLessThan(key2, length); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + + for (var i = 0; i < valueLength; i++) + { + db.ListRightPush(key2, i, flags: CommandFlags.FireAndForget); + } + Assert.False(db.KeyExists(key)); + Assert.Equal(valueLength, db.ListLength(key2)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(condition); + var push = tran.StringSetAsync(key, "any value"); + var exec = tran.ExecuteAsync(); + var get = db.StringLength(key); + + Assert.Equal(expectTranResult, await exec); + + if (expectSuccess) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.True(await push); // eq: push + Assert.Equal("any value".Length, get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(push)); // neq: push + Assert.Equal(0, get); // neq: get + } + } + + [Theory] + [InlineData("five", ComparisonType.Equal, 5L, false)] + [InlineData("four", ComparisonType.Equal, 4L, true)] + [InlineData("three", ComparisonType.Equal, 3L, false)] + [InlineData("", ComparisonType.Equal, 2L, false)] + [InlineData("", ComparisonType.Equal, 0L, true)] + + [InlineData("five", ComparisonType.LessThan, 5L, true)] + [InlineData("four", ComparisonType.LessThan, 4L, false)] + [InlineData("three", ComparisonType.LessThan, 3L, false)] + [InlineData("", ComparisonType.LessThan, 2L, true)] + [InlineData("", ComparisonType.LessThan, 0L, false)] + + [InlineData("five", ComparisonType.GreaterThan, 5L, false)] + [InlineData("four", ComparisonType.GreaterThan, 4L, false)] + [InlineData("three", ComparisonType.GreaterThan, 3L, true)] + [InlineData("", ComparisonType.GreaterThan, 2L, false)] + [InlineData("", ComparisonType.GreaterThan, 0L, false)] + public async Task BasicTranWithStreamLengthCondition(string value, ComparisonType type, long length, bool expectTranResult) + { + await using var conn = Create(require: RedisFeatures.v5_0_0); + + RedisKey key = Me(), key2 = Me() + "2"; + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + db.KeyDelete(key2, CommandFlags.FireAndForget); + + bool expectSuccess; + Condition? condition; + var valueLength = value?.Length ?? 0; + switch (type) + { + case ComparisonType.Equal: + expectSuccess = valueLength == length; + condition = Condition.StreamLengthEqual(key2, length); + break; + case ComparisonType.GreaterThan: + expectSuccess = valueLength > length; + condition = Condition.StreamLengthGreaterThan(key2, length); + break; + case ComparisonType.LessThan: + expectSuccess = valueLength < length; + condition = Condition.StreamLengthLessThan(key2, length); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + RedisValue fieldName = "Test"; + for (var i = 0; i < valueLength; i++) + { + db.StreamAdd(key2, fieldName, i, flags: CommandFlags.FireAndForget); + } + Assert.False(db.KeyExists(key)); + Assert.Equal(valueLength, db.StreamLength(key2)); + + var tran = db.CreateTransaction(); + var cond = tran.AddCondition(condition); + var push = tran.StringSetAsync(key, "any value"); + var exec = tran.ExecuteAsync(); + var get = db.StringLength(key); + + Assert.Equal(expectTranResult, await exec); + + if (expectSuccess) + { + Assert.True(await exec, "eq: exec"); + Assert.True(cond.WasSatisfied, "eq: was satisfied"); + Assert.True(await push); // eq: push + Assert.Equal("any value".Length, get); // eq: get + } + else + { + Assert.False(await exec, "neq: exec"); + Assert.False(cond.WasSatisfied, "neq: was satisfied"); + Assert.Equal(TaskStatus.Canceled, SafeStatus(push)); // neq: push + Assert.Equal(0, get); // neq: get + } + } + + [Fact] + public async Task BasicTran() + { + await using var conn = Create(); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + + var tran = db.CreateTransaction(); + var a = tran.StringIncrementAsync(key, 10); + var b = tran.StringIncrementAsync(key, 5); + var c = tran.StringGetAsync(key); + var d = tran.KeyExistsAsync(key); + var e = tran.KeyDeleteAsync(key); + var f = tran.KeyExistsAsync(key); + Assert.False(a.IsCompleted); + Assert.False(b.IsCompleted); + Assert.False(c.IsCompleted); + Assert.False(d.IsCompleted); + Assert.False(e.IsCompleted); + Assert.False(f.IsCompleted); + var result = await tran.ExecuteAsync().ForAwait(); + Assert.True(result, "result"); + await Task.WhenAll(a, b, c, d, e, f).ForAwait(); + Assert.True(a.IsCompleted, "a"); + Assert.True(b.IsCompleted, "b"); + Assert.True(c.IsCompleted, "c"); + Assert.True(d.IsCompleted, "d"); + Assert.True(e.IsCompleted, "e"); + Assert.True(f.IsCompleted, "f"); + + var g = db.KeyExists(key); + + Assert.Equal(10, await a.ForAwait()); + Assert.Equal(15, await b.ForAwait()); + Assert.Equal(15, (long)await c.ForAwait()); + Assert.True(await d.ForAwait()); + Assert.True(await e.ForAwait()); + Assert.False(await f.ForAwait()); + Assert.False(g); + } + + [Fact] + public async Task CombineFireAndForgetAndRegularAsyncInTransaction() + { + await using var conn = Create(); + + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + + var tran = db.CreateTransaction("state"); + var a = tran.StringIncrementAsync(key, 5); + var b = tran.StringIncrementAsync(key, 10, CommandFlags.FireAndForget); + var c = tran.StringIncrementAsync(key, 15); + Assert.True(tran.Execute()); + var count = (long)db.StringGet(key); + + Assert.Equal(5, await a); + Assert.Equal("state", a.AsyncState); + Assert.Equal(0, await b); + Assert.Null(b.AsyncState); + Assert.Equal(30, await c); + Assert.Equal("state", a.AsyncState); + Assert.Equal(30, count); + } + + [Fact] + public async Task TransactionWithAdHocCommandsAndSelectDisabled() + { + await using var conn = Create(disabledCommands: ["SELECT"]); + RedisKey key = Me(); + var db = conn.GetDatabase(); + db.KeyDelete(key, CommandFlags.FireAndForget); + Assert.False(db.KeyExists(key)); + + var tran = db.CreateTransaction("state"); + var a = tran.ExecuteAsync("SET", "foo", "bar"); + Assert.True(await tran.ExecuteAsync()); + await a; + var setting = db.StringGet("foo"); + Assert.Equal("bar", setting); + } + +#if VERBOSE + [Fact] + public async Task WatchAbort_StringEqual() + { + await using var vicConn = Create(); + await using var perpConn = Create(); + + var key = Me(); + var db = vicConn.GetDatabase(); + + // expect foo, change to bar at the last minute + vicConn.PreTransactionExec += cmd => + { + Writer.WriteLine($"'{cmd}' detected; changing it..."); + perpConn.GetDatabase().StringSet(key, "bar"); + }; + db.KeyDelete(key); + db.StringSet(key, "foo"); + var tran = db.CreateTransaction(); + tran.AddCondition(Condition.StringEqual(key, "foo")); + var pong = tran.PingAsync(); + Assert.False(await tran.ExecuteAsync(), "expected abort"); + await Assert.ThrowsAsync(() => pong); + } + + [Fact] + public async Task WatchAbort_HashLengthEqual() + { + await using var vicConn = Create(); + await using var perpConn = Create(); + + var key = Me(); + var db = vicConn.GetDatabase(); + + // expect foo, change to bar at the last minute + vicConn.PreTransactionExec += cmd => + { + Writer.WriteLine($"'{cmd}' detected; changing it..."); + perpConn.GetDatabase().HashSet(key, "bar", "def"); + }; + db.KeyDelete(key); + db.HashSet(key, "foo", "abc"); + var tran = db.CreateTransaction(); + tran.AddCondition(Condition.HashLengthEqual(key, 1)); + var pong = tran.PingAsync(); + Assert.False(await tran.ExecuteAsync()); + await Assert.ThrowsAsync(() => pong); + } +#endif + + [Fact] + public async Task ExecCompletes_Issue943() + { + Skip.UnlessLongRunning(); + int hashHit = 0, hashMiss = 0, expireHit = 0, expireMiss = 0; + await using (var conn = Create()) + { + var db = conn.GetDatabase(); + for (int i = 0; i < 40000; i++) + { + RedisKey key = Me(); + await db.KeyDeleteAsync(key); + HashEntry[] hashEntries = + [ + new HashEntry("blah", DateTime.UtcNow.ToString("R")), + ]; + ITransaction transaction = db.CreateTransaction(); + transaction.AddCondition(Condition.KeyNotExists(key)); + Task hashSetTask = transaction.HashSetAsync(key, hashEntries); + Task expireTask = transaction.KeyExpireAsync(key, TimeSpan.FromSeconds(30)); + bool committed = await transaction.ExecuteAsync(); + if (committed) + { + if (hashSetTask.IsCompleted) hashHit++; else hashMiss++; + if (expireTask.IsCompleted) expireHit++; else expireMiss++; + await hashSetTask; + await expireTask; + } + } + } + + Log($"hash hit: {hashHit}, miss: {hashMiss}; expire hit: {expireHit}, miss: {expireMiss}"); + Assert.Equal(0, hashMiss); + Assert.Equal(0, expireMiss); + } +} diff --git a/tests/StackExchange.Redis.Tests/ValueTests.cs b/tests/StackExchange.Redis.Tests/ValueTests.cs new file mode 100644 index 000000000..69a8a2cbc --- /dev/null +++ b/tests/StackExchange.Redis.Tests/ValueTests.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using System.Text; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class ValueTests(ITestOutputHelper output) : TestBase(output) +{ + [Fact] + public void NullValueChecks() + { + RedisValue four = 4; + Assert.False(four.IsNull); + Assert.True(four.IsInteger); + Assert.True(four.HasValue); + Assert.False(four.IsNullOrEmpty); + + RedisValue n = default; + Assert.True(n.IsNull); + Assert.False(n.IsInteger); + Assert.False(n.HasValue); + Assert.True(n.IsNullOrEmpty); + + RedisValue emptyArr = Array.Empty(); + Assert.False(emptyArr.IsNull); + Assert.False(emptyArr.IsInteger); + Assert.False(emptyArr.HasValue); + Assert.True(emptyArr.IsNullOrEmpty); + } + + [Fact] + public void FromStream() + { + var arr = Encoding.UTF8.GetBytes("hello world"); + var ms = new MemoryStream(arr); + var val = RedisValue.CreateFrom(ms); + Assert.Equal("hello world", val); + + ms = new MemoryStream(arr, 1, 6, false, false); + val = RedisValue.CreateFrom(ms); + Assert.Equal("ello w", val); + + ms = new MemoryStream(arr, 2, 6, false, true); + val = RedisValue.CreateFrom(ms); + Assert.Equal("llo wo", val); + } +} diff --git a/tests/StackExchange.Redis.Tests/VectorSetIntegrationTests.cs b/tests/StackExchange.Redis.Tests/VectorSetIntegrationTests.cs new file mode 100644 index 000000000..12eda7147 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/VectorSetIntegrationTests.cs @@ -0,0 +1,675 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + +namespace StackExchange.Redis.Tests; + +[RunPerProtocol] +public sealed class VectorSetIntegrationTests(ITestOutputHelper output) : TestBase(output) +{ + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task VectorSetAdd_BasicOperation(bool suppressFp32) + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + // Clean up any existing data + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var vector = new[] { 1.0f, 2.0f, 3.0f, 4.0f }; + + if (suppressFp32) VectorSetAddMessage.SuppressFp32(); + try + { + var request = VectorSetAddRequest.Member("element1", vector.AsMemory(), null); + var result = await db.VectorSetAddAsync(key, request); + + Assert.True(result); + } + finally + { + if (suppressFp32) VectorSetAddMessage.RestoreFp32(); + } + } + + [Fact] + public async Task VectorSetAdd_WithAttributes() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var vector = new[] { 1.0f, 2.0f, 3.0f, 4.0f }; + var attributes = """{"category":"test","id":123}"""; + + var request = VectorSetAddRequest.Member("element1", vector.AsMemory(), attributes); + var result = await db.VectorSetAddAsync(key, request); + + Assert.True(result); + + // Verify attributes were stored + var retrievedAttributes = await db.VectorSetGetAttributesJsonAsync(key, "element1"); + Assert.Equal(attributes, retrievedAttributes); + } + + [Theory] + [InlineData(VectorSetQuantization.Int8)] + [InlineData(VectorSetQuantization.None)] + [InlineData(VectorSetQuantization.Binary)] + public async Task VectorSetAdd_WithEverything(VectorSetQuantization quantization) + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var vector = new[] { 1.0f, 2.0f, 3.0f, 4.0f }; + var attributes = """{"category":"test","id":123}"""; + + var request = VectorSetAddRequest.Member( + "element1", + vector.AsMemory(), + attributes); + request.Quantization = quantization; + request.ReducedDimensions = 64; + request.BuildExplorationFactor = 300; + request.MaxConnections = 32; + request.UseCheckAndSet = true; + var result = await db.VectorSetAddAsync( + key, + request); + + Assert.True(result); + + // Verify attributes were stored + var retrievedAttributes = await db.VectorSetGetAttributesJsonAsync(key, "element1"); + Assert.Equal(attributes, retrievedAttributes); + } + + [Fact] + public async Task VectorSetLength_EmptySet() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var length = await db.VectorSetLengthAsync(key); + Assert.Equal(0, length); + } + + [Fact] + public async Task VectorSetLength_WithElements() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var vector1 = new[] { 1.0f, 2.0f, 3.0f }; + var vector2 = new[] { 4.0f, 5.0f, 6.0f }; + + var request = VectorSetAddRequest.Member("element1", vector1.AsMemory()); + await db.VectorSetAddAsync(key, request); + request = VectorSetAddRequest.Member("element2", vector2.AsMemory()); + await db.VectorSetAddAsync(key, request); + + var length = await db.VectorSetLengthAsync(key); + Assert.Equal(2, length); + } + + [Fact] + public async Task VectorSetDimension() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var vector = new[] { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + var request = VectorSetAddRequest.Member("element1", vector.AsMemory()); + await db.VectorSetAddAsync(key, request); + + var dimension = await db.VectorSetDimensionAsync(key); + Assert.Equal(5, dimension); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task VectorSetContains(bool suppressFp32) + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var vector = new[] { 1.0f, 2.0f, 3.0f }; + if (suppressFp32) VectorSetAddMessage.SuppressFp32(); + try + { + var request = VectorSetAddRequest.Member("element1", vector.AsMemory()); + await db.VectorSetAddAsync(key, request); + + var exists = await db.VectorSetContainsAsync(key, "element1"); + var notExists = await db.VectorSetContainsAsync(key, "element2"); + + Assert.True(exists); + Assert.False(notExists); + } + finally + { + if (suppressFp32) VectorSetAddMessage.RestoreFp32(); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task VectorSetGetApproximateVector(bool suppressFp32) + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var originalVector = new[] { 1.0f, 2.0f, 3.0f, 4.0f }; + if (suppressFp32) VectorSetAddMessage.SuppressFp32(); + try + { + var request = VectorSetAddRequest.Member("element1", originalVector.AsMemory()); + await db.VectorSetAddAsync(key, request); + + using var retrievedLease = await db.VectorSetGetApproximateVectorAsync(key, "element1"); + + Assert.NotNull(retrievedLease); + var retrievedVector = retrievedLease.Span; + + Assert.Equal(originalVector.Length, retrievedVector.Length); + // Note: Due to quantization, values might not be exactly equal + for (int i = 0; i < originalVector.Length; i++) + { + Assert.True( + Math.Abs(originalVector[i] - retrievedVector[i]) < 0.1f, + $"Vector component {i} differs too much: expected {originalVector[i]}, got {retrievedVector[i]}"); + } + } + finally + { + if (suppressFp32) VectorSetAddMessage.RestoreFp32(); + } + } + + [Fact] + public async Task VectorSetRemove() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var vector = new[] { 1.0f, 2.0f, 3.0f }; + var request = VectorSetAddRequest.Member("element1", vector.AsMemory()); + await db.VectorSetAddAsync(key, request); + + var removed = await db.VectorSetRemoveAsync(key, "element1"); + Assert.True(removed); + + removed = await db.VectorSetRemoveAsync(key, "element1"); + Assert.False(removed); + + var exists = await db.VectorSetContainsAsync(key, "element1"); + Assert.False(exists); + } + + [Theory] + [InlineData(VectorSetQuantization.Int8)] + [InlineData(VectorSetQuantization.Binary)] + [InlineData(VectorSetQuantization.None)] + public async Task VectorSetInfo(VectorSetQuantization quantization) + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var vector = new[] { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + var request = VectorSetAddRequest.Member("element1", vector.AsMemory()); + request.Quantization = quantization; + await db.VectorSetAddAsync(key, request); + + var info = await db.VectorSetInfoAsync(key); + + Assert.NotNull(info); + var v = info.GetValueOrDefault(); + Assert.Equal(5, v.Dimension); + Assert.Equal(1, v.Length); + Assert.Equal(quantization, v.Quantization); + Assert.Null(v.QuantizationRaw); // Should be null for known quant types + + Assert.NotEqual(0, v.VectorSetUid); + Assert.NotEqual(0, v.HnswMaxNodeUid); + } + + [Fact] + public async Task VectorSetRandomMember() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var vector1 = new[] { 1.0f, 2.0f, 3.0f }; + var vector2 = new[] { 4.0f, 5.0f, 6.0f }; + + var request = VectorSetAddRequest.Member("element1", vector1.AsMemory()); + await db.VectorSetAddAsync(key, request); + request = VectorSetAddRequest.Member("element2", vector2.AsMemory()); + await db.VectorSetAddAsync(key, request); + + var randomMember = await db.VectorSetRandomMemberAsync(key); + Assert.True(randomMember == "element1" || randomMember == "element2"); + } + + [Fact] + public async Task VectorSetRandomMembers() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var vector1 = new[] { 1.0f, 2.0f, 3.0f }; + var vector2 = new[] { 4.0f, 5.0f, 6.0f }; + var vector3 = new[] { 7.0f, 8.0f, 9.0f }; + + var request = VectorSetAddRequest.Member("element1", vector1.AsMemory()); + await db.VectorSetAddAsync(key, request); + request = VectorSetAddRequest.Member("element2", vector2.AsMemory()); + await db.VectorSetAddAsync(key, request); + request = VectorSetAddRequest.Member("element3", vector3.AsMemory()); + await db.VectorSetAddAsync(key, request); + + var randomMembers = await db.VectorSetRandomMembersAsync(key, 2); + + Assert.Equal(2, randomMembers.Length); + Assert.All(randomMembers, member => + Assert.True(member == "element1" || member == "element2" || member == "element3")); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public async Task VectorSetSimilaritySearch_ByVector(bool withScores, bool withAttributes) + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var disambiguator = (withScores ? 1 : 0) + (withAttributes ? 2 : 0); + var key = Me() + disambiguator; + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + // Add some test vectors + var vector1 = new[] { 1.0f, 0.0f, 0.0f }; + var vector2 = new[] { 0.0f, 1.0f, 0.0f }; + var vector3 = new[] { 0.9f, 0.1f, 0.0f }; // Similar to vector1 + + var request = + VectorSetAddRequest.Member("element1", vector1.AsMemory(), attributesJson: """{"category":"x"}"""); + await db.VectorSetAddAsync(key, request); + request = VectorSetAddRequest.Member("element2", vector2.AsMemory(), attributesJson: """{"category":"y"}"""); + await db.VectorSetAddAsync(key, request); + request = VectorSetAddRequest.Member("element3", vector3.AsMemory(), attributesJson: """{"category":"z"}"""); + await db.VectorSetAddAsync(key, request); + + // Search for vectors similar to vector1 + var query = VectorSetSimilaritySearchRequest.ByVector(vector1.AsMemory()); + query.Count = 2; + query.WithScores = withScores; + query.WithAttributes = withAttributes; + using var results = await db.VectorSetSimilaritySearchAsync(key, query); + + Assert.NotNull(results); + foreach (var result in results.Span) + { + Log(result.ToString()); + } + + var resultsArray = results.Span.ToArray(); + + Assert.True(resultsArray.Length <= 2); + Assert.Contains(resultsArray, r => r.Member == "element1"); + var found = resultsArray.First(r => r.Member == "element1"); + + if (withAttributes) + { + Assert.Equal("""{"category":"x"}""", found.AttributesJson); + } + else + { + Assert.Null(found.AttributesJson); + } + + Assert.NotEqual(withScores, double.IsNaN(found.Score)); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public async Task VectorSetSimilaritySearch_ByMember(bool withScores, bool withAttributes) + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var disambiguator = (withScores ? 1 : 0) + (withAttributes ? 2 : 0); + var key = Me() + disambiguator; + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var vector1 = new[] { 1.0f, 0.0f, 0.0f }; + var vector2 = new[] { 0.0f, 1.0f, 0.0f }; + + var request = + VectorSetAddRequest.Member("element1", vector1.AsMemory(), attributesJson: """{"category":"x"}"""); + await db.VectorSetAddAsync(key, request); + request = VectorSetAddRequest.Member("element2", vector2.AsMemory(), attributesJson: """{"category":"y"}"""); + await db.VectorSetAddAsync(key, request); + + var query = VectorSetSimilaritySearchRequest.ByMember("element1"); + query.Count = 1; + query.WithScores = withScores; + query.WithAttributes = withAttributes; + using var results = await db.VectorSetSimilaritySearchAsync(key, query); + + Assert.NotNull(results); + foreach (var result in results.Span) + { + Log(result.ToString()); + } + + var resultsArray = results.Span.ToArray(); + + Assert.Single(resultsArray); + Assert.Equal("element1", resultsArray[0].Member); + if (withAttributes) + { + Assert.Equal("""{"category":"x"}""", resultsArray[0].AttributesJson); + } + else + { + Assert.Null(resultsArray[0].AttributesJson); + } + + Assert.NotEqual(withScores, double.IsNaN(resultsArray[0].Score)); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task VectorSetSimilaritySearch_WithFilter(bool corruptPrefix, bool corruptSuffix) + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + Random rand = new Random(); + + float[] vector = new float[50]; + + void ScrambleVector() + { + var arr = vector; + for (int i = 0; i < arr.Length; i++) + { + arr[i] = (float)rand.NextDouble(); + } + } + + string[] regions = new[] { "us-west", "us-east", "eu-west", "eu-east", "ap-south", "ap-north" }; + for (int i = 0; i < 100; i++) + { + var region = regions[rand.Next(regions.Length)]; + var json = (corruptPrefix ? "oops" : "") + + JsonConvert.SerializeObject(new { id = i, region }) + + (corruptSuffix ? "oops" : ""); + ScrambleVector(); + var request = VectorSetAddRequest.Member($"element{i}", vector.AsMemory(), json); + await db.VectorSetAddAsync(key, request); + } + + ScrambleVector(); + var query = VectorSetSimilaritySearchRequest.ByVector(vector); + query.Count = 100; + query.WithScores = true; + query.WithAttributes = true; + query.FilterExpression = ".id >= 30"; + using var results = await db.VectorSetSimilaritySearchAsync(key, query); + + Assert.NotNull(results); + foreach (var result in results.Span) + { + Log(result.ToString()); + } + + Log($"Total matches: {results.Span.Length}"); + + var resultsArray = results.Span.ToArray(); + if (corruptPrefix) + { + // server short-circuits failure to be no match; we just want to assert + // what the observed behavior *is* + Assert.Empty(resultsArray); + } + else + { + Assert.Equal(70, resultsArray.Length); + Assert.All(resultsArray, r => Assert.True( + r.Score is > 0.0 and < 1.0 && GetId(r.Member!) >= 30)); + } + + static int GetId(string member) + { + if (member.StartsWith("element")) + { + return int.Parse(member.Substring(7), NumberStyles.Integer, CultureInfo.InvariantCulture); + } + + return -1; + } + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData(".id >= 30")] + public async Task VectorSetSimilaritySearch_TestFilterValues(string? filterExpression) + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + Random rand = new Random(); + + float[] vector = new float[50]; + + void ScrambleVector() + { + var arr = vector; + for (int i = 0; i < arr.Length; i++) + { + arr[i] = (float)rand.NextDouble(); + } + } + + string[] regions = new[] { "us-west", "us-east", "eu-west", "eu-east", "ap-south", "ap-north" }; + for (int i = 0; i < 100; i++) + { + var region = regions[rand.Next(regions.Length)]; + var json = JsonConvert.SerializeObject(new { id = i, region }); + ScrambleVector(); + var request = VectorSetAddRequest.Member($"element{i}", vector.AsMemory(), json); + await db.VectorSetAddAsync(key, request); + } + + ScrambleVector(); + var query = VectorSetSimilaritySearchRequest.ByVector(vector); + query.Count = 100; + query.WithScores = true; + query.WithAttributes = true; + query.FilterExpression = filterExpression; + + using var results = await db.VectorSetSimilaritySearchAsync(key, query); + + Assert.NotNull(results); + foreach (var result in results.Span) + { + Log(result.ToString()); + } + + Log($"Total matches: {results.Span.Length}"); + // we're not interested in the specific results; we're just checking that the + // filter expression was added and parsed without exploding about arg mismatch + } + + [Fact] + public async Task VectorSetSetAttributesJson() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + var vector = new[] { 1.0f, 2.0f, 3.0f }; + var request = VectorSetAddRequest.Member("element1", vector.AsMemory()); + await db.VectorSetAddAsync(key, request); + + // Set attributes for existing element + var attributes = """{"category":"updated","priority":"high","timestamp":"2024-01-01"}"""; + var result = await db.VectorSetSetAttributesJsonAsync(key, "element1", attributes); + + Assert.True(result); + + // Verify attributes were set + var retrievedAttributes = await db.VectorSetGetAttributesJsonAsync(key, "element1"); + Assert.Equal(attributes, retrievedAttributes); + + // Try setting attributes for non-existent element + var failResult = await db.VectorSetSetAttributesJsonAsync(key, "nonexistent", attributes); + Assert.False(failResult); + } + + [Fact] + public async Task VectorSetGetLinks() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + // Add some vectors that should be linked + var vector1 = new[] { 1.0f, 0.0f, 0.0f }; + var vector2 = new[] { 0.9f, 0.1f, 0.0f }; // Similar to vector1 + var vector3 = new[] { 0.0f, 1.0f, 0.0f }; // Different from vector1 + + var request = VectorSetAddRequest.Member("element1", vector1.AsMemory()); + await db.VectorSetAddAsync(key, request); + request = VectorSetAddRequest.Member("element2", vector2.AsMemory()); + await db.VectorSetAddAsync(key, request); + request = VectorSetAddRequest.Member("element3", vector3.AsMemory()); + await db.VectorSetAddAsync(key, request); + + // Get links for element1 (should include similar vectors) + using var links = await db.VectorSetGetLinksAsync(key, "element1"); + + Assert.NotNull(links); + foreach (var link in links.Span) + { + Log(link.ToString()); + } + + var linksArray = links.Span.ToArray(); + + // Should contain the other elements (note there can be transient duplicates, so: contains, not exact) + Assert.Contains("element2", linksArray); + Assert.Contains("element3", linksArray); + } + + [Fact] + public async Task VectorSetGetLinksWithScores() + { + await using var conn = Create(require: RedisFeatures.v8_0_0_M04); + var db = conn.GetDatabase(); + var key = Me(); + + await db.KeyDeleteAsync(key, CommandFlags.FireAndForget); + + // Add some vectors with known relationships + var vector1 = new[] { 1.0f, 0.0f, 0.0f }; + var vector2 = new[] { 0.9f, 0.1f, 0.0f }; // Similar to vector1 + var vector3 = new[] { 0.0f, 1.0f, 0.0f }; // Different from vector1 + + var request = VectorSetAddRequest.Member("element1", vector1.AsMemory()); + await db.VectorSetAddAsync(key, request); + request = VectorSetAddRequest.Member("element2", vector2.AsMemory()); + await db.VectorSetAddAsync(key, request); + request = VectorSetAddRequest.Member("element3", vector3.AsMemory()); + await db.VectorSetAddAsync(key, request); + + // Get links with scores for element1 + using var linksWithScores = await db.VectorSetGetLinksWithScoresAsync(key, "element1"); + Assert.NotNull(linksWithScores); + foreach (var link in linksWithScores.Span) + { + Log(link.ToString()); + } + + var linksArray = linksWithScores.Span.ToArray(); + Assert.NotEmpty(linksArray); + + // Verify each link has a valid score + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + Assert.All(linksArray, static link => + { + Assert.False(link.Member.IsNull); + Assert.False(double.IsNaN(link.Score)); + Assert.True(link.Score >= 0.0); // Similarity scores should be non-negative + }); + + // Should contain the other elements (note there can be transient duplicates, so: contains, not exact) + Assert.Contains(linksArray, l => l.Member == "element2"); + Assert.Contains(linksArray, l => l.Member == "element3"); + + Assert.True(linksArray.First(l => l.Member == "element2").Score > 0.9); // similar + Assert.True(linksArray.First(l => l.Member == "element3").Score < 0.8); // less-so + } +} diff --git a/tests/StackExchange.Redis.Tests/WithKeyPrefixTests.cs b/tests/StackExchange.Redis.Tests/WithKeyPrefixTests.cs new file mode 100644 index 000000000..acbef74cf --- /dev/null +++ b/tests/StackExchange.Redis.Tests/WithKeyPrefixTests.cs @@ -0,0 +1,133 @@ +using System; +using System.Threading.Tasks; +using StackExchange.Redis.KeyspaceIsolation; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class WithKeyPrefixTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture) +{ + [Fact] + public async Task BlankPrefixYieldsSame_Bytes() + { + await using var conn = Create(); + + var raw = conn.GetDatabase(); + var prefixed = raw.WithKeyPrefix(Array.Empty()); + Assert.Same(raw, prefixed); + } + + [Fact] + public async Task BlankPrefixYieldsSame_String() + { + await using var conn = Create(); + + var raw = conn.GetDatabase(); + var prefixed = raw.WithKeyPrefix(""); + Assert.Same(raw, prefixed); + } + + [Fact] + public async Task NullPrefixIsError_Bytes() + { + await Assert.ThrowsAsync(async () => + { + await using var conn = Create(); + + var raw = conn.GetDatabase(); + raw.WithKeyPrefix((byte[]?)null); + }); + } + + [Fact] + public async Task NullPrefixIsError_String() + { + await Assert.ThrowsAsync(async () => + { + await using var conn = Create(); + + var raw = conn.GetDatabase(); + raw.WithKeyPrefix((string?)null); + }); + } + + [Theory] + [InlineData("abc")] + [InlineData("")] + [InlineData(null)] + public void NullDatabaseIsError(string? prefix) + { + Assert.Throws(() => + { + IDatabase? raw = null; + raw!.WithKeyPrefix(prefix); + }); + } + + [Fact] + public async Task BasicSmokeTest() + { + await using var conn = Create(); + + var raw = conn.GetDatabase(); + + var prefix = Me(); + var foo = raw.WithKeyPrefix(prefix); + var foobar = foo.WithKeyPrefix("bar"); + + string key = Me(); + + string s = Guid.NewGuid().ToString(), t = Guid.NewGuid().ToString(); + + foo.StringSet(key, s, flags: CommandFlags.FireAndForget); + var val = (string?)foo.StringGet(key); + Assert.Equal(s, val); // fooBasicSmokeTest + + foobar.StringSet(key, t, flags: CommandFlags.FireAndForget); + val = foobar.StringGet(key); + Assert.Equal(t, val); // foobarBasicSmokeTest + + val = foo.StringGet("bar" + key); + Assert.Equal(t, val); // foobarBasicSmokeTest + + val = raw.StringGet(prefix + key); + Assert.Equal(s, val); // fooBasicSmokeTest + + val = raw.StringGet(prefix + "bar" + key); + Assert.Equal(t, val); // foobarBasicSmokeTest + } + + [Fact] + public async Task ConditionTest() + { + await using var conn = Create(); + + var raw = conn.GetDatabase(); + + var prefix = Me() + ":"; + var foo = raw.WithKeyPrefix(prefix); + + raw.KeyDelete(prefix + "abc", CommandFlags.FireAndForget); + raw.KeyDelete(prefix + "i", CommandFlags.FireAndForget); + + // execute while key exists + raw.StringSet(prefix + "abc", "def", flags: CommandFlags.FireAndForget); + var tran = foo.CreateTransaction(); + tran.AddCondition(Condition.KeyExists("abc")); + _ = tran.StringIncrementAsync("i"); + tran.Execute(); + + int i = (int)raw.StringGet(prefix + "i"); + Assert.Equal(1, i); + + // repeat without key + raw.KeyDelete(prefix + "abc", CommandFlags.FireAndForget); + tran = foo.CreateTransaction(); + tran.AddCondition(Condition.KeyExists("abc")); + _ = tran.StringIncrementAsync("i"); + tran.Execute(); + + i = (int)raw.StringGet(prefix + "i"); + Assert.Equal(1, i); + } +} diff --git a/tests/StackExchange.Redis.Tests/redislabs_ca.pem b/tests/StackExchange.Redis.Tests/redislabs_ca.pem new file mode 100644 index 000000000..a4af612d2 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/redislabs_ca.pem @@ -0,0 +1,77 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 11859567854091286320 (0xa495a620ecc0b730) + Signature Algorithm: sha1WithRSAEncryption + Issuer: O=Garantia Data, CN=SSL Certification Authority + Validity + Not Before: Oct 1 12:14:55 2013 GMT + Not After : Sep 29 12:14:55 2023 GMT + Subject: O=Garantia Data, CN=SSL Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b6:6a:92:1f:c3:73:35:8f:26:7c:67:1c:b4:3b: + 40:bd:13:e0:1e:02:0c:a5:81:28:27:22:b2:b8:86: + 6c:0e:99:78:f5:95:36:8e:21:7c:a4:02:e8:9a:f3: + 7d:1f:b4:f3:53:5e:0f:a5:5c:59:48:b3:ae:67:7e: + 8e:d3:e1:21:8e:1c:f9:65:50:62:6e:4f:29:a3:7a: + 0d:3d:62:99:87:71:43:0e:da:a8:ee:63:d8:a5:02: + 12:1f:dc:ce:7a:4b:c5:e4:87:a1:3c:65:47:7e:04: + 43:01:76:f1:69:77:7a:0d:af:73:97:2d:f0:b8:d4: + dd:ea:33:59:59:37:81:be:da:97:1f:66:48:0d:92: + 82:6b:97:e6:51:10:6b:09:7e:fa:b4:a3:b0:14:ad: + 7a:66:36:04:3c:0e:a4:03:17:22:b7:44:c8:ff:dc: + 56:7f:26:92:f8:bf:04:3b:39:33:91:be:d3:d8:f4: + 81:f8:72:0b:34:56:31:0e:c7:9f:bd:6e:d5:ea:25: + 47:1c:15:c6:08:b7:4c:c9:fe:fe:f4:da:15:2a:b1: + 2a:38:1c:93:ac:ee:01:88:c1:44:f6:87:7b:ba:8b: + c4:73:6b:d5:2a:3f:31:cf:67:3f:2f:b7:c0:77:9b: + 17:06:c8:72:75:28:8f:06:e9:e2:77:2d:91:66:e3: + 6f:67 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + FD:70:86:D7:2B:C9:D9:96:DD:92:5E:B9:2A:0A:64:82:A3:CD:ED:F0 + X509v3 Authority Key Identifier: + keyid:FD:70:86:D7:2B:C9:D9:96:DD:92:5E:B9:2A:0A:64:82:A3:CD:ED:F0 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 6d:9e:ad:78:70:44:06:bb:f9:93:81:b3:40:7a:5f:9e:c7:c3: + 27:75:47:89:1f:99:77:2c:d2:bb:5a:95:b3:e9:be:05:0b:4a: + 20:7e:4c:26:df:dc:46:e1:26:71:c6:ca:f7:42:63:5b:6f:95: + f7:cb:8d:d0:3b:1c:9d:0f:08:e9:fe:61:82:c1:03:4a:53:53: + f7:72:be:b3:7a:4a:ef:0d:b9:2e:72:b9:b9:ed:f6:66:f5:de: + 70:c6:62:8d:6b:9e:dd:18:45:fc:4d:fb:c0:cc:dd:f5:c8:56: + bd:37:f0:0d:f4:52:53:d7:d8:eb:b5:13:11:49:4f:43:19:b8: + 52:98:e9:9b:cb:74:8e:bf:d5:c6:e0:9a:0b:8c:94:08:4c:f8: + 38:4a:c9:5e:92:af:9e:bd:f4:b3:37:ce:a7:88:f3:5e:a9:66: + 69:51:10:44:d8:90:6a:fd:d6:ae:e4:06:95:c9:bb:f7:6d:1d: + a1:b1:83:56:46:bb:ac:3f:3c:2b:18:19:47:04:09:61:0d:60: + 3e:15:40:f7:7c:37:7d:89:8c:e7:ee:ea:f1:20:a0:40:30:7c: + f3:fe:de:81:a9:67:89:b7:7b:00:02:71:63:80:7a:7a:9f:95: + bf:9c:41:80:b8:3e:c1:7b:a9:b5:c3:99:16:96:ad:b2:a7:b4: + e9:59:de:7d +-----BEGIN CERTIFICATE----- +MIIDTzCCAjegAwIBAgIJAKSVpiDswLcwMA0GCSqGSIb3DQEBBQUAMD4xFjAUBgNV +BAoMDUdhcmFudGlhIERhdGExJDAiBgNVBAMMG1NTTCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0xMzEwMDExMjE0NTVaFw0yMzA5MjkxMjE0NTVaMD4xFjAUBgNV +BAoMDUdhcmFudGlhIERhdGExJDAiBgNVBAMMG1NTTCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALZqkh/DczWP +JnxnHLQ7QL0T4B4CDKWBKCcisriGbA6ZePWVNo4hfKQC6JrzfR+081NeD6VcWUiz +rmd+jtPhIY4c+WVQYm5PKaN6DT1imYdxQw7aqO5j2KUCEh/cznpLxeSHoTxlR34E +QwF28Wl3eg2vc5ct8LjU3eozWVk3gb7alx9mSA2SgmuX5lEQawl++rSjsBStemY2 +BDwOpAMXIrdEyP/cVn8mkvi/BDs5M5G+09j0gfhyCzRWMQ7Hn71u1eolRxwVxgi3 +TMn+/vTaFSqxKjgck6zuAYjBRPaHe7qLxHNr1So/Mc9nPy+3wHebFwbIcnUojwbp +4nctkWbjb2cCAwEAAaNQME4wHQYDVR0OBBYEFP1whtcrydmW3ZJeuSoKZIKjze3w +MB8GA1UdIwQYMBaAFP1whtcrydmW3ZJeuSoKZIKjze3wMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAG2erXhwRAa7+ZOBs0B6X57Hwyd1R4kfmXcs0rta +lbPpvgULSiB+TCbf3EbhJnHGyvdCY1tvlffLjdA7HJ0PCOn+YYLBA0pTU/dyvrN6 +Su8NuS5yubnt9mb13nDGYo1rnt0YRfxN+8DM3fXIVr038A30UlPX2Ou1ExFJT0MZ +uFKY6ZvLdI6/1cbgmguMlAhM+DhKyV6Sr5699LM3zqeI816pZmlREETYkGr91q7k +BpXJu/dtHaGxg1ZGu6w/PCsYGUcECWENYD4VQPd8N32JjOfu6vEgoEAwfPP+3oGp +Z4m3ewACcWOAenqflb+cQYC4PsF7qbXDmRaWrbKntOlZ3n0= +-----END CERTIFICATE----- diff --git a/tests/StackExchange.Redis.Tests/xunit.runner.json b/tests/StackExchange.Redis.Tests/xunit.runner.json new file mode 100644 index 000000000..dc36b1875 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/xunit.runner.json @@ -0,0 +1,8 @@ +{ + "methodDisplay": "classAndMethod", + "parallelizeAssembly": true, + "maxParallelThreads": "2x", + "parallelizeTestCollections": true, + "diagnosticMessages": false, + "longRunningTestSeconds": 60 +} \ No newline at end of file diff --git a/toys/KestrelRedisServer/KestrelRedisServer.csproj b/toys/KestrelRedisServer/KestrelRedisServer.csproj new file mode 100644 index 000000000..8854d6ac8 --- /dev/null +++ b/toys/KestrelRedisServer/KestrelRedisServer.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + $(NoWarn);CS1591 + enable + enable + + + + + + + diff --git a/toys/KestrelRedisServer/Program.cs b/toys/KestrelRedisServer/Program.cs new file mode 100644 index 000000000..6cabf95d1 --- /dev/null +++ b/toys/KestrelRedisServer/Program.cs @@ -0,0 +1,41 @@ +using KestrelRedisServer; +using Microsoft.AspNetCore.Connections; +using StackExchange.Redis.Server; + +var server = new MemoryCacheRedisServer(); + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddSingleton(server); +builder.WebHost.ConfigureKestrel(options => +{ + // HTTP 5000 (test/debug API only) + options.ListenLocalhost(5000); + + // this is the core of using Kestrel to create a TCP server + // TCP 6379 + options.ListenLocalhost(6379, builder => builder.UseConnectionHandler()); +}); + +var app = builder.Build(); + +// redis-specific hack - there is a redis command to shutdown the server +_ = server.Shutdown.ContinueWith( + static (t, s) => + { + try + { + // if the resp server is shutdown by a client: stop the kestrel server too + if (t.Result == RespServer.ShutdownReason.ClientInitiated) + { + ((IServiceProvider)s!).GetService()?.StopApplication(); + } + } + catch { /* Don't go boom on shutdown */ } + }, + app.Services); + +// add debug route +app.Run(context => context.Response.WriteAsync(server.GetStats())); + +// run the server +await app.RunAsync(); diff --git a/toys/KestrelRedisServer/RedisConnectionHandler.cs b/toys/KestrelRedisServer/RedisConnectionHandler.cs new file mode 100644 index 000000000..a447440d9 --- /dev/null +++ b/toys/KestrelRedisServer/RedisConnectionHandler.cs @@ -0,0 +1,15 @@ +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using StackExchange.Redis.Server; + +namespace KestrelRedisServer +{ + public class RedisConnectionHandler : ConnectionHandler + { + private readonly RespServer _server; + public RedisConnectionHandler(RespServer server) => _server = server; + public override Task OnConnectedAsync(ConnectionContext connection) + => _server.RunClientAsync(connection.Transport); + } +} diff --git a/toys/StackExchange.Redis.Server/GlobalSuppressions.cs b/toys/StackExchange.Redis.Server/GlobalSuppressions.cs new file mode 100644 index 000000000..8784fa37f --- /dev/null +++ b/toys/StackExchange.Redis.Server/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "Pending", Scope = "member", Target = "~M:StackExchange.Redis.TypedRedisValue.ToString~System.String")] diff --git a/toys/StackExchange.Redis.Server/MemoryCacheRedisServer.cs b/toys/StackExchange.Redis.Server/MemoryCacheRedisServer.cs new file mode 100644 index 000000000..b57ec4aea --- /dev/null +++ b/toys/StackExchange.Redis.Server/MemoryCacheRedisServer.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Caching; +using System.Runtime.CompilerServices; + +namespace StackExchange.Redis.Server +{ + public class MemoryCacheRedisServer : RedisServer + { + public MemoryCacheRedisServer(TextWriter output = null) : base(1, output) + => CreateNewCache(); + + private MemoryCache _cache; + + private void CreateNewCache() + { + var old = _cache; + _cache = new MemoryCache(GetType().Name); + old?.Dispose(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) _cache.Dispose(); + base.Dispose(disposing); + } + + protected override long Dbsize(int database) => _cache.GetCount(); + protected override RedisValue Get(int database, RedisKey key) + => RedisValue.Unbox(_cache[key]); + protected override void Set(int database, RedisKey key, RedisValue value) + => _cache[key] = value.Box(); + protected override bool Del(int database, RedisKey key) + => _cache.Remove(key) != null; + protected override void Flushdb(int database) + => CreateNewCache(); + + protected override bool Exists(int database, RedisKey key) + => _cache.Contains(key); + + protected override IEnumerable Keys(int database, RedisKey pattern) + { + foreach (var pair in _cache) + { + if (IsMatch(pattern, pair.Key)) yield return pair.Key; + } + } + protected override bool Sadd(int database, RedisKey key, RedisValue value) + => GetSet(key, true).Add(value); + + protected override bool Sismember(int database, RedisKey key, RedisValue value) + => GetSet(key, false)?.Contains(value) ?? false; + + protected override bool Srem(int database, RedisKey key, RedisValue value) + { + var set = GetSet(key, false); + if (set != null && set.Remove(value)) + { + if (set.Count == 0) _cache.Remove(key); + return true; + } + return false; + } + protected override long Scard(int database, RedisKey key) + => GetSet(key, false)?.Count ?? 0; + + private HashSet GetSet(RedisKey key, bool create) + { + var set = (HashSet)_cache[key]; + if (set == null && create) + { + set = new HashSet(); + _cache[key] = set; + } + return set; + } + + protected override RedisValue Spop(int database, RedisKey key) + { + var set = GetSet(key, false); + if (set == null) return RedisValue.Null; + + var result = set.First(); + set.Remove(result); + if (set.Count == 0) _cache.Remove(key); + return result; + } + + protected override long Lpush(int database, RedisKey key, RedisValue value) + { + var stack = GetStack(key, true); + stack.Push(value); + return stack.Count; + } + protected override RedisValue Lpop(int database, RedisKey key) + { + var stack = GetStack(key, false); + if (stack == null) return RedisValue.Null; + + var val = stack.Pop(); + if (stack.Count == 0) _cache.Remove(key); + return val; + } + + protected override long Llen(int database, RedisKey key) + => GetStack(key, false)?.Count ?? 0; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException(); + + protected override void LRange(int database, RedisKey key, long start, Span arr) + { + var stack = GetStack(key, false); + + using (var iter = stack.GetEnumerator()) + { + // skip + while (start-- > 0) if (!iter.MoveNext()) ThrowArgumentOutOfRangeException(); + + // take + for (int i = 0; i < arr.Length; i++) + { + if (!iter.MoveNext()) ThrowArgumentOutOfRangeException(); + arr[i] = TypedRedisValue.BulkString(iter.Current); + } + } + } + + private Stack GetStack(RedisKey key, bool create) + { + var stack = (Stack)_cache[key]; + if (stack == null && create) + { + stack = new Stack(); + _cache[key] = stack; + } + return stack; + } + } +} diff --git a/toys/StackExchange.Redis.Server/RedisClient.cs b/toys/StackExchange.Redis.Server/RedisClient.cs new file mode 100644 index 000000000..bfe27b042 --- /dev/null +++ b/toys/StackExchange.Redis.Server/RedisClient.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.IO.Pipelines; + +namespace StackExchange.Redis.Server +{ + public sealed class RedisClient : IDisposable + { + internal int SkipReplies { get; set; } + internal bool ShouldSkipResponse() + { + if (SkipReplies > 0) + { + SkipReplies--; + return true; + } + return false; + } + private HashSet _subscripions; + public int SubscriptionCount => _subscripions?.Count ?? 0; + internal int Subscribe(RedisChannel channel) + { + if (_subscripions == null) _subscripions = new HashSet(); + _subscripions.Add(channel); + return _subscripions.Count; + } + internal int Unsubscribe(RedisChannel channel) + { + if (_subscripions == null) return 0; + _subscripions.Remove(channel); + return _subscripions.Count; + } + public int Database { get; set; } + public string Name { get; set; } + internal IDuplexPipe LinkedPipe { get; set; } + public bool Closed { get; internal set; } + public int Id { get; internal set; } + + public void Dispose() + { + Closed = true; + var pipe = LinkedPipe; + LinkedPipe = null; + if (pipe != null) + { + try { pipe.Input.CancelPendingRead(); } catch { } + try { pipe.Input.Complete(); } catch { } + try { pipe.Output.CancelPendingFlush(); } catch { } + try { pipe.Output.Complete(); } catch { } + if (pipe is IDisposable d) try { d.Dispose(); } catch { } + } + } + } +} diff --git a/toys/StackExchange.Redis.Server/RedisRequest.cs b/toys/StackExchange.Redis.Server/RedisRequest.cs new file mode 100644 index 000000000..36d133bab --- /dev/null +++ b/toys/StackExchange.Redis.Server/RedisRequest.cs @@ -0,0 +1,64 @@ +using System; + +namespace StackExchange.Redis.Server +{ + public readonly ref struct RedisRequest + { + // why ref? don't *really* need it, but: these things are "in flight" + // based on an open RawResult (which is just the detokenized ReadOnlySequence) + // so: using "ref" makes it clear that you can't expect to store these and have + // them keep working + private readonly RawResult _inner; + + public int Count { get; } + + public override string ToString() => Count == 0 ? "(n/a)" : GetString(0); + public override bool Equals(object obj) => throw new NotSupportedException(); + + public TypedRedisValue WrongArgCount() => TypedRedisValue.Error($"ERR wrong number of arguments for '{ToString()}' command"); + + public TypedRedisValue CommandNotFound() + => TypedRedisValue.Error($"ERR unknown command '{ToString()}'"); + + public TypedRedisValue UnknownSubcommandOrArgumentCount() => TypedRedisValue.Error($"ERR Unknown subcommand or wrong number of arguments for '{ToString()}'."); + + public string GetString(int index) + => _inner[index].GetString(); + + public bool IsString(int index, string value) // TODO: optimize + => string.Equals(value, _inner[index].GetString(), StringComparison.OrdinalIgnoreCase); + + public override int GetHashCode() => throw new NotSupportedException(); + internal RedisRequest(scoped in RawResult result) + { + _inner = result; + Count = result.ItemsCount; + } + + public RedisValue GetValue(int index) + => _inner[index].AsRedisValue(); + + public int GetInt32(int index) + => (int)_inner[index].AsRedisValue(); + + public long GetInt64(int index) => (long)_inner[index].AsRedisValue(); + + public RedisKey GetKey(int index) => _inner[index].AsRedisKey(); + + internal RedisChannel GetChannel(int index, RedisChannel.RedisChannelOptions options) + => _inner[index].AsRedisChannel(null, options); + + internal bool TryGetCommandBytes(int i, out CommandBytes command) + { + var payload = _inner[i].Payload; + if (payload.Length > CommandBytes.MaxLength) + { + command = default; + return false; + } + + command = payload.IsEmpty ? default : new CommandBytes(payload); + return true; + } + } +} diff --git a/toys/StackExchange.Redis.Server/RedisServer.cs b/toys/StackExchange.Redis.Server/RedisServer.cs new file mode 100644 index 000000000..52728fd44 --- /dev/null +++ b/toys/StackExchange.Redis.Server/RedisServer.cs @@ -0,0 +1,547 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace StackExchange.Redis.Server +{ + public abstract class RedisServer : RespServer + { + // non-trivial wildcards not implemented yet! + public static bool IsMatch(string pattern, string key) => + pattern == "*" || string.Equals(pattern, key, StringComparison.OrdinalIgnoreCase); + + protected RedisServer(int databases = 16, TextWriter output = null) : base(output) + { + if (databases < 1) throw new ArgumentOutOfRangeException(nameof(databases)); + Databases = databases; + var config = ServerConfiguration; + config["timeout"] = "0"; + config["slave-read-only"] = "yes"; + config["replica-read-only"] = "yes"; + config["databases"] = databases.ToString(); + config["slaveof"] = ""; + config["replicaof"] = ""; + } + protected override void AppendStats(StringBuilder sb) + { + base.AppendStats(sb); + sb.Append("Databases: ").Append(Databases).AppendLine(); + lock (ServerSyncLock) + { + for (int i = 0; i < Databases; i++) + { + try + { + sb.Append("Database ").Append(i).Append(": ").Append(Dbsize(i)).AppendLine(" keys"); + } + catch { } + } + } + } + public int Databases { get; } + + [RedisCommand(-3)] + protected virtual TypedRedisValue Sadd(RedisClient client, RedisRequest request) + { + int added = 0; + var key = request.GetKey(1); + for (int i = 2; i < request.Count; i++) + { + if (Sadd(client.Database, key, request.GetValue(i))) + added++; + } + return TypedRedisValue.Integer(added); + } + protected virtual bool Sadd(int database, RedisKey key, RedisValue value) => throw new NotSupportedException(); + + [RedisCommand(-3)] + protected virtual TypedRedisValue Srem(RedisClient client, RedisRequest request) + { + int removed = 0; + var key = request.GetKey(1); + for (int i = 2; i < request.Count; i++) + { + if (Srem(client.Database, key, request.GetValue(i))) + removed++; + } + return TypedRedisValue.Integer(removed); + } + protected virtual bool Srem(int database, RedisKey key, RedisValue value) => throw new NotSupportedException(); + + [RedisCommand(2)] + protected virtual TypedRedisValue Spop(RedisClient client, RedisRequest request) + => TypedRedisValue.BulkString(Spop(client.Database, request.GetKey(1))); + + protected virtual RedisValue Spop(int database, RedisKey key) => throw new NotSupportedException(); + + [RedisCommand(2)] + protected virtual TypedRedisValue Scard(RedisClient client, RedisRequest request) + => TypedRedisValue.Integer(Scard(client.Database, request.GetKey(1))); + + protected virtual long Scard(int database, RedisKey key) => throw new NotSupportedException(); + + [RedisCommand(3)] + protected virtual TypedRedisValue Sismember(RedisClient client, RedisRequest request) + => Sismember(client.Database, request.GetKey(1), request.GetValue(2)) ? TypedRedisValue.One : TypedRedisValue.Zero; + + protected virtual bool Sismember(int database, RedisKey key, RedisValue value) => throw new NotSupportedException(); + + [RedisCommand(3, "client", "setname", LockFree = true)] + protected virtual TypedRedisValue ClientSetname(RedisClient client, RedisRequest request) + { + client.Name = request.GetString(2); + return TypedRedisValue.OK; + } + + [RedisCommand(2, "client", "getname", LockFree = true)] + protected virtual TypedRedisValue ClientGetname(RedisClient client, RedisRequest request) + => TypedRedisValue.BulkString(client.Name); + + [RedisCommand(3, "client", "reply", LockFree = true)] + protected virtual TypedRedisValue ClientReply(RedisClient client, RedisRequest request) + { + if (request.IsString(2, "on")) client.SkipReplies = -1; // reply to nothing + else if (request.IsString(2, "off")) client.SkipReplies = 0; // reply to everything + else if (request.IsString(2, "skip")) client.SkipReplies = 2; // this one, and the next one + else return TypedRedisValue.Error("ERR syntax error"); + return TypedRedisValue.OK; + } + + [RedisCommand(-1)] + protected virtual TypedRedisValue Cluster(RedisClient client, RedisRequest request) + => request.CommandNotFound(); + + [RedisCommand(-3)] + protected virtual TypedRedisValue Lpush(RedisClient client, RedisRequest request) + { + var key = request.GetKey(1); + long length = -1; + for (int i = 2; i < request.Count; i++) + { + length = Lpush(client.Database, key, request.GetValue(i)); + } + return TypedRedisValue.Integer(length); + } + + [RedisCommand(-3)] + protected virtual TypedRedisValue Rpush(RedisClient client, RedisRequest request) + { + var key = request.GetKey(1); + long length = -1; + for (int i = 2; i < request.Count; i++) + { + length = Rpush(client.Database, key, request.GetValue(i)); + } + return TypedRedisValue.Integer(length); + } + + [RedisCommand(2)] + protected virtual TypedRedisValue Lpop(RedisClient client, RedisRequest request) + => TypedRedisValue.BulkString(Lpop(client.Database, request.GetKey(1))); + + [RedisCommand(2)] + protected virtual TypedRedisValue Rpop(RedisClient client, RedisRequest request) + => TypedRedisValue.BulkString(Rpop(client.Database, request.GetKey(1))); + + [RedisCommand(2)] + protected virtual TypedRedisValue Llen(RedisClient client, RedisRequest request) + => TypedRedisValue.Integer(Llen(client.Database, request.GetKey(1))); + + protected virtual long Lpush(int database, RedisKey key, RedisValue value) => throw new NotSupportedException(); + protected virtual long Rpush(int database, RedisKey key, RedisValue value) => throw new NotSupportedException(); + protected virtual long Llen(int database, RedisKey key) => throw new NotSupportedException(); + protected virtual RedisValue Rpop(int database, RedisKey key) => throw new NotSupportedException(); + protected virtual RedisValue Lpop(int database, RedisKey key) => throw new NotSupportedException(); + + [RedisCommand(4)] + protected virtual TypedRedisValue LRange(RedisClient client, RedisRequest request) + { + var key = request.GetKey(1); + long start = request.GetInt64(2), stop = request.GetInt64(3); + + var len = Llen(client.Database, key); + if (len == 0) return TypedRedisValue.EmptyArray; + + if (start < 0) start = len + start; + if (stop < 0) stop = len + stop; + + if (stop < 0 || start >= len || stop < start) return TypedRedisValue.EmptyArray; + + if (start < 0) start = 0; + else if (start >= len) start = len - 1; + + if (stop < 0) stop = 0; + else if (stop >= len) stop = len - 1; + + var arr = TypedRedisValue.Rent(checked((int)((stop - start) + 1)), out var span); + LRange(client.Database, key, start, span); + return arr; + } + protected virtual void LRange(int database, RedisKey key, long start, Span arr) => throw new NotSupportedException(); + + protected virtual void OnUpdateServerConfiguration() { } + protected RedisConfig ServerConfiguration { get; } = RedisConfig.Create(); + protected struct RedisConfig + { + internal static RedisConfig Create() => new RedisConfig( + new Dictionary(StringComparer.OrdinalIgnoreCase)); + + internal Dictionary Wrapped { get; } + public int Count => Wrapped.Count; + + private RedisConfig(Dictionary inner) => Wrapped = inner; + public string this[string key] + { + get => Wrapped.TryGetValue(key, out var val) ? val : null; + set + { + if (Wrapped.ContainsKey(key)) Wrapped[key] = value; // no need to fix case + else Wrapped[key.ToLowerInvariant()] = value; + } + } + + internal int CountMatch(string pattern) + { + int count = 0; + foreach (var pair in Wrapped) + { + if (IsMatch(pattern, pair.Key)) count++; + } + return count; + } + } + [RedisCommand(3, "config", "get", LockFree = true)] + protected virtual TypedRedisValue Config(RedisClient client, RedisRequest request) + { + var pattern = request.GetString(2); + + OnUpdateServerConfiguration(); + var config = ServerConfiguration; + var matches = config.CountMatch(pattern); + if (matches == 0) return TypedRedisValue.EmptyArray; + + var arr = TypedRedisValue.Rent(2 * matches, out var span); + int index = 0; + foreach (var pair in config.Wrapped) + { + if (IsMatch(pattern, pair.Key)) + { + span[index++] = TypedRedisValue.BulkString(pair.Key); + span[index++] = TypedRedisValue.BulkString(pair.Value); + } + } + if (index != span.Length) + { + arr.Recycle(index); + throw new InvalidOperationException("Configuration CountMatch fail"); + } + return arr; + } + + [RedisCommand(2, LockFree = true)] + protected virtual TypedRedisValue Echo(RedisClient client, RedisRequest request) + => TypedRedisValue.BulkString(request.GetValue(1)); + + [RedisCommand(2)] + protected virtual TypedRedisValue Exists(RedisClient client, RedisRequest request) + { + int count = 0; + var db = client.Database; + for (int i = 1; i < request.Count; i++) + { + if (Exists(db, request.GetKey(i))) + count++; + } + return TypedRedisValue.Integer(count); + } + + protected virtual bool Exists(int database, RedisKey key) + { + try + { + return !Get(database, key).IsNull; + } + catch (InvalidCastException) { return true; } // to be an invalid cast, it must exist + } + + [RedisCommand(2)] + protected virtual TypedRedisValue Get(RedisClient client, RedisRequest request) + => TypedRedisValue.BulkString(Get(client.Database, request.GetKey(1))); + + protected virtual RedisValue Get(int database, RedisKey key) => throw new NotSupportedException(); + + [RedisCommand(3)] + protected virtual TypedRedisValue Set(RedisClient client, RedisRequest request) + { + Set(client.Database, request.GetKey(1), request.GetValue(2)); + return TypedRedisValue.OK; + } + protected virtual void Set(int database, RedisKey key, RedisValue value) => throw new NotSupportedException(); + [RedisCommand(1)] + protected new virtual TypedRedisValue Shutdown(RedisClient client, RedisRequest request) + { + DoShutdown(ShutdownReason.ClientInitiated); + return TypedRedisValue.OK; + } + [RedisCommand(2)] + protected virtual TypedRedisValue Strlen(RedisClient client, RedisRequest request) + => TypedRedisValue.Integer(Strlen(client.Database, request.GetKey(1))); + + protected virtual long Strlen(int database, RedisKey key) => Get(database, key).Length(); + + [RedisCommand(-2)] + protected virtual TypedRedisValue Del(RedisClient client, RedisRequest request) + { + int count = 0; + for (int i = 1; i < request.Count; i++) + { + if (Del(client.Database, request.GetKey(i))) + count++; + } + return TypedRedisValue.Integer(count); + } + protected virtual bool Del(int database, RedisKey key) => throw new NotSupportedException(); + + [RedisCommand(1)] + protected virtual TypedRedisValue Dbsize(RedisClient client, RedisRequest request) + => TypedRedisValue.Integer(Dbsize(client.Database)); + + protected virtual long Dbsize(int database) => throw new NotSupportedException(); + + [RedisCommand(1)] + protected virtual TypedRedisValue Flushall(RedisClient client, RedisRequest request) + { + var count = Databases; + for (int i = 0; i < count; i++) + { + Flushdb(i); + } + return TypedRedisValue.OK; + } + + [RedisCommand(1)] + protected virtual TypedRedisValue Flushdb(RedisClient client, RedisRequest request) + { + Flushdb(client.Database); + return TypedRedisValue.OK; + } + protected virtual void Flushdb(int database) => throw new NotSupportedException(); + + [RedisCommand(-1, LockFree = true, MaxArgs = 2)] + protected virtual TypedRedisValue Info(RedisClient client, RedisRequest request) + { + var info = Info(request.Count == 1 ? null : request.GetString(1)); + return TypedRedisValue.BulkString(info); + } + protected virtual string Info(string selected) + { + var sb = new StringBuilder(); + bool IsMatch(string section) => string.IsNullOrWhiteSpace(selected) + || string.Equals(section, selected, StringComparison.OrdinalIgnoreCase); + if (IsMatch("Server")) Info(sb, "Server"); + if (IsMatch("Clients")) Info(sb, "Clients"); + if (IsMatch("Memory")) Info(sb, "Memory"); + if (IsMatch("Persistence")) Info(sb, "Persistence"); + if (IsMatch("Stats")) Info(sb, "Stats"); + if (IsMatch("Replication")) Info(sb, "Replication"); + if (IsMatch("Keyspace")) Info(sb, "Keyspace"); + return sb.ToString(); + } + + [RedisCommand(2)] + protected virtual TypedRedisValue Keys(RedisClient client, RedisRequest request) + { + List found = null; + foreach (var key in Keys(client.Database, request.GetKey(1))) + { + if (found == null) found = new List(); + found.Add(TypedRedisValue.BulkString(key.AsRedisValue())); + } + if (found == null) return TypedRedisValue.EmptyArray; + return TypedRedisValue.MultiBulk(found); + } + protected virtual IEnumerable Keys(int database, RedisKey pattern) => throw new NotSupportedException(); + + protected virtual void Info(StringBuilder sb, string section) + { + StringBuilder AddHeader() + { + if (sb.Length != 0) sb.AppendLine(); + return sb.Append("# ").AppendLine(section); + } + + switch (section) + { + case "Server": + AddHeader().AppendLine("redis_version:1.0") + .AppendLine("redis_mode:standalone") + .Append("os:").Append(Environment.OSVersion).AppendLine() + .Append("arch_bits:x").Append(IntPtr.Size * 8).AppendLine(); + using (var process = Process.GetCurrentProcess()) + { + sb.Append("process:").Append(process.Id).AppendLine(); + } + // var port = TcpPort(); + // if (port >= 0) sb.Append("tcp_port:").Append(port).AppendLine(); + break; + case "Clients": + AddHeader().Append("connected_clients:").Append(ClientCount).AppendLine(); + break; + case "Memory": + break; + case "Persistence": + AddHeader().AppendLine("loading:0"); + break; + case "Stats": + AddHeader().Append("total_connections_received:").Append(TotalClientCount).AppendLine() + .Append("total_commands_processed:").Append(TotalCommandsProcesed).AppendLine(); + break; + case "Replication": + AddHeader().AppendLine("role:master"); + break; + case "Keyspace": + break; + } + } + [RedisCommand(2, "memory", "purge")] + protected virtual TypedRedisValue MemoryPurge(RedisClient client, RedisRequest request) + { + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); + return TypedRedisValue.OK; + } + [RedisCommand(-2)] + protected virtual TypedRedisValue Mget(RedisClient client, RedisRequest request) + { + int argCount = request.Count; + var arr = TypedRedisValue.Rent(argCount - 1, out var span); + var db = client.Database; + for (int i = 1; i < argCount; i++) + { + span[i - 1] = TypedRedisValue.BulkString(Get(db, request.GetKey(i))); + } + return arr; + } + [RedisCommand(-3)] + protected virtual TypedRedisValue Mset(RedisClient client, RedisRequest request) + { + int argCount = request.Count; + var db = client.Database; + for (int i = 1; i < argCount;) + { + Set(db, request.GetKey(i++), request.GetValue(i++)); + } + return TypedRedisValue.OK; + } + [RedisCommand(-1, LockFree = true, MaxArgs = 2)] + protected virtual TypedRedisValue Ping(RedisClient client, RedisRequest request) + => TypedRedisValue.SimpleString(request.Count == 1 ? "PONG" : request.GetString(1)); + + [RedisCommand(1, LockFree = true)] + protected virtual TypedRedisValue Quit(RedisClient client, RedisRequest request) + { + RemoveClient(client); + return TypedRedisValue.OK; + } + + [RedisCommand(1, LockFree = true)] + protected virtual TypedRedisValue Role(RedisClient client, RedisRequest request) + { + var arr = TypedRedisValue.Rent(3, out var span); + span[0] = TypedRedisValue.BulkString("master"); + span[1] = TypedRedisValue.Integer(0); + span[2] = TypedRedisValue.EmptyArray; + return arr; + } + + [RedisCommand(2, LockFree = true)] + protected virtual TypedRedisValue Select(RedisClient client, RedisRequest request) + { + var raw = request.GetValue(1); + if (!raw.IsInteger) return TypedRedisValue.Error("ERR invalid DB index"); + int db = (int)raw; + if (db < 0 || db >= Databases) return TypedRedisValue.Error("ERR DB index is out of range"); + client.Database = db; + return TypedRedisValue.OK; + } + + [RedisCommand(-2)] + protected virtual TypedRedisValue Subscribe(RedisClient client, RedisRequest request) + => SubscribeImpl(client, request); + [RedisCommand(-2)] + protected virtual TypedRedisValue Unsubscribe(RedisClient client, RedisRequest request) + => SubscribeImpl(client, request); + + private TypedRedisValue SubscribeImpl(RedisClient client, RedisRequest request) + { + var reply = TypedRedisValue.Rent(3 * (request.Count - 1), out var span); + int index = 0; + request.TryGetCommandBytes(0, out var cmd); + var cmdString = TypedRedisValue.BulkString(cmd.ToArray()); + var mode = cmd[0] == (byte)'p' ? RedisChannel.RedisChannelOptions.Pattern : RedisChannel.RedisChannelOptions.None; + for (int i = 1; i < request.Count; i++) + { + var channel = request.GetChannel(i, mode); + int count; + if (s_Subscribe.Equals(cmd)) + { + count = client.Subscribe(channel); + } + else if (s_Unsubscribe.Equals(cmd)) + { + count = client.Unsubscribe(channel); + } + else + { + reply.Recycle(index); + return TypedRedisValue.Nil; + } + span[index++] = cmdString; + span[index++] = TypedRedisValue.BulkString((byte[])channel); + span[index++] = TypedRedisValue.Integer(count); + } + return reply; + } + private static readonly CommandBytes + s_Subscribe = new CommandBytes("subscribe"), + s_Unsubscribe = new CommandBytes("unsubscribe"); + private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + [RedisCommand(1, LockFree = true)] + protected virtual TypedRedisValue Time(RedisClient client, RedisRequest request) + { + var delta = Time() - UnixEpoch; + var ticks = delta.Ticks; + var seconds = ticks / TimeSpan.TicksPerSecond; + var micros = (ticks % TimeSpan.TicksPerSecond) / (TimeSpan.TicksPerMillisecond / 1000); + var reply = TypedRedisValue.Rent(2, out var span); + span[0] = TypedRedisValue.BulkString(seconds); + span[1] = TypedRedisValue.BulkString(micros); + return reply; + } + protected virtual DateTime Time() => DateTime.UtcNow; + + [RedisCommand(-2)] + protected virtual TypedRedisValue Unlink(RedisClient client, RedisRequest request) + => Del(client, request); + + [RedisCommand(2)] + protected virtual TypedRedisValue Incr(RedisClient client, RedisRequest request) + => TypedRedisValue.Integer(IncrBy(client.Database, request.GetKey(1), 1)); + [RedisCommand(2)] + protected virtual TypedRedisValue Decr(RedisClient client, RedisRequest request) + => TypedRedisValue.Integer(IncrBy(client.Database, request.GetKey(1), -1)); + + [RedisCommand(3)] + protected virtual TypedRedisValue IncrBy(RedisClient client, RedisRequest request) + => TypedRedisValue.Integer(IncrBy(client.Database, request.GetKey(1), request.GetInt64(2))); + + protected virtual long IncrBy(int database, RedisKey key, long delta) + { + var value = ((long)Get(database, key)) + delta; + Set(database, key, value); + return value; + } + } +} diff --git a/toys/StackExchange.Redis.Server/RespServer.cs b/toys/StackExchange.Redis.Server/RespServer.cs new file mode 100644 index 000000000..75a0273ea --- /dev/null +++ b/toys/StackExchange.Redis.Server/RespServer.cs @@ -0,0 +1,521 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Pipelines.Sockets.Unofficial; +using Pipelines.Sockets.Unofficial.Arenas; + +namespace StackExchange.Redis.Server +{ + public abstract partial class RespServer : IDisposable + { + public enum ShutdownReason + { + ServerDisposed, + ClientInitiated, + } + + private readonly List _clients = new List(); + private readonly TextWriter _output; + + protected RespServer(TextWriter output = null) + { + _output = output; + _commands = BuildCommands(this); + } + + private static Dictionary BuildCommands(RespServer server) + { + static RedisCommandAttribute CheckSignatureAndGetAttribute(MethodInfo method) + { + if (method.ReturnType != typeof(TypedRedisValue)) return null; + var p = method.GetParameters(); + if (p.Length != 2 || p[0].ParameterType != typeof(RedisClient) || p[1].ParameterType != typeof(RedisRequest)) + return null; + return (RedisCommandAttribute)Attribute.GetCustomAttribute(method, typeof(RedisCommandAttribute)); + } + var grouped = from method in server.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + let attrib = CheckSignatureAndGetAttribute(method) + where attrib != null + select new RespCommand(attrib, method, server) into cmd + group cmd by cmd.Command; + + var result = new Dictionary(); + foreach (var grp in grouped) + { + RespCommand parent; + if (grp.Any(x => x.IsSubCommand)) + { + var subs = grp.Where(x => x.IsSubCommand).ToArray(); + parent = grp.SingleOrDefault(x => !x.IsSubCommand).WithSubCommands(subs); + } + else + { + parent = grp.Single(); + } + result.Add(new CommandBytes(grp.Key), parent); + } + return result; + } + + public string GetStats() + { + var sb = new StringBuilder(); + AppendStats(sb); + return sb.ToString(); + } + + protected virtual void AppendStats(StringBuilder sb) => + sb.Append("Current clients:\t").Append(ClientCount).AppendLine() + .Append("Total clients:\t").Append(TotalClientCount).AppendLine() + .Append("Total operations:\t").Append(TotalCommandsProcesed).AppendLine() + .Append("Error replies:\t").Append(TotalErrorCount).AppendLine(); + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + protected sealed class RedisCommandAttribute : Attribute + { + public RedisCommandAttribute( + int arity, + string command = null, + string subcommand = null) + { + Command = command; + SubCommand = subcommand; + Arity = arity; + MaxArgs = Arity > 0 ? Arity : int.MaxValue; + } + public int MaxArgs { get; set; } + public string Command { get; } + public string SubCommand { get; } + public int Arity { get; } + public bool LockFree { get; set; } + } + private readonly Dictionary _commands; + + private readonly struct RespCommand + { + public RespCommand(RedisCommandAttribute attrib, MethodInfo method, RespServer server) + { + _operation = (RespOperation)Delegate.CreateDelegate(typeof(RespOperation), server, method); + Command = (string.IsNullOrWhiteSpace(attrib.Command) ? method.Name : attrib.Command).Trim().ToLowerInvariant(); + CommandBytes = new CommandBytes(Command); + SubCommand = attrib.SubCommand?.Trim()?.ToLowerInvariant(); + Arity = attrib.Arity; + MaxArgs = attrib.MaxArgs; + LockFree = attrib.LockFree; + _subcommands = null; + } + private CommandBytes CommandBytes { get; } + public string Command { get; } + public string SubCommand { get; } + public bool IsSubCommand => !string.IsNullOrEmpty(SubCommand); + public int Arity { get; } + public int MaxArgs { get; } + public bool LockFree { get; } + private readonly RespOperation _operation; + + private readonly RespCommand[] _subcommands; + public bool HasSubCommands => _subcommands != null; + internal RespCommand WithSubCommands(RespCommand[] subs) + => new RespCommand(this, subs); + private RespCommand(in RespCommand parent, RespCommand[] subs) + { + if (parent.IsSubCommand) throw new InvalidOperationException("Cannot have nested sub-commands"); + if (parent.HasSubCommands) throw new InvalidOperationException("Already has sub-commands"); + if (subs == null || subs.Length == 0) throw new InvalidOperationException("Cannot add empty sub-commands"); + + Command = parent.Command; + CommandBytes = parent.CommandBytes; + SubCommand = parent.SubCommand; + Arity = parent.Arity; + MaxArgs = parent.MaxArgs; + LockFree = parent.LockFree; + _operation = parent._operation; + _subcommands = subs; + } + public bool IsUnknown => _operation == null; + public RespCommand Resolve(in RedisRequest request) + { + if (request.Count >= 2) + { + var subs = _subcommands; + if (subs != null) + { + var subcommand = request.GetString(1); + for (int i = 0; i < subs.Length; i++) + { + if (string.Equals(subcommand, subs[i].SubCommand, StringComparison.OrdinalIgnoreCase)) + return subs[i]; + } + } + } + return this; + } + public TypedRedisValue Execute(RedisClient client, in RedisRequest request) + { + var args = request.Count; + if (!CheckArity(request.Count)) + { + return IsSubCommand + ? request.UnknownSubcommandOrArgumentCount() + : request.WrongArgCount(); + } + + return _operation(client, request); + } + private bool CheckArity(int count) + => count <= MaxArgs && (Arity <= 0 ? count >= -Arity : count == Arity); + + internal int NetArity() + { + if (!HasSubCommands) return Arity; + + var minMagnitude = _subcommands.Min(x => Math.Abs(x.Arity)); + bool varadic = _subcommands.Any(x => x.Arity <= 0); + if (!IsUnknown) + { + minMagnitude = Math.Min(minMagnitude, Math.Abs(Arity)); + if (Arity <= 0) varadic = true; + } + return varadic ? -minMagnitude : minMagnitude; + } + } + + private delegate TypedRedisValue RespOperation(RedisClient client, RedisRequest request); + + // for extensibility, so that a subclass can get their own client type + // to be used via ListenForConnections + public virtual RedisClient CreateClient() => new RedisClient(); + + public int ClientCount + { + get { lock (_clients) { return _clients.Count; } } + } + public int TotalClientCount { get; private set; } + private int _nextId; + public RedisClient AddClient() + { + var client = CreateClient(); + lock (_clients) + { + ThrowIfShutdown(); + client.Id = ++_nextId; + _clients.Add(client); + TotalClientCount++; + } + return client; + } + public bool RemoveClient(RedisClient client) + { + if (client == null) return false; + lock (_clients) + { + client.Closed = true; + return _clients.Remove(client); + } + } + + private readonly TaskCompletionSource _shutdown = TaskSource.Create(null, TaskCreationOptions.RunContinuationsAsynchronously); + private bool _isShutdown; + protected void ThrowIfShutdown() + { + if (_isShutdown) throw new InvalidOperationException("The server is shutting down"); + } + protected void DoShutdown(ShutdownReason reason) + { + if (_isShutdown) return; + Log("Server shutting down..."); + _isShutdown = true; + lock (_clients) + { + foreach (var client in _clients) client.Dispose(); + _clients.Clear(); + } + _shutdown.TrySetResult(reason); + } + public Task Shutdown => _shutdown.Task; + public void Dispose() => Dispose(true); + protected virtual void Dispose(bool disposing) + { + _arena.Dispose(); + DoShutdown(ShutdownReason.ServerDisposed); + } + + public async Task RunClientAsync(IDuplexPipe pipe) + { + Exception fault = null; + RedisClient client = null; + try + { + client = AddClient(); + while (!client.Closed) + { + var readResult = await pipe.Input.ReadAsync().ConfigureAwait(false); + var buffer = readResult.Buffer; + + bool makingProgress = false; + while (!client.Closed && await TryProcessRequestAsync(ref buffer, client, pipe.Output).ConfigureAwait(false)) + { + makingProgress = true; + } + pipe.Input.AdvanceTo(buffer.Start, buffer.End); + + if (!makingProgress && readResult.IsCompleted) + { // nothing to do, and nothing more will be arriving + break; + } + } + } + catch (ConnectionResetException) { } + catch (ObjectDisposedException) { } + catch (Exception ex) + { + if (ex.GetType().Name != nameof(ConnectionResetException)) + { + // aspnet core has one too; swallow it by pattern + fault = ex; + throw; + } + } + finally + { + RemoveClient(client); + try { pipe.Input.Complete(fault); } catch { } + try { pipe.Output.Complete(fault); } catch { } + + if (fault != null && !_isShutdown) + { + Log("Connection faulted (" + fault.GetType().Name + "): " + fault.Message); + } + } + } + public void Log(string message) + { + var output = _output; + if (output != null) + { + lock (output) + { + output.WriteLine(message); + } + } + } + + public static async ValueTask WriteResponseAsync(RedisClient client, PipeWriter output, TypedRedisValue value) + { + static void WritePrefix(PipeWriter ooutput, char pprefix) + { + var span = ooutput.GetSpan(1); + span[0] = (byte)pprefix; + ooutput.Advance(1); + } + + if (value.IsNil) return; // not actually a request (i.e. empty/whitespace request) + if (client != null && client.ShouldSkipResponse()) return; // intentionally skipping the result + char prefix; + switch (value.Type.ToResp2()) + { + case ResultType.Integer: + PhysicalConnection.WriteInteger(output, (long)value.AsRedisValue()); + break; + case ResultType.Error: + prefix = '-'; + goto BasicMessage; + case ResultType.SimpleString: + prefix = '+'; + BasicMessage: + WritePrefix(output, prefix); + var val = (string)value.AsRedisValue(); + var expectedLength = Encoding.UTF8.GetByteCount(val); + PhysicalConnection.WriteRaw(output, val, expectedLength); + PhysicalConnection.WriteCrlf(output); + break; + case ResultType.BulkString: + PhysicalConnection.WriteBulkString(value.AsRedisValue(), output); + break; + case ResultType.Array: + if (value.IsNullArray) + { + PhysicalConnection.WriteMultiBulkHeader(output, -1); + } + else + { + var segment = value.Segment; + PhysicalConnection.WriteMultiBulkHeader(output, segment.Count); + var arr = segment.Array; + int offset = segment.Offset; + for (int i = 0; i < segment.Count; i++) + { + var item = arr[offset++]; + if (item.IsNil) + throw new InvalidOperationException("Array element cannot be nil, index " + i); + + // note: don't pass client down; this would impact SkipReplies + await WriteResponseAsync(null, output, item); + } + } + break; + default: + throw new InvalidOperationException( + "Unexpected result type: " + value.Type); + } + await output.FlushAsync().ConfigureAwait(false); + } + + private static bool TryParseRequest(Arena arena, ref ReadOnlySequence buffer, out RedisRequest request) + { + var reader = new BufferReader(buffer); + var raw = PhysicalConnection.TryParseResult(false, arena, in buffer, ref reader, false, null, true); + if (raw.HasValue) + { + buffer = reader.SliceFromCurrent(); + request = new RedisRequest(raw); + return true; + } + request = default; + + return false; + } + + private readonly Arena _arena = new Arena(); + + public ValueTask TryProcessRequestAsync(ref ReadOnlySequence buffer, RedisClient client, PipeWriter output) + { + static async ValueTask Awaited(ValueTask wwrite, TypedRedisValue rresponse) + { + await wwrite; + rresponse.Recycle(); + return true; + } + if (!buffer.IsEmpty && TryParseRequest(_arena, ref buffer, out var request)) + { + TypedRedisValue response; + try { response = Execute(client, request); } + finally { _arena.Reset(); } + + var write = WriteResponseAsync(client, output, response); + if (!write.IsCompletedSuccessfully) return Awaited(write, response); + response.Recycle(); + return new ValueTask(true); + } + return new ValueTask(false); + } + + protected object ServerSyncLock => this; + + private long _totalCommandsProcesed, _totalErrorCount; + public long TotalCommandsProcesed => _totalCommandsProcesed; + public long TotalErrorCount => _totalErrorCount; + + public TypedRedisValue Execute(RedisClient client, RedisRequest request) + { + if (request.Count == 0) return default; // not a request + + if (!request.TryGetCommandBytes(0, out var cmdBytes)) return request.CommandNotFound(); + if (cmdBytes.Length == 0) return default; // not a request + Interlocked.Increment(ref _totalCommandsProcesed); + try + { + TypedRedisValue result; + if (_commands.TryGetValue(cmdBytes, out var cmd)) + { + if (cmd.HasSubCommands) + { + cmd = cmd.Resolve(request); + if (cmd.IsUnknown) return request.UnknownSubcommandOrArgumentCount(); + } + if (cmd.LockFree) + { + result = cmd.Execute(client, request); + } + else + { + lock (ServerSyncLock) + { + result = cmd.Execute(client, request); + } + } + } + else + { + result = TypedRedisValue.Nil; + } + + if (result.IsNil) + { + Log($"missing command: '{request.GetString(0)}'"); + return request.CommandNotFound(); + } + if (result.Type == ResultType.Error) Interlocked.Increment(ref _totalErrorCount); + return result; + } + catch (NotSupportedException) + { + Log($"missing command: '{request.GetString(0)}'"); + return request.CommandNotFound(); + } + catch (NotImplementedException) + { + Log($"missing command: '{request.GetString(0)}'"); + return request.CommandNotFound(); + } + catch (InvalidCastException) + { + return TypedRedisValue.Error("WRONGTYPE Operation against a key holding the wrong kind of value"); + } + catch (Exception ex) + { + if (!_isShutdown) Log(ex.Message); + return TypedRedisValue.Error("ERR " + ex.Message); + } + } + + internal static string ToLower(in RawResult value) + { + var val = value.GetString(); + if (string.IsNullOrWhiteSpace(val)) return val; + return val.ToLowerInvariant(); + } + + [RedisCommand(1, LockFree = true)] + protected virtual TypedRedisValue Command(RedisClient client, RedisRequest request) + { + var results = TypedRedisValue.Rent(_commands.Count, out var span); + int index = 0; + foreach (var pair in _commands) + span[index++] = CommandInfo(pair.Value); + return results; + } + + [RedisCommand(-2, "command", "info", LockFree = true)] + protected virtual TypedRedisValue CommandInfo(RedisClient client, RedisRequest request) + { + var results = TypedRedisValue.Rent(request.Count - 2, out var span); + for (int i = 2; i < request.Count; i++) + { + span[i - 2] = request.TryGetCommandBytes(i, out var cmdBytes) + && _commands.TryGetValue(cmdBytes, out var cmdInfo) + ? CommandInfo(cmdInfo) : TypedRedisValue.NullArray; + } + return results; + } + + private TypedRedisValue CommandInfo(RespCommand command) + { + var arr = TypedRedisValue.Rent(6, out var span); + span[0] = TypedRedisValue.BulkString(command.Command); + span[1] = TypedRedisValue.Integer(command.NetArity()); + span[2] = TypedRedisValue.EmptyArray; + span[3] = TypedRedisValue.Zero; + span[4] = TypedRedisValue.Zero; + span[5] = TypedRedisValue.Zero; + return arr; + } + } +} diff --git a/toys/StackExchange.Redis.Server/RespSocketServer.cs b/toys/StackExchange.Redis.Server/RespSocketServer.cs new file mode 100644 index 000000000..c5cc010ab --- /dev/null +++ b/toys/StackExchange.Redis.Server/RespSocketServer.cs @@ -0,0 +1,27 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Pipelines.Sockets.Unofficial; + +namespace StackExchange.Redis.Server +{ + public sealed class RespSocketServer : SocketServer + { + private readonly RespServer _server; + public RespSocketServer(RespServer server) + { + _server = server ?? throw new ArgumentNullException(nameof(server)); + server.Shutdown.ContinueWith((_, o) => ((SocketServer)o).Dispose(), this); + } + protected override void OnStarted(EndPoint endPoint) + => _server.Log("Server is listening on " + endPoint); + + protected override Task OnClientConnectedAsync(in ClientConnection client) + => _server.RunClientAsync(client.Transport); + + protected override void Dispose(bool disposing) + { + if (disposing) _server.Dispose(); + } + } +} diff --git a/toys/StackExchange.Redis.Server/StackExchange.Redis.Server.csproj b/toys/StackExchange.Redis.Server/StackExchange.Redis.Server.csproj new file mode 100644 index 000000000..9908e9088 --- /dev/null +++ b/toys/StackExchange.Redis.Server/StackExchange.Redis.Server.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0 + Basic redis server based on StackExchange.Redis + StackExchange.Redis + StackExchange.Redis.Server + StackExchange.Redis.Server + Server;Async;Redis;Cache;PubSub;Messaging + Library + true + $(NoWarn);CS1591 + + + + + + diff --git a/toys/StackExchange.Redis.Server/TypedRedisValue.cs b/toys/StackExchange.Redis.Server/TypedRedisValue.cs new file mode 100644 index 000000000..a67ab8d5a --- /dev/null +++ b/toys/StackExchange.Redis.Server/TypedRedisValue.cs @@ -0,0 +1,211 @@ +using System; +using System.Buffers; +using System.Collections.Generic; + +namespace StackExchange.Redis +{ + /// + /// A with an eplicit encoding type, which could represent an array of items. + /// + public readonly struct TypedRedisValue + { + // note: if this ever becomes exposed on the public API, it should be made so that it clears; + // can't trust external callers to clear the space, and using recycle without that is dangerous + internal static TypedRedisValue Rent(int count, out Span span) + { + if (count == 0) + { + span = default; + return EmptyArray; + } + var arr = ArrayPool.Shared.Rent(count); + span = new Span(arr, 0, count); + return new TypedRedisValue(arr, count); + } + + /// + /// An invalid empty value that has no type. + /// + public static TypedRedisValue Nil => default; + + /// + /// Returns whether this value is an invalid empty value. + /// + public bool IsNil => Type == ResultType.None; + + /// + /// Returns whether this value represents a null array. + /// + public bool IsNullArray => Type == ResultType.Array && _value.DirectObject == null; + + private readonly RedisValue _value; + + /// + /// The type of value being represented. + /// + public ResultType Type { get; } + + /// + /// Initialize a TypedRedisValue from a value and optionally a type. + /// + /// The value to initialize. + /// The type of . + private TypedRedisValue(RedisValue value, ResultType? type = null) + { + Type = type ?? (value.IsInteger ? ResultType.Integer : ResultType.BulkString); + _value = value; + } + + /// + /// Initialize a TypedRedisValue that represents an error. + /// + /// The error message. + public static TypedRedisValue Error(string value) + => new TypedRedisValue(value, ResultType.Error); + + /// + /// Initialize a TypedRedisValue that represents a simple string. + /// + /// The string value. + public static TypedRedisValue SimpleString(string value) + => new TypedRedisValue(value, ResultType.SimpleString); + + /// + /// The simple string OK. + /// + public static TypedRedisValue OK { get; } = SimpleString("OK"); + internal static TypedRedisValue Zero { get; } = Integer(0); + internal static TypedRedisValue One { get; } = Integer(1); + internal static TypedRedisValue NullArray { get; } = new TypedRedisValue((TypedRedisValue[])null, 0); + internal static TypedRedisValue EmptyArray { get; } = new TypedRedisValue(Array.Empty(), 0); + + /// + /// Gets the array elements as a span. + /// + public ReadOnlySpan Span + { + get + { + if (Type != ResultType.Array) return default; + var arr = (TypedRedisValue[])_value.DirectObject; + if (arr == null) return default; + var length = (int)_value.DirectOverlappedBits64; + return new ReadOnlySpan(arr, 0, length); + } + } + public ArraySegment Segment + { + get + { + if (Type != ResultType.Array) return default; + var arr = (TypedRedisValue[])_value.DirectObject; + if (arr == null) return default; + var length = (int)_value.DirectOverlappedBits64; + return new ArraySegment(arr, 0, length); + } + } + + /// + /// Initialize a that represents an integer. + /// + /// The value to initialize from. + public static TypedRedisValue Integer(long value) + => new TypedRedisValue(value, ResultType.Integer); + + /// + /// Initialize a from a . + /// + /// The items to intialize a value from. + public static TypedRedisValue MultiBulk(ReadOnlySpan items) + { + if (items.IsEmpty) return EmptyArray; + var result = Rent(items.Length, out var span); + items.CopyTo(span); + return result; + } + + /// + /// Initialize a from a collection. + /// + /// The items to intialize a value from. + public static TypedRedisValue MultiBulk(ICollection items) + { + if (items == null) return NullArray; + int count = items.Count; + if (count == 0) return EmptyArray; + var arr = ArrayPool.Shared.Rent(count); + items.CopyTo(arr, 0); + return new TypedRedisValue(arr, count); + } + + /// + /// Initialize a that represents a bulk string. + /// + /// The value to initialize from. + public static TypedRedisValue BulkString(RedisValue value) + => new TypedRedisValue(value, ResultType.BulkString); + + private TypedRedisValue(TypedRedisValue[] oversizedItems, int count) + { + if (oversizedItems == null) + { + if (count != 0) throw new ArgumentOutOfRangeException(nameof(count)); + } + else + { + if (count < 0 || count > oversizedItems.Length) throw new ArgumentOutOfRangeException(nameof(count)); + if (count == 0) oversizedItems = Array.Empty(); + } + _value = new RedisValue(oversizedItems, count); + Type = ResultType.Array; + } + + internal void Recycle(int limit = -1) + { + if (_value.DirectObject is TypedRedisValue[] arr) + { + if (limit < 0) limit = (int)_value.DirectOverlappedBits64; + for (int i = 0; i < limit; i++) + { + arr[i].Recycle(); + } + ArrayPool.Shared.Return(arr, clearArray: false); + } + } + + /// + /// Get the underlying assuming that it is a valid type with a meaningful value. + /// + internal RedisValue AsRedisValue() => Type == ResultType.Array ? default : _value; + + /// + /// Obtain the value as a string. + /// + public override string ToString() + { + switch (Type) + { + case ResultType.BulkString: + case ResultType.SimpleString: + case ResultType.Integer: + case ResultType.Error: + return $"{Type}:{_value}"; + case ResultType.Array: + return $"{Type}:[{Span.Length}]"; + default: + return Type.ToString(); + } + } + + /// + /// Not supported. + /// + public override int GetHashCode() => throw new NotSupportedException(); + + /// + /// Not supported. + /// + /// The object to compare to. + public override bool Equals(object obj) => throw new NotSupportedException(); + } +} diff --git a/toys/StackExchange.Redis.Server/readme.md b/toys/StackExchange.Redis.Server/readme.md new file mode 100644 index 000000000..3128cc971 --- /dev/null +++ b/toys/StackExchange.Redis.Server/readme.md @@ -0,0 +1,41 @@ +# Wait, what is this? + +This is **not** a replacement for redis! + +This is some example code that illustrates using "pipelines" to implement a server, in this case a server that works like 'redis', +implementing the same protocol, and offering similar services. + +What it isn't: + +- supported +- as good as redis +- feature complete +- bug free + +What it is: + +- useful for me to test my protocol handling +- useful for debugging +- useful for anyone looking for reference code for implementing a custom server based on pipelines +- fun + +Example usage: + +```csharp +using System; +using System.Net; +using System.Threading.Tasks; +using StackExchange.Redis.Server; + +static class Program +{ + static async Task Main() + { + using (var server = new MemoryCacheRedisServer(Console.Out)) + { + server.Listen(new IPEndPoint(IPAddress.Loopback, 6379)); + await server.Shutdown; + } + } +} +``` \ No newline at end of file diff --git a/toys/TestConsole/Program.cs b/toys/TestConsole/Program.cs new file mode 100644 index 000000000..dfc43f7f3 --- /dev/null +++ b/toys/TestConsole/Program.cs @@ -0,0 +1,52 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using StackExchange.Redis; + +namespace TestConsole +{ + internal static class Program + { + public static async Task Main() + { + var client = ConnectionMultiplexer.Connect("localhost"); + client.GetDatabase().Ping(); + var db = client.GetDatabase(0); + + var start = DateTime.Now; + + Show(client.GetCounters()); + + var tasks = Enumerable.Range(0, 1000).Select(async i => + { + int timeoutCount = 0; + RedisKey key = i.ToString(); + for (int t = 0; t < 1000; t++) + { + try + { + await db.StringIncrementAsync(key, 1); + } + catch (TimeoutException) { timeoutCount++; } + } + return timeoutCount; + }).ToArray(); + + await Task.WhenAll(tasks); + int totalTimeouts = tasks.Sum(x => x.Result); + Console.WriteLine("Total timeouts: " + totalTimeouts); + Console.WriteLine(); + Show(client.GetCounters()); + + var duration = DateTime.Now.Subtract(start).TotalMilliseconds; + Console.WriteLine($"{duration}ms"); + } + private static void Show(ServerCounters counters) + { + Console.WriteLine("CA: " + counters.Interactive.CompletedAsynchronously); + Console.WriteLine("FA: " + counters.Interactive.FailedAsynchronously); + Console.WriteLine("CS: " + counters.Interactive.CompletedSynchronously); + Console.WriteLine(); + } + } +} diff --git a/toys/TestConsole/TestConsole.csproj b/toys/TestConsole/TestConsole.csproj new file mode 100644 index 000000000..674ec5cfe --- /dev/null +++ b/toys/TestConsole/TestConsole.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0;net472 + SEV2 + true + + + + true + + + + + + diff --git a/toys/TestConsoleBaseline/TestConsoleBaseline.csproj b/toys/TestConsoleBaseline/TestConsoleBaseline.csproj new file mode 100644 index 000000000..10d5ab321 --- /dev/null +++ b/toys/TestConsoleBaseline/TestConsoleBaseline.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0;net461;net462;net47;net472 + + + + true + + + + + + + + + + diff --git a/version.json b/version.json new file mode 100644 index 000000000..63f4a5346 --- /dev/null +++ b/version.json @@ -0,0 +1,18 @@ +{ + "version": "2.9", + "versionHeightOffset": -1, + "assemblyVersion": "2.0", + "publicReleaseRefSpec": [ + "^refs/heads/main$", + "^refs/tags/v\\d+\\.\\d+" + ], + "nugetPackageVersion": { + "semVer": 2 + }, + "cloudBuild": { + "buildNumber": { + "enabled": true, + "setVersionVariables": true + } + } +} \ No newline at end of file